/*
 * My1on1 — visual design
 *
 * ⭐ ONE PRODUCT, ONE LOOK. Until 9 September the front door at `/` and the app
 * behind it were two different designs, and `css/home.css` said so at the top
 * of the file: "THERE IS A SEAM. Press Sign in and the world changes from warm
 * coral to deep green. That is a real, undecided design question." The owner
 * decided it: the app follows the front door.
 *
 * The idea is unchanged — a teacher's appointment book, warm paper rather than
 * clinical grey — but it now uses the front door's own materials.
 *
 * The hard constraints, which the aesthetic serves rather than fights:
 *
 *  - The readers are a 75-year-old teacher and a student. Body text is 20px and
 *    nothing is below 16px, ever.
 *  - One primary action per screen, and it is unmistakable.
 *  - No icon means anything on its own. Every control is labelled in words.
 *  - Colour never carries information alone; there is always text too.
 *
 * ⚠️⚠️ THE PENS ARE DARKER THAN THE FRONT DOOR'S, AND THAT IS NOT A MISTAKE.
 *
 * Measured before they were copied: on `index.html` those colours carry a
 * scribble, a badge and an icon tile — never a sentence — so they never had to
 * pass a text-contrast bar. In here they land on words.
 *
 *      sun  #d4881a  2.74:1   FAILS 4.5:1, and fails the 3:1 large-text bar
 *      sky  #2e7d9a  4.45:1   fails
 *      leaf #44805a  4.48:1   fails
 *
 * Each is darkened until it clears 4.5:1 on the worst ground it can sit on —
 * its own soft tint. Same hue, same saturation, a little less light. D-023
 * commits this project to WCAG AA and the reader is 75. **L-92.**
 *
 * ⭐ DEPTH MUST MEAN SOMETHING — and this is a rule, not a mood.
 *
 * NN/g: "elements that appear raised look like they could be pressed down…
 * elements that appear sunken or hollow look like they could be filled." And
 * the failure mode, from the same page: "the 3D effects are purely aesthetic
 * and don't add any meaningful information for users."
 *
 *      --press   raised  = you can press it          (buttons)
 *      --sunken  hollow  = you can fill it           (inputs)
 *      --shadow  lifted  = a layer above the page    (cards, panels)
 *
 * Anything else gets no shadow.
 *
 * Fraunces carries the headings (a soft serif with SOFT and WONK axes — a hand,
 * not a masthead) over Nunito Sans, which is rounded and still legible at 20px.
 * ⚠️ Both are LOADED here. `home.css` names them and never imported them, so
 * the front door has been rendering in Georgia and the system sans since it
 * shipped — a font the stylesheet has never heard of does not error, it just
 * renders as something else. L-45's shape, and now fixed in both files.
 */

:root {
  /* Paper and ink, not white and grey. The front door's own ground. */
  --paper:      #fff9f3;
  --paper-warm: #fdeee1;
  --paper-card: #ffffff;
  --ink:        #2a1f1a;   /* 15.4:1 on paper */
  --ink-soft:   #6f5d50;   /* 5.9:1 — the front door's #7c6a5d is 4.5, too thin */
  --rule:       #f0d9c2;

  /* Go: start, save, confirm. Leaf, darkened from the front door's #44805a. */
  --go:      #3f7754;
  --go-dark: #33603f;
  --go-tint: #e4f0e8;

  /* The ribbon: what is happening next. Coral, from #c4462a. Never decorative. */
  --mark:      #b94228;
  --mark-tint: #fde5dd;

  /* A subject, a link, a quiet fact. Sky, from #2e7d9a. */
  --sky:      #2b748f;
  --sky-tint: #e2f0f5;

  --warn:      #9a6313;    /* from #d4881a, which was 2.74:1 and unusable */
  --warn-tint: #fdf0d9;
  --stop:      #a32c1e;

  --radius:       16px;    /* the general corner */
  --radius-card:  22px;    /* a card, lifted off the paper */
  --radius-pill:  999px;   /* a button, a chip */

  --shadow: 0 2px 4px rgba(90, 60, 40, .06), 0 22px 44px -26px rgba(90, 60, 40, .42);
  --lift:   0 1px 2px rgba(90, 60, 40, .05), 0 10px 24px -14px rgba(90, 60, 40, .35);
  --press:  0 5px 0 -1px rgba(0, 0, 0, .16);
  --sunken: inset 0 2px 3px rgba(90, 60, 40, .10);

  /*
   * ⚠️⚠️ NO WEBFONT, DELIBERATELY -- and the reason is not performance.
   *
   * `css/home.css` names Fraunces and Nunito Sans and NEVER IMPORTED THEM, so
   * `my1on1.com` has rendered in Georgia and the system sans since the day it
   * shipped. That is what the owner has been looking at, and what he said he
   * liked -- so the fonts he praised ARE these. Loading the "real" ones would
   * have changed the page he approved, in the name of matching it.
   *
   * ⭐ Three things fall out of matching the front door exactly:
   *   - the homepage does not change at all, so nothing he already likes moves
   *   - nothing is downloaded, which matters most on the slow connection a
   *     75-year-old is most likely to be on
   *   - one fewer company touches a request. app.css imported Google Fonts from
   *     day one, so this is not new -- but D-023 and D-050 both treat "very few
   *     companies touch it" as an asset this product can point at, and this
   *     spends slightly less of it.
   *
   * ⚠️ DO NOT "FIX" THIS BY ADDING AN IMPORT. Decided by the owner on
   * 9 September after seeing both rendered side by side. D-077.
   */
  --sans:  -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
  --serif: Georgia, 'Times New Roman', serif;
}

* { box-sizing: border-box; }

/*
 * The hidden attribute must actually hide. This is not a nicety.
 *
 * `hidden` works by setting display:none in the BROWSER's default stylesheet,
 * which any rule of ours beats. So every element we hide from JavaScript that
 * also carries a class setting `display` — .btn, .field, .devicecheck__run —
 * was staying on screen while the code believed it was gone. It shipped: after
 * allowing the camera the page said "Camera and microphone are working" with
 * "Check my camera and microphone" still sitting underneath it, on a real
 * iPhone.
 *
 * Two places had already been patched one at a time (.who__change, .waiting),
 * which should have been the clue that the problem was general. One rule
 * covers every element, including ones nobody has written yet.
 */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 20px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
}

.page { max-width: 720px; margin: 0 auto; padding: 40px 20px 72px; }

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

.masthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 18px;
  margin-bottom: 32px;
  border-bottom: 2px solid var(--ink);
}

.masthead { flex-wrap: wrap; }
.masthead h1 { font: 500 34px/1.1 var(--serif); margin: 0; letter-spacing: -.01em; }

/*
 * ⭐ THE WORDMARK, and it is the whole of D-081.
 *
 * The front door makes the product's name visible with ONE device: `My` in ink
 * and `1on1` in coral (home.css `.word`). Nothing else in the app had it --
 * five of the nine pages had no masthead at all, and a sixth was headed with
 * the name of the screen. A parent following a link from a tutor never saw what
 * this was called.
 *
 * ⚠️ NOT a copy of home.css's rule. That one is 28px Fraunces-or-Georgia on a
 * marketing page; this is the app's own serif at the app's own size. Same
 * gesture, this stylesheet's voice.
 */
.masthead h1 i,
.masthead__word i { font-style: normal; color: var(--mark); }

/*
 * ⚠️ A <p>, NOT AN <h1>. The wordmark is branding; the page's own <h1> is its
 * title, and two <h1>s is a document with two names. The audit found
 * student.html already down to ONE heading element with four headline-shaped
 * paragraphs, so adding a competing <h1> to six more pages would make a real
 * accessibility fault worse while fixing a visual one.
 */
.masthead__word {
  font: 600 27px/1 var(--serif);
  letter-spacing: -.015em;
  margin: 0;
}

/*
 * ⚠️ A HAIRLINE, not the 2px ink slab this used to draw.
 *
 * The old rule underlined every page with `2px solid var(--ink)` -- heavier
 * than anything else on the screen, and the front door uses `1px solid
 * var(--rule)`. The bar should hold the name, not shout it.
 */
.masthead { border-bottom-width: 1px; border-bottom-color: var(--rule); }

.masthead .who {
  display: flex;
  align-items: baseline;
  gap: 12px;
  color: var(--ink-soft);
  font-size: 17px;
}

/*
 * "Change name" — spelled out, rather than making the name itself clickable.
 *
 * A name you can click on is a convention, and a convention is something you
 * either already know or never discover. This reader has to be able to SEE
 * that a thing can be changed. It also has to be findable at all: the name was
 * asked for once on the first run and then had no way back, so a typo could
 * only be undone by editing the database — and that name is what a waiting
 * child's browser watches for before turning their camera on.
 */
/* Padding, not decoration: measured at 21px tall it was under WCAG 2.2's
   24px minimum target size (2.5.8). For hands that are not steady, a control
   you can see but keep missing is barely better than one you cannot find. */
.who__change {
  font: 500 16px/1 var(--sans);
  color: var(--ink);
  background: none;
  border: 0;
  border-bottom: 1.5px solid var(--rule);
  padding: 4px 4px 5px;
  cursor: pointer;
}

.who__change:hover { color: var(--go-dark); border-bottom-color: var(--go); }
.who__change:focus-visible { outline: 3px solid var(--mark); outline-offset: 3px; }

/* Cancel goes under Save rather than beside it. Two buttons shoulder to
   shoulder at this size is a target-accuracy problem for unsteady hands. */
#name-form .btn--quiet { margin-top: 12px; }

.section-title {
  font: 600 14px/1 var(--sans);
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: .12em;
  margin: 40px 0 14px;
}

/* ------------------------------------------------------------------ card */

.card {
  background: var(--paper-card);
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  box-shadow: var(--lift);
  padding: 30px;
  margin-bottom: 20px;
}

.card--hero {
  /* Left-aligned since 12 Sep (D-083): a name with two people beside it is a
     row, not a poster, and the pill and the facts sit on the same left edge.
     The device sentence keeps its own centring (.devicecheck). */
  text-align: left;
  padding: 40px 32px 34px;
  /* The busiest surface in the product sits a layer higher than everything
     else, which is the only reason it gets the heavier shadow. */
  box-shadow: var(--shadow);
  position: relative;
  overflow: hidden;
}
/* A warm bloom behind the card's own content. Decoration, so it carries NO
   shadow and no border -- it is light, not a layer. */
.card--hero::before {
  content: '';
  position: absolute;
  inset: -70% -35% auto auto;
  width: 380px; height: 380px; border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, var(--warn-tint) 0%, transparent 70%);
  pointer-events: none;
}
.card--hero > * { position: relative; }

.card__label {
  display: inline-flex;
  align-items: center;
  font: 700 13px/1 var(--sans);
  color: var(--mark);
  background: var(--mark-tint);
  border-radius: var(--radius-pill);
  padding: 9px 15px;
  text-transform: uppercase;
  letter-spacing: .13em;
  margin: 0 0 16px;
}
/* Happening NOW is a different fact from happening next, and it is told in
   colour AND in the words inside it -- never colour alone. */
.card__label--now { color: var(--go); background: var(--go-tint); }

.card__headline { font: 500 38px/1.1 var(--serif); margin: 0 0 8px; letter-spacing: -.015em; }
.card__when { font-size: 21px; color: var(--ink-soft); margin: 0 0 26px; }

/*
 * THE TWO PEOPLE ON THE CARD (D-083). Her initial in ink, the student's in the
 * pen their Families row uses -- the front door's own motif (index.html
 * .pair), drawn 9 Sep and built 12 Sep. Decoration with one job: the same
 * person is the same colour on both tabs. aria-hidden, because the name beside
 * it already says who. The row is left-aligned; a name with two discs beside
 * it is a row, not a poster.
 */
.card__people { display: flex; align-items: center; gap: 18px; text-align: left; }
.card__people > div { flex: 1; min-width: 0; }
.card__pair { display: inline-flex; align-items: center; flex: none; }
.card__disc {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font: 700 22px/1 var(--sans);
  color: #fff;
  border: 2px solid var(--paper-card);
  background: var(--ink);             /* hers: the constant on every card */
}
.card__disc + .card__disc { margin-left: -17px; }
/* ⚠️ APPLIED FROM A TEMPLATE LITERAL (people.js TONES) -- invisible to
   check_public.mjs, so written by hand (L-64). White on every pen is 5:1 or
   better, measured 12 Sep. */
.card__disc--coral { background: var(--mark); }
.card__disc--sun   { background: var(--warn); }
.card__disc--sky   { background: var(--sky); }
.card__disc--leaf  { background: var(--go); }
@media (max-width: 430px) {
  .card__people { gap: 14px; }
  .card__disc { width: 48px; height: 48px; font-size: 19px; }
  .card__disc + .card__disc { margin-left: -14px; }
}
.card__theirs { font-size: 18px; color: var(--mark); margin: -18px 0 24px; }

/*
 * The step list on the "getting started" card.
 *
 * Ruled rows like the lesson list, and left-aligned inside a centred card:
 * three lines of text centred under one another is a poster, not a list you
 * work down. The status is a WORD as well as a colour, which is the rule
 * everywhere in this app and also what the GOV.UK task list does.
 */
.steps {
  list-style: none;
  text-align: left;
  max-width: 470px;
  margin: 0 auto 26px;
  padding: 0;
  border-top: 1px solid var(--rule);
}

.steps__row {
  display: grid;
  grid-template-columns: 24px 1fr max-content;
  gap: 14px;
  align-items: baseline;
  padding: 13px 2px;
  border-bottom: 1px solid var(--rule);
}

/*
 * A FINISHED step is a way back to the thing it named.
 *
 * GOV.UK made the whole task row a link in its 2023 iteration for exactly this
 * reason -- people need to change an answer they have already given -- and
 * completed tasks keep their link. The owner asked the same question of this
 * card: "should the user be able to go back to the previous step?"
 *
 * ⚠️ ONLY on a Done row, and only where the target is unambiguous. A "To do"
 * step cannot act yet -- "Add your first student" has no family to attach to
 * while she is still on step 2 -- and a control that does nothing reads to a
 * 75-year-old as HER mistake rather than an unfinished feature. L-34, L-43.
 *
 * It is a link and not a button on purpose: D-029 says the card shows exactly
 * one button and that button is the real next step. This must not compete with
 * it.
 *
 * The negative margin gives back the row's own padding, so the target is the
 * whole cell (~50px tall, past the 44px AAA bar) without the Done rows
 * standing taller than the others.
 */
.steps__go {
  display: block;
  width: 100%;
  /*
   * ⚠️ Quieter than the step she is standing on, and louder than a step she
   * cannot reach. A finished step matched the "Now" row for weight in the first
   * build, which put three equally-loud rows on a card whose whole job is to
   * answer "what do I do next" (D-029). It reads at the weight of what it is:
   * finished, and still changeable.
   */
  font: 400 18px/1.35 var(--sans);
  color: var(--ink-soft);
  background: none;
  border: 0;
  padding: 13px 2px;
  margin: -13px 0;
  text-align: left;
  cursor: pointer;
  /*
   * ⚠️ The underline follows the TEXT, not the box. A border-bottom on the
   * full-width button lands exactly on the row's own divider, so the link was
   * invisible -- it rendered perfectly, as a row rule. Seen on the real screen,
   * not in the code: L-45's family.
   */
  text-decoration: underline;
  /* ⚠️ NOT var(--rule): that is the divider cream, and an underline in it is
     invisible on this card -- measured on the real screen. The affordance has
     to be seen by someone who is 75. */
  text-decoration-color: var(--ink-soft);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 5px;
}
.steps__go:hover { color: var(--go); text-decoration-color: var(--go); }
.steps__go:focus-visible { outline: 3px solid var(--mark); outline-offset: 2px; }

.steps__num { font: 600 17px/1.4 var(--sans); color: var(--ink-soft); }
.steps__label { font-size: 18px; line-height: 1.35; color: var(--ink-soft); }
.steps__hint { display: block; font-size: 16px; color: var(--ink-soft); margin-top: 2px; }

/* The step she is on is the only one in full ink. */
.is-now .steps__num   { color: var(--ink); }
.is-now .steps__label { color: var(--ink); font-weight: 600; }

.steps__status {
  font: 600 14px/1.6 var(--sans);
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--ink-soft);
}

.is-done .steps__status { color: var(--go); }
.is-now  .steps__status { color: var(--mark); }

/* On a phone the status word has to give up its column or the label is left
   with about forty pixels to wrap into. */
@media (max-width: 430px) {
  .steps__row { grid-template-columns: 20px 1fr; row-gap: 4px; gap: 10px; }
  .steps__status { grid-column: 2; }
}

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

.btn {
  display: block;
  width: 100%;
  padding: 22px 24px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--go);
  color: #fff9f3;
  font: 700 22px/1.2 var(--sans);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  /* RAISED = pressable. The one shadow on this page that is a signifier
     rather than decoration, and the reason it is allowed. */
  box-shadow: var(--press);
  transition: background .15s ease, transform .1s ease, box-shadow .1s ease;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 6px 0 -1px rgba(0,0,0,.18); }
.btn:active { transform: translateY(2px); box-shadow: 0 2px 0 -1px rgba(0,0,0,.18); }

.btn:hover { background: var(--go-dark); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 3px solid var(--mark); outline-offset: 3px; }

/*
 * WAITING FOR SOMETHING TO ARRIVE.
 *
 * Put `data-waiting="Reading your students…"` on any container that is visible
 * before its contents have been fetched. While it is empty the message shows;
 * the instant anything is put inside it, `:empty` stops matching and the
 * message disappears by itself.
 *
 * ⚠️ WHY CSS AND NOT JAVASCRIPT. There is nothing to remember to call and
 * nothing to remember to clear, so a new region cannot silently get this wrong
 * -- which is exactly how it went wrong. On 29 August the roster was an empty
 * div with an "Add a family" button beneath it, and for the second or two
 * before the data arrived it read as "my students are gone". The owner saw it,
 * and his mother would have telephoned.
 *
 * And the failure mode is the right way round: forget to fill the container and
 * the message STAYS, visibly, instead of leaving a silent blank. L-58.
 */
[data-waiting]:empty::before {
  content: attr(data-waiting);
  display: block;
  padding: 18px 2px;
  font: 400 19px/1.5 var(--sans);
  color: var(--ink-soft);
}

/* A closed door explains itself rather than looking broken. */
/*
 * A disabled button must LOOK disabled.
 *
 * Only [aria-disabled] was styled, and code elsewhere sets the `disabled`
 * PROPERTY -- so on 28 August a green "Next" that had been left disabled by a
 * save was indistinguishable from a working one: opacity 1, full colour,
 * pointer cursor. It was pressed and nothing happened, which reads to a
 * 75-year-old as "I have done something wrong". L-46.
 *
 * Styling both means the next leak of this kind is visible the moment it
 * happens instead of looking like ordinary software.
 */
.btn[aria-disabled='true'],
.btn:disabled {
  background: transparent;
  color: var(--ink-soft);
  border: 2px dashed var(--rule);
  cursor: default;
  pointer-events: none;
}

.btn--quiet {
  background: transparent;
  color: var(--ink);
  border: 2px solid var(--ink);
  font-size: 19px;
  padding: 15px 20px;
}

.btn--quiet:hover { background: var(--ink); color: var(--paper-card); }

/*
 * A small button, for a control INSIDE a row -- not the page's own green slab.
 *
 * ⚠️ MOVED HERE FROM prices.css ON 9 SEPTEMBER. It was defined only in a
 * stylesheet `prices.html` links and `teacher.html` does not, so the first
 * time the calendar used it ("+ Add a lesson") it rendered as a full-width
 * 22px slab. Nothing errored and nothing warned -- L-45, and L-64's documented
 * gap: check_public.mjs cannot scope a class used inside a `.js` module to one
 * page, because any page may import it. **Found by looking at the screen.**
 *
 * 48px tall, so it still clears the 44px AAA target the rest of this app holds.
 */
.btn--sm {
  display: inline-block;
  width: auto;
  font: 700 18px/1 var(--sans);
  padding: 15px 22px;
  border-radius: var(--radius-pill);
}

/* Removing a child is the one irreversible act here, so it does not look like
   the button beside it. It also confirms before acting. */
.btn--danger { color: var(--stop); border-color: #e0c3bd; margin-top: 12px; }
.btn--danger:hover { background: var(--stop); border-color: var(--stop); color: #fff; }

.linkbtn {
  font: 600 17px/1 var(--sans);
  color: var(--ink);
  text-decoration: none;
  border: 1.5px solid var(--rule);
  border-radius: 8px;
  padding: 12px 16px;
  background: var(--paper-card);
  cursor: pointer;
  white-space: nowrap;
}

.linkbtn:hover { background: var(--go-tint); border-color: var(--go); color: var(--go-dark); }

/* ------------------------------------------------- the order, said aloud
 *
 * The list has always been sorted by whoever is next. An order nobody can see
 * is worth nothing: with six children on screen it reads as a jumble, or as a
 * sort by name that has gone wrong.
 *
 * A ruled heading in the serif, the way a diary breaks up a week. The rule
 * runs to the right margin so the eye is carried across into the day's
 * students rather than stopping at the word.
 */
.daygroup {
  display: flex;
  align-items: center;
  gap: 14px;
  font: 500 23px/1.2 var(--serif);
  color: var(--ink);
  margin: 34px 0 14px;
}

.daygroup::after { content: ''; flex: 1; border-top: 1px solid var(--rule); }

/* The first heading sits directly under "All students" and needs no second gap. */
.daygroup:first-child { margin-top: 4px; }

/* ==================================================== the student card ==
 *
 * Rebuilt. It used to be a name, a run-on sentence, and three buttons floating
 * vertically centred against a block of text that grew as lessons were added —
 * so the more a teacher used it, the more broken it looked.
 *
 * Now it reads top to bottom like a diary entry:
 *
 *      Emma
 *      ────────────────────────────────────────
 *      TUE   Every week          4:00 – 4:45 PM   NEXT
 *      SAT   Every 2 weeks      10:00 – 10:45 AM
 *      ────────────────────────────────────────
 *      [ Start Emma's lesson ]    Edit    Parent link
 *
 * Lessons are a ruled table so days, patterns and times line up in columns
 * instead of wrapping into one another. The actions sit on their own row at
 * the bottom — the only place they can be without fighting a block of text
 * whose height nobody can predict.
 */

/*
 * A STUDENT IS A PANEL SUNK INTO THE FAMILY, not a card of its own (D-083).
 * The 9 Sep drawing put the family on the white lifted card and each student
 * on a paper panel inside it; the build did the reverse -- white students on a
 * paper family on a paper page -- and the owner photographed the result as
 * "everything runs together" (12 Sep). Depth means something (D-077): a
 * region inside a card is not a layer, so it gets no shadow.
 */
.student {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 24px 26px;
  margin-bottom: 16px;
}

.student__name { font: 500 27px/1.15 var(--serif); margin: 0 0 16px; letter-spacing: -.01em; }

/* Ruled rows, like a page from the book. */
.student__lessons { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule); }

.student__lesson {
  display: grid;
  grid-template-columns: minmax(54px, max-content) 1fr auto;
  align-items: baseline;
  gap: 14px;
  padding: 13px 0;
  border-bottom: 1px solid var(--rule);
}

.student__day {
  font: 700 15px/1 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.student__pattern { font-size: 18px; color: var(--ink-soft); }

.student__time {
  font: 600 19px/1 var(--sans);
  font-variant-numeric: tabular-nums;   /* times line up down the column */
  color: var(--ink);
  white-space: nowrap;
}

/* The ribbon. Marks the one lesson actually coming next — the question
   "Start" used to leave unanswered. */
.student__lesson--next .student__day,
.student__lesson--next .student__time { color: var(--mark); }

.student__lesson--next .student__pattern::after {
  content: 'NEXT';
  display: inline-block;
  margin-left: 10px;
  padding: 3px 8px;
  border-radius: 4px;
  background: var(--mark-tint);
  color: var(--mark);
  font: 700 12px/1 var(--sans);
  letter-spacing: .1em;
  vertical-align: 2px;
}

/*
 * On a phone the three columns have nowhere to go: "Every week" breaks across
 * two lines and the NEXT ribbon drops underneath on its own, so the row stops
 * reading as a row. Two lines by design beats three by accident — the day and
 * the time keep the top line, which is what she is scanning for, and the
 * pattern sits under them.
 */
@media (max-width: 430px) {
  .student__lesson { grid-template-columns: minmax(54px, max-content) 1fr; row-gap: 3px; }
  .student__day     { grid-column: 1; grid-row: 1; }
  .student__time    { grid-column: 2; grid-row: 1; text-align: right; }
  .student__pattern { grid-column: 1 / -1; grid-row: 2; }
}

.student__none {
  font-size: 18px;
  color: var(--ink-soft);
  padding: 14px 0;
  border-bottom: 1px solid var(--rule);
}

/*
 * The four buttons under a child.
 *
 * `flex-wrap: wrap` is not decoration. Without it this row is a fixed 471px --
 * `.linkbtn` sets `white-space: nowrap`, so not one of the four can wrap or
 * shrink -- and the card is 235px wide on a phone. "Cancel a lesson" was sliced
 * in half at the card's edge and "Parent link" was off the screen entirely: a
 * button on every child that a phone could not reach. Measured, 23 August.
 *
 * It overflowed before families existed (471 into 279) and the family card's
 * padding made it worse by 44px. Both are fixed here.
 *
 * This is a standard, not a preference: WCAG 2.1 Reflow (1.4.10, AA) asks for
 * content to reflow to 320px without scrolling in two directions, and D-023
 * commits this project to AA.
 */
.student__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 20px;
}

/* The primary action takes the room it deserves; the rest stay out of its way. */
.student__actions .btn { flex: 1; padding: 17px 18px; font-size: 19px; }

/*
 * On a phone, a predictable grid rather than a ragged wrap.
 *
 * "Start Emma's lesson" gets a line of its own; the other three share the two
 * columns below it. `white-space: normal` is what lets "Cancel a lesson" fold
 * onto two lines instead of demanding 159px in a 112px column.
 *
 * The label is NOT shortened to fit. teacher.html says why: Edit changes every
 * Tuesday and this changes one Tuesday, and confusing the two is how a teacher
 * deletes a term by accident. A layout problem is not worth trading for that.
 *
 * 560px, not the 430px used elsewhere in this file: the row needs 471px, and a
 * card stops being that wide at about 607px of viewport -- so a 430px
 * breakpoint would have left every tablet in portrait still broken.
 */
@media (max-width: 560px) {
  /*
   * `stretch`, because the base rule centres them and a wrapped label makes
   * "Cancel a lesson" 61px against Edit's 44px. Two buttons side by side at
   * different heights read as two unrelated things rather than as a row.
   */
  .student__actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: stretch;
  }
  .student__actions .btn { grid-column: 1 / -1; }
  .student__actions .linkbtn { white-space: normal; }
}

.student__hint { font-size: 16px; color: var(--ink-soft); margin: 12px 0 0; }

/*
 * WHEN, under a student: the ruled rows (.student__lesson, above) -- day,
 * pattern, time, the one coming next ribboned. They were the card's since
 * August, became two sentences of prose on 11 September (D-082), and came
 * back on 12 September (D-083): a timetable reads as a timetable.
 */
/* Their clock, when it is not hers, under the rows. Coral because it is the
   ribbon's job: the fact that will catch her out. Never colour alone -- it is
   a sentence. */
.student__theirs { font-size: 16px; color: var(--mark); margin: 10px 0 0; }
/* A signpost to the other tab carries an arrow, so it reads as "go", not "do". */
.linkbtn--go::after { content: ' \203A'; font-weight: 800; }
/*
 * AND IT LOOKS LIKE A SIGNPOST (D-083). "See Ben's lessons ›" goes somewhere;
 * the two buttons beside it do something here. So it is a leaf-coloured link at
 * the right, not a third bordered button. Still 44px tall (L-45); on a phone it
 * takes the whole second row of the grid.
 */
.student__actions .linkbtn--go {
  border-color: transparent;
  background: none;
  color: var(--go);
  text-decoration: underline;
  text-underline-offset: 4px;
  font-weight: 700;
  padding: 12px 4px;
  margin-left: auto;
  min-height: 44px;
}
.student__actions .linkbtn--go:hover { background: none; border-color: transparent; color: var(--go-dark); }
@media (max-width: 560px) {
  .student__actions .linkbtn--go { grid-column: 1 / -1; margin-left: 0; text-align: left; }
}

/* "Ben already has: ..." on the lesson form -- the basket, as a sentence. */
.lesson-has { font-size: 17px; color: var(--ink-soft); margin: 0 0 20px; line-height: 1.5; }
.lesson-has strong { color: var(--ink); }
.lesson-theirs { margin: -8px 0 16px; }
/* The chip's "Change who" -- a real control inside a chip that is not one. */
.familychip__change {
  font: 600 15px/1 var(--sans);
  color: var(--go-dark);
  background: none;
  border: 0;
  padding: 6px 4px;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
/* "Stop them from a date, or remove them altogether?" */
.removeask {
  margin-top: 14px;
  padding: 16px 18px;
  background: var(--paper);
  border: 1px solid #e0c3bd;
  border-radius: var(--radius);
}
.removeask__q { font: 600 17px/1.35 var(--sans); margin: 0 0 12px; }
.removeask__sub { font-size: 15px; color: var(--ink-soft); margin: 12px 0 0; line-height: 1.5; }

/* ------------------------------------------------------- the parent link */

.student__panel { margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--rule); }
.student__panel-help { font-size: 17px; color: var(--ink-soft); margin: 0 0 12px; }

.student__url {
  width: 100%;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 15px;
  color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--rule);
  border-radius: 8px;
  padding: 13px 15px;
}

.student__panel-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }

/*
 * The email written out in the three pieces an email is made of.
 *
 * Laid out as To / Subject / Message in that order, because that is the order
 * she will fill them in, and because the shape of an email is a thing she
 * already recognises — the fallback should look like the job it is standing in
 * for. Labels sit above their boxes, as everywhere else in this app: a label
 * inside a box disappears the moment you touch it.
 */
/* ------------------------------------------------- calling off one lesson
 *
 * A cancelled lesson stays on the list, struck through, with a way back. It
 * would be less code to simply remove the row — and then a teacher who
 * cancelled the wrong Tuesday would have no way of telling that she had, which
 * is the same as the app lying to her.
 */
.cancelrow {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 18px;
}

.cancelrow span { flex: 1; min-width: 0; }
.cancelrow--off span { color: var(--ink-soft); text-decoration: line-through; }

@media (max-width: 430px) {
  .cancelrow { flex-wrap: wrap; }
  .cancelrow span { flex: 1 0 100%; }
}

.draft__note { font-size: 16px; color: var(--ink-soft); margin: 18px 0 12px; }

/*
 * The answer to "Send it for me", said out loud.
 *
 * A bounce must not wear the colour of a success -- but colour never carries
 * the meaning ALONE here: the sentence names the addresses either way, which is
 * this file's standing rule and what makes it readable to someone who cannot
 * tell green from red.
 */
.draft__note--ok {
  color: var(--go);
  background: var(--go-tint);
  border-left: 4px solid var(--go);
  padding: 12px 14px;
  border-radius: 0 8px 8px 0;
}
/*
 * The escape below a draft: "or send it yourself". Deliberately QUIET.
 * It is the way out for the day sending fails, not a second way to do the
 * ordinary thing -- D-029 allows exactly one button that means "do it".
 */
.draft__escape { margin: 14px 0 0; font-size: 16px; }
.draft__escape a {
  color: var(--ink-soft);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  cursor: pointer;
}
.draft__escape a:hover { color: var(--ink); text-decoration-color: var(--ink-soft); }
.draft__row--escape { display: block; }

.draft__note--bad {
  color: var(--stop);
  background: var(--mark-tint);
  border-left: 4px solid var(--stop);
  padding: 12px 14px;
  border-radius: 0 8px 8px 0;
  font-weight: 500;
}

.draft__row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px;
  align-items: start;
  margin-bottom: 14px;
}

.draft__label {
  grid-column: 1 / -1;
  font: 600 15px/1.3 var(--sans);
  color: var(--ink-soft);
}

.draft__field,
.draft__text {
  display: block;
  width: 100%;
  font: 400 17px/1.45 var(--sans);
  color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--rule);
  border-radius: 8px;
  padding: 12px 14px;
}

.draft__text { font-size: 16px; line-height: 1.5; resize: vertical; }

.draft__field:focus-visible,
.draft__text:focus-visible { outline: 3px solid var(--go); outline-offset: 1px; }

/* Nothing to copy is not the same as a button that does nothing when pressed. */
.linkbtn[aria-disabled='true'],
.linkbtn:disabled {
  color: var(--ink-soft);
  border-style: dashed;
  cursor: default;
  pointer-events: none;
}

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

/*
 * Labels above fields, never inside them as placeholder text: a placeholder
 * vanishes the moment you start typing, which is exactly when someone unsure
 * of themselves wants to re-read what was asked.
 */
.field { display: block; text-align: left; margin-bottom: 20px; }

.field__label { display: block; font: 600 16px/1.3 var(--sans); color: var(--ink-soft); margin-bottom: 7px; }
.field__label em { font-style: normal; opacity: .75; }

.field__input {
  width: 100%;
  font: 400 20px/1.4 var(--sans);
  color: var(--ink);
  background: var(--paper-card);
  border: 2px solid var(--rule);
  border-radius: var(--radius);
  padding: 14px 15px;
  /* SUNKEN = fillable. A box you type into is pressed INTO the paper. */
  box-shadow: var(--sunken);
}

.field__input:focus-visible { outline: 3px solid var(--go); outline-offset: 1px; border-color: var(--go); }

.field__hint { display: block; font-size: 16px; color: var(--ink-soft); margin-top: 7px; }

.field-row { display: flex; gap: 12px; }
.field-row .field { flex: 1; min-width: 0; }

@media (max-width: 560px) { .field-row { display: block; } }

/* Toggle buttons, not checkboxes: bigger targets, and the state is legible
   from across a room rather than needing a squint at a tick. */
.daypick { display: flex; gap: 7px; flex-wrap: wrap; }

.daypick__day {
  flex: 1;
  min-width: 60px;
  font: 500 16px/1 var(--sans);
  padding: 15px 4px;
  border: 1.5px solid var(--rule);
  border-radius: 8px;
  background: var(--paper-card);
  color: var(--ink-soft);
  cursor: pointer;
}

.daypick__day:hover { border-color: var(--go); color: var(--go-dark); }

.daypick__day[aria-pressed='true'] {
  background: var(--go);
  border-color: var(--go);
  color: #fff;
  font-weight: 700;
}

.daypick__day:focus-visible { outline: 3px solid var(--mark); outline-offset: 2px; }

/* The pattern read back as a sentence, so she can check her own work. */
.summary {
  font-size: 18px;
  background: var(--go-tint);
  border-left: 3px solid var(--go);
  border-radius: 0 8px 8px 0;
  padding: 14px 16px;
  margin: 0 0 16px;
}

.subcard {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 22px 22px 6px;
  margin-bottom: 20px;
}

.lessonrow {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--paper-card);
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: 15px 17px;
  margin-bottom: 9px;
  font-size: 18px;
}

.lessonrow span { flex: 1; }

/* A clash is a warning, not a refusal: back-to-back lessons are normal and
   she may have a reason. It states the problem and lets her decide. */
.clash {
  font-size: 17px;
  color: var(--warn);
  background: #fdf7ec;
  border-left: 3px solid var(--warn);
  border-radius: 0 8px 8px 0;
  padding: 13px 15px;
  margin: 0 0 16px;
}

.or { text-align: center; color: var(--ink-soft); font-size: 17px; margin: 20px 0 14px; }

/* ------------------------------------------------- "go and check your email"
 *
 * The one screen where the next thing to do is not on the screen at all.
 *
 * This was a 17px grey footnote under two buttons, in the same style as every
 * hint on the page, and it was easy to read straight past — leaving someone
 * looking at a page that appears to have done nothing. The obvious response to
 * that is to press the button again, which is the one genuinely harmful move
 * here: the email sender allows two an hour, and the second press is the one
 * that fails without saying so.
 *
 * So it takes the whole card, in the action colour, at reading size. Not
 * because it is a success message — because it is an instruction, and it is
 * the only one left.
 */
.sent {
  background: var(--go-tint);
  border: 1.5px solid var(--go);
  border-radius: var(--radius);
  padding: 30px 26px 26px;
  text-align: center;
}

.sent:focus { outline: none; }        /* focused only so a screen reader lands here */

.sent__title { font: 500 31px/1.15 var(--serif); color: var(--go-dark); margin: 0 0 16px; }

.sent__body { font-size: 20px; line-height: 1.5; margin: 0 0 16px; }

/* Her own address, shown back to her: the commonest failure is a typo, and
   she cannot spot one she is not shown. */
.sent__address { display: block; margin-top: 8px; font-weight: 700; word-break: break-word; }

.sent__aside { font-size: 17px; line-height: 1.5; color: var(--ink-soft); margin: 0 0 22px; }

.signin-note { font-size: 17px; color: var(--ink-soft); margin: 16px 0 0; min-height: 24px; }

/*
 * And a DONE thing must look done.
 *
 * `.is-error` was written with the comment below and its opposite was never
 * written -- so a success came out in the same grey as an ordinary hint, in the
 * same slot the red error had just been in. On 29 August the owner sent an
 * invitation that worked perfectly and reported it as "it just comes back to
 * the same page with the email cleared". The count went 5 to 4, the message was
 * there, and none of it looked like anything had happened. L-59.
 */
.signin-note.is-ok {
  color: var(--go);
  background: var(--go-tint);
  border-left: 3px solid var(--go);
  border-radius: 0 8px 8px 0;
  padding: 13px 15px;
}

/* A failed save must look like a failure, not like a hint. */
.signin-note.is-error {
  color: var(--stop);
  background: #fcf1ef;
  border-left: 3px solid var(--stop);
  border-radius: 0 8px 8px 0;
  padding: 13px 15px;
}

/* --------------------------------------------------------------- notices */

.notice {
  background: #fdf7ec;
  border: 1px solid #ecdcbb;
  border-radius: var(--radius);
  padding: 18px 22px;
  font-size: 17px;
  margin-bottom: 20px;
}

.notice--error { background: #fcf1ef; border-color: #e0c3bd; }

.notice--error code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 15px;
  color: var(--stop);
  word-break: break-word;
}

.notice .student__url { margin: 10px 0; }

/* ------------------------------------------------------------ green tick */

.devicecheck {
  display: flex;
  align-items: center;
  gap: 11px;
  justify-content: center;
  margin-top: 22px;
  font-size: 18px;
  color: var(--ink-soft);
  min-height: 28px;
}

.devicecheck__dot { width: 11px; height: 11px; border-radius: 50%; background: #cdc5b8; flex: none; }

.devicecheck.is-ok  { color: var(--go); }
.devicecheck.is-ok  .devicecheck__dot { background: var(--go); }
.devicecheck.is-bad { color: var(--stop); text-align: left; }
.devicecheck.is-bad .devicecheck__dot { background: var(--stop); }

/* Nothing is wrong; it simply has not been asked yet. On an iPad that is the
   ordinary case, and it must not wear the colour of a fault. */
.devicecheck.is-info { color: var(--ink-soft); text-align: left; }
.devicecheck.is-info .devicecheck__dot { background: #cdc5b8; }

/*
 * The button that asks the question, on devices that will not answer it
 * unasked. `.linkbtn` refuses to wrap, which is right for "Copy" and wrong
 * here: this label is a sentence, and on a phone a sentence that cannot wrap
 * runs off the edge of the screen.
 */
.devicecheck__run {
  display: inline-block;
  margin-top: 14px;
  max-width: 100%;
  white-space: normal;
  line-height: 1.35;
}

/* An error runs to several lines and a centred dot floats oddly beside them. */
.devicecheck.is-bad,
.devicecheck.is-info { align-items: flex-start; }
.devicecheck.is-bad .devicecheck__dot,
.devicecheck.is-info .devicecheck__dot { margin-top: 7px; }

/* ------------------------------------------------------------------ help */

.help { margin-top: 44px; text-align: center; font-size: 17px; color: var(--ink-soft); }

/* Same 24px floor as everything else pressable. These measured 20px — small
   for a footer link anywhere, and "Something not working?" is the one control
   on the page that only gets pressed by someone already struggling. */
.help a { color: var(--ink-soft); display: inline-block; padding: 4px 3px; }

/* ----------------------------------------------------------- lesson room */

/*
 * The room is the one full-bleed screen, and the one that goes dark: the
 * paper recedes so the child and the whiteboard are all there is.
 */
/*
 * 100dvh, with 100vh left underneath as the fallback for older browsers.
 *
 * On an iPad or iPhone, 100vh is the height the page WOULD have if Safari's
 * toolbars were hidden — so a full-height layout runs underneath them and the
 * bottom of the whiteboard is cut off, on a page that cannot scroll to reveal
 * it. dvh is the height actually available right now.
 */
body.room { height: 100vh; height: 100dvh; overflow: hidden; background: #14120f; }

/* One number, used by the bar and by the grid that fills what is left. They
   were two separate 54s, which is precisely how a layout drifts apart. */
body.room { --bar-h: 64px; }

.room__bar {
  height: var(--bar-h);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 18px;
  background: #14120f;
  color: #f5f0e8;
  border-bottom: 1px solid #2c2822;
}

/* Truncates rather than shoving the way out off the edge of a phone. */
.room__bar strong {
  font: 500 20px/1 var(--serif);
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.room__warning { margin-left: auto; color: #f0b26b; font-size: 17px; }
.room__warning + .room__exit { margin-left: 16px; }

/* Only while the board is the public Excalidraw, which cannot share a drawing.
   Amber rather than red: nothing is broken, it is simply not finished, and the
   red is spoken for by the way out. */
.room__notshared {
  margin: 0 0 0 auto;
  padding: 8px 14px;
  border: 1.5px solid #7a5a2a;
  border-radius: 8px;
  background: #2a2013;
  color: #f0b26b;
  font-size: 16px;
  line-height: 1.3;
  max-width: 42ch;
}

.room__notshared + .room__exit { margin-left: 16px; }

@media (max-width: 760px) { .room__notshared { display: none; } }

/*
 * The way out.
 *
 * It was #2c2822 on a #14120f bar — a contrast of 1.28:1, where WCAG asks 3:1
 * for the edge of a control. It was not that the label was unclear; it was
 * that the button had no visible edge at all, so it read as a word rather than
 * as a thing you press.
 *
 * Deliberately NOT red, though Zoom and Meet both use red here. Two reasons.
 * A child sits in this same bar, and a red button is a magnet for a seven-year
 * old. And red means one thing everywhere else in this app — irreversible —
 * whereas leaving a lesson is the least permanent thing you can do here: the
 * green button on their own page puts them straight back. What was actually
 * wrong was the contrast, and the fix for invisible is visible, not alarming.
 *
 * 48px tall, comfortably over Apple's 44pt minimum, because this is now a
 * button pressed with a finger on an iPad rather than a mouse on a laptop.
 */
.room__exit {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  /* A brighter red than --stop, which is tuned for dark text on paper. On the
     near-black bar this reaches 3.9:1 against the background and 4.8:1 for the
     label, where the muted one fell under both. */
  background: #d13b28;
  border: 1.5px solid #e8705c;
  border-radius: 8px;
  padding: 15px 22px;
  font: 600 17px/1 var(--sans);
  color: #fff;
  text-decoration: none;
  cursor: pointer;
}

/*
 * Never two lines, never squeezed.
 *
 * On an iPhone in portrait this wrapped onto two lines and became the largest
 * thing on the child's screen -- and it is the one control that ends the
 * lesson. Seen on a real phone, 19 August.
 */
.room__exit { white-space: nowrap; flex: none; }

.room__exit:hover { background: #b8301f; border-color: #fff; }

/*
 * Smaller on a phone, but not smaller than a thumb.
 *
 * There was no phone rule at all: 15px padding and 17px text at every size,
 * which is proportionate on a laptop and overbearing on a 390px screen. This
 * keeps the icon AND the words -- a child should not have to know that a
 * symbol means "leave" -- while giving the lesson its screen back.
 *
 * 13 + 15 + 13 + 3px of border = 44px tall, which is Apple's minimum for
 * something you tap. Do not trim it further; below that it stops being
 * reliably pressable by the person most likely to need it.
 */
/* Icon AND words, never the icon alone. Every meeting app puts a symbol here,
   and it helps people who already know the convention find it faster — but the
   word is what makes it findable by someone who does not. */
.room__exit-icon { width: 20px; height: 20px; flex: none; }

@media (max-width: 560px) {
  .room__bar { gap: 10px; padding: 0 12px; }
  .room__bar strong { font-size: 17px; }
  .room__exit { padding: 13px 14px; font-size: 15px; gap: 7px; }
  /* ⚠️ Keep this BELOW the plain .room__exit-icon rule, or it is undone (L-103). */
  .room__exit-icon { width: 18px; height: 18px; }
}
.room__exit:focus-visible { outline: 3px solid #f0b26b; outline-offset: 2px; }

.room__grid {
  display: grid;
  /* Never narrower than a usable face, never wider than one needs to be — the
     rest is whiteboard, which is where the teaching actually happens. */
  grid-template-columns: clamp(200px, 30%, 420px) 1fr;
  height: calc(100vh - var(--bar-h));
  height: calc(100dvh - var(--bar-h));
}

.room__video { background: #14120f; min-width: 0; position: relative; }
.room__board { background: #fff; border-left: 1px solid #2c2822; min-width: 0; }
.room__board iframe { width: 100%; height: 100%; border: 0; display: block; }

/*
 * Which way up the device is, not how wide it is.
 *
 * This was `max-width: 900px`, which is a laptop assumption: it reads a phone
 * turned sideways (844 x 390) as "small, so stack them" and hands the child a
 * whiteboard 203 pixels tall — measured, not guessed. Turning a phone sideways
 * is exactly what you do when you want more room to draw, so the app was
 * punishing the instinct that was right.
 *
 * Orientation asks the question that actually matters: is there more width
 * than height? Then put them side by side, whatever the device is.
 */
@media (orientation: portrait) {
  .room__grid { grid-template-columns: 1fr; grid-template-rows: 34vh 1fr; }
  .room__board { border-left: 0; border-top: 1px solid #2c2822; }
}

/*
 * While the OTHER person is sharing their screen.
 *
 * The video column is deliberately narrow -- never more than 420px -- because
 * the whiteboard is where teaching happens. That is right until someone shares
 * a screen into it, at which point a laptop's worth of detail is squeezed into
 * a column narrower than a phone and nobody can read it. Seen on 19 August: a
 * shared calendar, legible to no one.
 *
 * So the panes swap emphasis: the shared screen takes the room, the whiteboard
 * stays visible but small. It reverts by itself when the sharing stops.
 *
 * Only on the RECEIVING side. The person sharing is looking at the real thing
 * on their own screen already, and shrinking their whiteboard to show them a
 * picture of what they are doing would be absurd.
 */
.room--sharing .room__grid {
  grid-template-columns: 1fr clamp(160px, 25%, 320px);
}
.room--sharing .room__video { order: 1; }
.room--sharing .room__board {
  order: 2;
  border-left: 1px solid #2c2822;
}

@media (orientation: portrait) {
  .room--sharing .room__grid {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr clamp(120px, 25vh, 240px);
  }
  .room--sharing .room__board { border-left: 0; border-top: 1px solid #2c2822; }
}

/* Whiteboard-only mode — what the iPad opens, so the Pencil gets the whole
   screen and the teacher's face stays on the laptop. */
.room--board-only .room__grid { grid-template-columns: 1fr; grid-template-rows: 1fr; }
.room--board-only .room__video { display: none; }

/* Sits over the video so a waiting child sees an explanation, not a black
   rectangle with their own face in it. */
.waiting {
  position: absolute;
  inset: 0;
  z-index: 10;
  background: #14120f;
  color: #f5f0e8;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 28px;
  gap: 10px;
}

.waiting__title { font: 500 26px/1.2 var(--serif); margin: 0; }
.waiting__body { font-size: 18px; line-height: 1.5; color: #b8ada0; margin: 0; max-width: 34ch; }
.waiting__body--quiet { color: #7d746a; margin-top: 6px; }

/* ---------------------------------------------------------------- family */

/*
 * The family is the account and the child belongs to it, so the child's card
 * sits INSIDE the family's — DECISIONS.md D-024.
 *
 * The container is paper, the cards inside it are card-paper. That one step of
 * contrast is what makes the nesting readable without a second border weight,
 * a tint, or anything else decorative.
 */
/*
 * THE FAMILY IS THE CARD (D-083). White, lifted, the card corner -- the same
 * object as the action card above it -- and the students sit inside it as
 * paper panels. The 9 Sep drawing (design/refresh p5) said so; the build gave
 * the family the page's own colour, so it had no edge and the tab read as one
 * column. Measured 11 Sep: .family and body were the same #fff9f3.
 */
.family {
  background: var(--paper-card);
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  box-shadow: var(--lift);
  padding: 22px 22px 18px;
  margin-bottom: 22px;
}
/*
 * On a phone the family and the student each give back 12px a side.
 * ⚠️ This rule used to sit 650 lines ABOVE the .family block, which overrode
 * it -- same selector, same weight, later wins -- so phones got 22px for three
 * days (L-103). checkMediaOrder() in tools/check_public.mjs now refuses a
 * deploy if a media rule is undone by a later plain rule. Keep this BELOW both
 * .family and .student.
 */
@media (max-width: 430px) {
  .family  { padding: 18px 14px 14px; }
  .student { padding: 18px 14px 14px; }
}

/* The name and how many, on one line; then two labelled groups. Proximity
   says what the words say (NN/g; Families PDF p4). */
.family__top { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.family__name { font: 500 26px/1.2 var(--serif); margin: 0; letter-spacing: -.01em; }
.family__count { font: 600 15px/1 var(--sans); color: var(--ink-soft); }
.family__eyebrow {
  font: 800 12px/1 var(--sans);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 14px 0 8px;
}
.family__rule { border: 0; border-top: 1px solid var(--rule); margin: 14px 0 0; }

.family__grownups { font-size: 17px; color: var(--ink-soft); margin: 0 0 6px; }

/* An enquiry is a family too, so an empty one must not read as a mistake. */
.family__empty {
  margin: 0 0 14px;
  padding: 14px 16px;
  background: var(--paper-card);
  border: 1px dashed #cfc7b8;
  border-radius: 8px;
  font-size: 17px;
  color: var(--ink-soft);
}

.family__actions { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 4px; }

/*
 * "to the Reynolds family" on the child form. Not a control: there is nothing
 * to choose, which is the entire point of the design.
 */
.familychip {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 12px 16px;
  background: var(--go-tint);
  border-radius: 999px;
  margin: 0 0 26px;
  font: 500 17px/1 var(--sans);
  color: var(--go);
}

/* One parent or guardian, on the family form or the child form. */
.grownup {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 16px;
}

.grownup__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 16px;
}

.grownup__foot .relpick__opt { flex: none; }

/*
 * Relationship: chips, not a dropdown.
 *
 * The same control as the day picker, deliberately — she has used it before.
 * GOV.UK's design system treats a select as a last resort in a public-facing
 * service because their research found people struggle with them. 49px tall
 * clears Apple's 44pt minimum for a finger.
 */
/*
 * A grid, not a wrapping flex row. With `flex: 1` the sixth chip drops onto a
 * line of its own and stretches the whole width, which stops reading as a chip
 * and starts reading as a banner — seen on the real page at 820px wide.
 */
.relpick {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
  gap: 7px;
  margin-bottom: 10px;
}

.relpick__opt {
  min-width: 0;
  font: 500 16px/1 var(--sans);
  padding: 15px 6px;
  border: 1.5px solid var(--rule);
  border-radius: 8px;
  background: var(--paper-card);
  color: var(--ink-soft);
  cursor: pointer;
}

.relpick__opt:hover { border-color: var(--go); color: var(--go-dark); }

.relpick__opt[aria-pressed='true'] {
  background: var(--go);
  border-color: var(--go);
  color: #fff;
  font-weight: 700;
}

.relpick__opt:focus-visible { outline: 3px solid var(--mark); outline-offset: 2px; }

/* --------------------------------------------- family card, second pass */

/*
 * The grown-ups line. It used to repeat the family's own name back at itself —
 * "DARA's family" with "DARA (main)" under it — so it now carries what she
 * needs instead: the relationship, and the two ways to make contact.
 */
.grownupline {
  font-size: 17px;
  color: var(--ink-soft);
  margin: 0 0 4px;
  line-height: 1.5;
}


.grownupline__who { font-weight: 600; color: var(--ink); }

/*
 * Underlined, not merely coloured. Colour alone is not a signal everyone can
 * see, and this is a link a 75-year-old needs to find at speed.
 */
.grownupline__link {
  color: var(--go);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.grownupline__link:hover { color: var(--go-dark); }

/* A tap target that clears 44pt without pushing the lines apart. */
.grownupline__link { display: inline-block; padding: 4px 0; }

.grownupline__sep { color: var(--rule); }

/*
 * On a phone the line wraps, and the "·" before the email is left stranded at
 * the end of the line above it — reading as a typo rather than as a separator.
 * Narrow screens stack the ways of reaching someone instead, and the dots go.
 */
@media (max-width: 560px) {
  .grownupline { display: flex; flex-wrap: wrap; column-gap: 12px; }
  .grownupline__sep { display: none; }
}

/*
 * "optional" said in words, next to the label.
 *
 * Marking the optional ones is the right way round here: every other field on
 * this form is needed, so marking those instead would be noise on all of them
 * to explain one.
 */
.field__optional {
  font: 400 15px/1 var(--sans);
  color: var(--ink-soft);
  text-transform: none;
  letter-spacing: 0;
  font-style: italic;
  margin-left: 6px;
}

/* ------------------------------------------------------------ install hint */

/*
 * "Save this to your home screen", shown on an iPhone or iPad only — the one
 * platform where the option is genuinely hidden and the steps are stable.
 * Quiet by design: it is an offer, not an instruction, and the page works
 * exactly as well ignored.
 */
.installhint {
  font-size: 17px;
  line-height: 1.5;
  color: var(--ink-soft);
  background: var(--go-tint);
  border-radius: var(--radius);
  padding: 16px 18px;
  margin: 22px 0 0;
}

.installhint strong { color: var(--ink); }

/* The Install button, when the browser has offered. */
.installhint .linkbtn { margin-top: 12px; display: inline-block; }

/*
 * Footer separators, drawn rather than typed.
 *
 * They were `&nbsp;·&nbsp;` in the markup, between three links. The middle one
 * hides itself once the app is installed — and left both its dots behind, so
 * the line read "Something not working? ·  · Sign out" with a hole in it.
 *
 * A separator belongs to the item it precedes. Written this way it is part of
 * that item, so it vanishes with it, and the remaining links still get exactly
 * one dot between them however many are hidden.
 */
.help a + a::before {
  content: "·";
  display: inline-block;      /* keeps the dot out of the link's underline */
  /*
   * Uneven on purpose. The newline between the links in the markup collapses to
   * about 4px of space, and it lands on the LEFT of the dot only — so a
   * symmetrical margin renders asymmetrically. 2px + that space ≈ 6px, which
   * matches the 6px on the right.
   */
  margin: 0 6px 0 2px;
  color: var(--ink-soft);
}

/* ------------------------------------------------- where a lesson happens */
.wherepick { display: grid; gap: 10px; }
.wherepick__opt {
  display: flex; gap: 12px; align-items: flex-start;
  border: 1.5px solid var(--rule); border-radius: 10px;
  padding: 14px 16px; background: var(--paper-card); cursor: pointer;
  font: 600 18px/1.3 var(--sans);
}
.wherepick__opt:focus-within { outline: 3px solid var(--mark); outline-offset: 3px; }
.wherepick__opt:has(input:checked) { border-color: var(--go); background: var(--go-tint); }
.wherepick__opt input { width: 24px; height: 24px; margin: 0; flex: none; accent-color: #1d5c3f; }
.wherepick__sub { display: block; font: 400 16px/1.4 var(--sans); color: var(--ink-soft); margin-top: 3px; }

/* ==========================================================================
   THE CALENDAR                                            D-075, D-076

   Two things here, and the first is the smaller one:

   .tabs   the strip that replaces the "Families" heading. The action card
           stays ABOVE it, always -- NN/g: anything critical in a non-default
           tab may simply be ignored, and that is the whole risk tabs carry.

   .cal__  the calendar itself: month, week, day, and the list a narrow screen
           gets instead of a grid.

   ⚠️ The colour variants below (--blue, --green, ...) are applied from a
   TEMPLATE LITERAL in calendar-view.js, so check_public.mjs cannot see them.
   It sees every other class in this block. If a colour is added there it must
   be added here BY HAND -- L-64, and this is the shape the check still cannot
   read.
   ========================================================================== */

/* ------------------------------------------------------------------ tabs */

.tabs {
  display: flex;
  gap: 2px;
  align-items: stretch;
  border-bottom: 2px solid var(--rule);
  margin: 40px 0 22px;
  overflow-x: auto;                 /* never wraps to a second row */
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }

.tab {
  font: 600 19px/1 var(--sans);
  color: var(--ink-soft);
  background: none;
  border: 0;
  border-bottom: 3px solid transparent;
  padding: 16px 20px;               /* 51px tall: past the 44px AAA bar */
  margin-bottom: -2px;
  white-space: nowrap;
  cursor: pointer;
}
/* TWO indicators, never colour alone: the weight AND the rule beneath. */
.tab--on { color: var(--ink); font-weight: 800; border-bottom-color: var(--mark); }
.tab:focus-visible { outline: 3px solid var(--go); outline-offset: -3px; }

/* ---------------------------------------------------------- facts and chips */

/*
 * The row of chips under the headline on the action card, and the subject tag
 * on a student. One class for both, so a subject looks the same in both places.
 *
 * ⚠️ Every chip carries WORDS. The dot is a second indicator beside the name,
 * never the only one -- colour carries no information on its own anywhere in
 * this app.
 */
.facts {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: flex-start;
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
}
.facts__item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: 600 16px/1.3 var(--sans);
  color: var(--ink-soft);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  padding: 9px 15px;
}
.facts__dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }

/*
 * ⚠️ The modifiers are applied from a TEMPLATE LITERAL in teacher.html
 * (`facts__dot--${f.tone}`), so check_public.mjs cannot see them -- L-64, the
 * shape the check still cannot read. If a tone is added there it must be added
 * here BY HAND. The same warning already sits above the calendar's colours.
 */
.facts__dot--subject { background: var(--sky); }
.facts__dot--where   { background: var(--warn); }
.facts__dot--zone    { background: var(--mark); }

/* The subject beside a student's name, on the roster. */
.student__top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
.student__top .student__name { margin: 0; }
/*
 * The initial disc (D-083): a soft tint with the pen ONE STEP DARKER as the
 * letter. Measured with the real tokens the pens themselves sit at 4.47-4.51:1
 * on their tints and sun fails; these sit at 5.2-6.2:1. Not a control.
 * ⚠️ Applied from a template literal (people.js TONES) -- written by hand (L-64).
 */
.student__mark {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font: 700 16px/1 var(--sans);
  flex: none;
}
.student__mark--coral { background: var(--mark-tint); color: #a63a22; }
.student__mark--sun   { background: var(--warn-tint); color: #85550f; }
.student__mark--sky   { background: var(--sky-tint);  color: #276a83; }
.student__mark--leaf  { background: var(--go-tint);   color: #33603f; }
.student__subject { font-size: 15px; padding: 6px 12px; }

/* ------------------------------------------------------ the list of lessons */

/*
 * ⭐ The view that shows ARRANGEMENTS rather than occurrences: Ben, Tuesdays
 * and Thursdays, 4:00, Reading, every week. The grids answer "what happens on
 * the 17th"; this answers "what is the standing agreement".
 *
 * ⚠️ It is a GRID that becomes a STACK, not a table. A table cannot reflow, and
 * this app has already failed WCAG Reflow once by putting a fixed-width row
 * inside a narrow card (L-24). Below 720px every row becomes two lines and
 * nothing is ever cut off.
 */
.cal__list { margin-top: 4px; }

.cal__lrow {
  display: grid;
  /*
   * The three text columns get room in proportion to what they hold: a name
   * and a family, a day and a time, and a whole sentence. `minmax(0, …)` is
   * what stops a long family name forcing the row wider than its card --
   * without it a grid track refuses to shrink below its content and the row
   * silently overflows, which is L-24's fault in a different container.
   */
  grid-template-columns:
    minmax(0, 1.2fr) minmax(128px, 0.9fr) minmax(0, 1.1fr) minmax(0, 1.1fr) auto;
  gap: 10px 18px;
  align-items: center;
  padding: 16px 18px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-card);
  box-shadow: var(--lift);
  margin-bottom: 10px;
}

.cal__lcell { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cal__lcell--act { align-items: flex-end; }

.cal__lname { font: 700 20px/1.2 var(--sans); }
.cal__ldays {
  font: 800 14px/1.2 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--mark);
}
/* Everything quiet on a row: the family, the time, "Online". One class so the
   three cannot drift apart. */
.cal__lsub, .cal__loften, .cal__lwhere, .cal__ltime {
  font-size: 17px;
  color: var(--ink-soft);
}
/*
 * ⚠️ A family name may be CLIPPED. A lesson time may not.
 *
 * The first version put the ellipsis on both, and "3:30 – 4:1…" is a worse
 * answer than a wrapped line -- it removes the fact she came for. The time
 * gets its own rule and simply never wraps.
 */
.cal__lsub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal__ltime { white-space: nowrap; }
.cal__loften { white-space: normal; }
/* An end date is the one thing on a row that will surprise her. Marked in a
   WORD ("ends 19 Dec") as well as a colour -- never colour alone. */
.cal__lends { color: var(--mark); font-weight: 700; }

.cal__lchip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: 600 16px/1 var(--sans);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  padding: 7px 12px;
  align-self: flex-start;
}

.cal__lchange {
  font: 600 17px/1 var(--sans);
  color: var(--ink);
  background: var(--paper-card);
  border: 1.5px solid var(--rule);
  border-radius: var(--radius);
  padding: 13px 18px;          /* 44px tall: the AAA target, on a small control */
  cursor: pointer;
  white-space: nowrap;
}
.cal__lchange:hover { border-color: var(--ink); }

/*
 * ⭐ "6 lessons a week across 5 students — 4.5 hours."
 *
 * Nothing in this app has ever told her how much she teaches, and it is the
 * first question anybody asks about their own week.
 */
.cal__ltotal {
  margin: 16px 0 0;
  padding: 16px 20px;
  font-size: 19px;
  background: var(--go-tint);
  border-left: 5px solid var(--go);
  border-radius: var(--radius);
}

/* "+ Add a lesson" and the view picker share the right-hand end of the bar. */
.cal__actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.cal__add { width: auto; }

@media (max-width: 720px) {
  /*
   * Two lines, not five columns. `auto` on the second column keeps Change
   * beside the row rather than under it, where it would read as a control for
   * whatever comes next.
   */
  .cal__lrow {
    grid-template-columns: 1fr auto;
    gap: 10px 14px;
    align-items: start;
  }
  .cal__lcell--act { grid-row: 1 / span 2; align-self: center; }
}

/* The List showing ONE student, because she came from that student. */
.cal__lfilter { font-size: 17px; color: var(--ink-soft); margin: 0 0 12px; }
.cal__lfilter b { color: var(--ink); }
.cal__lfilter-all {
  font: 600 17px/1 var(--sans);
  color: var(--go-dark);
  background: none;
  border: 0;
  padding: 8px 4px;               /* 33px tall text target inside a sentence */
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
/* The six-row cancel list, mounted under a List row (D-082). It is a card
   like the row above it, so it reads as belonging to that row. */
.cal__lhost {
  margin: -4px 0 10px;
  padding: 4px 18px 18px;
  border: 1px solid var(--rule);
  border-top: 0;
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--paper-card);
}
/* The tell-the-family block inside the calendar's own panel. */
.cal__tell { margin-top: 6px; }

/* --------------------------------------------------------------- the bar */

.cal { --calslot: 22px; }

.cal__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.cal__when {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  /*
   * ⚠️ A FLOOR, so the bar does not collapse on the List view.
   *
   * The List has no ‹ › and no Today -- there is nothing for them to step
   * through -- so without this the row loses 19px and the view picker JUMPS UP
   * under her finger. She presses "List", and the very buttons she used to
   * press it have moved before she reaches for the next one.
   *
   * That is L-47's fault exactly: "a message that appears where the finger is
   * going costs a whole press." 48px is the height of a .cal__nav button, so
   * the row keeps the shape it has in every other view.
   */
  min-height: 48px;
}

.cal__nav {
  font: 500 22px/1 var(--sans);
  color: var(--ink);
  background: var(--paper-card);
  border: 1.5px solid var(--rule);
  border-radius: 8px;
  width: 46px; height: 46px;
  display: grid; place-items: center;
  cursor: pointer;
}
.cal__title {
  font: 500 26px/1.15 var(--serif);
  margin: 0 6px;
  letter-spacing: -.01em;
  white-space: nowrap;
}
.cal__today {
  font: 600 16px/1 var(--sans);
  color: var(--ink);
  background: var(--paper-card);
  border: 1.5px solid var(--rule);
  border-radius: 8px;
  padding: 15px 16px;
  cursor: pointer;
}
/*
 * THE VIEW PICKER IS A SEGMENTED CONTROL (D-083; design/refresh p7): one sunken
 * track, the chosen view raised out of it. Which view she is on is told by
 * depth AND weight AND fill, never colour alone (D-077). Every segment stays
 * 44px tall (L-45).
 */
.cal__views {
  display: flex;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  background: var(--paper);
  box-shadow: var(--sunken);
}
.cal__view {
  font: 500 17px/1 var(--sans);
  color: var(--ink-soft);
  background: none;
  border: 0;
  border-radius: var(--radius-pill);
  min-height: 44px;
  padding: 13px 18px;
  cursor: pointer;
}
.cal__view--on { background: var(--ink); color: #fff; font-weight: 600; box-shadow: var(--press); }

/* ------------------------------------------------------------- an event */

/*
 * A LESSON IS A SOLID BLOCK in its subject's colour, white text (D-083; drawn
 * 9 Sep, design/refresh p7, built 12 Sep). It reads as an object sitting on
 * the grid rather than a tinted cell. Every block colour was measured with
 * white text on it (L-92): five of the six pass as they were; amber did not
 * (3.61:1) and is the app's own --warn pen here (5.04:1). Raised = pressable
 * (D-077): the block opens the lesson panel, so it carries a small shadow.
 * Her own diary notes and a cancelled lesson stay quiet -- see below.
 */
.cal__ev {
  display: block;
  width: 100%;
  text-align: left;
  border: 0;
  border-radius: 7px;
  background: var(--cal-bar, #a99e8f);
  color: #fff;
  box-shadow: 0 1px 2px rgba(90, 60, 40, .18);
  padding: 6px 8px;
  font: 600 14px/1.25 var(--sans);
  overflow: hidden;
  cursor: pointer;
}
.cal__evname { display: block; }
.cal__evtime {
  display: block;
  font-weight: 500;
  font-size: 13px;
  /* Full strength: white at 85% on the blue block fell under 4.5:1. */
  opacity: 1;
  font-variant-numeric: tabular-nums;
}
.cal__badge {
  font: 700 10px/1.4 var(--sans);
  letter-spacing: .08em;
  white-space: nowrap;
}
.cal__ev:focus-visible { outline: 3px solid var(--go); outline-offset: 1px; }

/* ⚠️ APPLIED FROM A TEMPLATE LITERAL -- invisible to check_public.mjs. */
.cal__ev--blue   { --cal-bar: #3d7ab0; --cal-tint: #e9f0f6; --cal-deep: #22496a; }
.cal__ev--green  { --cal-bar: #2f8259; --cal-tint: #eaf3ee; --cal-deep: #1a533a; }
.cal__ev--purple { --cal-bar: #7d5aa8; --cal-tint: #f1ecf7; --cal-deep: #523573; }
/* ⚠️ amber's block is --warn (#9a6313), not its old #b07d2a: white on #b07d2a is 3.61:1. Her key still says amber. */
.cal__ev--amber  { --cal-bar: #9a6313; --cal-tint: #f8f1e4; --cal-deep: #6d4a12; }
.cal__ev--rose   { --cal-bar: #b0506a; --cal-tint: #f9ecef; --cal-deep: #6f2c3f; }
.cal__ev--teal   { --cal-bar: #2b8189; --cal-tint: #e7f2f3; --cal-deep: #17545a; }

/* Her own notes are not lessons: a quiet tinted box with a grey edge, as before. */
.cal__ev--diary {
  --cal-bar: #a99e8f; --cal-tint: #efeae2; --cal-deep: #5c554c;
  background: var(--cal-tint);
  color: var(--cal-deep);
  border-left: 4px solid var(--cal-bar);
  box-shadow: none;
}
.cal__ev--blocking { border-left-style: dashed; }
/* Cancelled, or moved away from this square: the tint, a dashed edge, and the
   name struck through (calendar-view.js). Not faded -- a solid block at 58%
   left the words unreadable. */
.cal__ev--gone {
  background: var(--cal-tint);
  color: var(--cal-deep);
  border: 1px dashed var(--cal-bar);
  box-shadow: none;
}
.cal__ev--moved    { --cal-bar: var(--mark); --cal-tint: var(--mark-tint); --cal-deep: #8d3122; }

/* ------------------------------------------------------------ month grid */

.cal__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-card);
  overflow: hidden;
}
.cal__mh {
  padding: 10px 4px;
  text-align: center;
  background: #fbf8f3;
  border-bottom: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  font: 700 12px/1 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.cal__mh:first-child { border-left: 0; }
.cal__cell {
  border-left: 1px solid var(--rule);
  border-top: 1px solid var(--rule);
  padding: 5px 5px 8px;
  min-height: 104px;
}
.cal__cell:nth-child(7n + 1) { border-left: 0; }
.cal__cell--out { background: #fbf8f3; }
.cal__cell--out .cal__daynum { color: #c3bab0; }
.cal__daynum {
  font: 600 16px/1 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: none;
  border: 0;
  padding: 6px;
  margin: 0 0 3px -2px;
  min-width: 30px;
  border-radius: 50%;
  cursor: pointer;
}
.cal__cell--today .cal__daynum { background: var(--mark); color: #fff; font-weight: 700; }
.cal__cell .cal__ev { margin-bottom: 3px; padding: 3px 6px; font-size: 12px; border-left-width: 3px; }
.cal__cell .cal__evtime { display: none; }
.cal__more {
  font: 600 12px/1 var(--sans);
  color: var(--ink-soft);
  background: none;
  border: 0;
  padding: 5px 3px;
  cursor: pointer;
  text-decoration: underline;
}

/* ------------------------------------------------------------- week grid */

.cal__week {
  display: grid;
  grid-template-columns: 54px repeat(7, 1fr);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-card);
  overflow: hidden;
}
.cal__corner { grid-column: 1; }
.cal__wh {
  grid-row: 1;
  padding: 10px 4px;
  text-align: center;
  border-bottom: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  background: #fbf8f3;
}
.cal__wday {
  font: 700 12px/1 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.cal__wdate {
  font: 600 19px/1.35 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.cal__wh--today .cal__wday, .cal__wh--today .cal__wdate { color: var(--mark); }

.cal__allday {
  border-left: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding: 3px;
  background: #fdfbf7;
}
.cal__allday .cal__ev { font-size: 12px; padding: 4px 6px; }
.cal__allday .cal__evtime { display: none; }

.cal__time {
  grid-column: 1;
  font: 500 12px/1 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  text-align: right;
  padding: 3px 7px 0 0;
  border-top: 1px solid var(--rule);
}
.cal__slot {
  border-left: 1px solid var(--rule);
  border-top: 1px solid var(--rule);
  cursor: pointer;
}
.cal__slot--half { border-top-style: dotted; border-top-color: #f2ede3; }
/* Hatched, never hidden: an empty afternoon and a closed one must not look
   the same. */
.cal__slot--shut {
  background: repeating-linear-gradient(-45deg, #f7f3ec 0 6px, #fbf8f3 6px 12px);
  cursor: default;
}
.cal__place { position: relative; z-index: 1; padding: 1px 3px; pointer-events: none; }
.cal__place .cal__ev { height: 100%; pointer-events: auto; }

/* --------------------------------------------------------------- day view */

.cal__day {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-card);
  overflow: hidden;
}
.cal__day > .cal__ev { border-radius: 0; border-left-width: 6px; }
.cal__drow {
  display: grid;
  grid-template-columns: 78px 1fr;
  border-top: 1px solid var(--rule);
  min-height: 60px;
}
.cal__drow:first-child { border-top: 0; }
.cal__dtime {
  font: 500 14px/1 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  padding: 10px 10px 0 0;
  text-align: right;
}
.cal__dslot { padding: 5px 8px 5px 0; }
.cal__dslot--shut {
  background: repeating-linear-gradient(-45deg, #f7f3ec 0 6px, #fbf8f3 6px 12px);
}
.cal__day .cal__ev { padding: 12px 14px; font-size: 18px; }
.cal__day .cal__evtime { font-size: 15px; }
.cal__free {
  font: 500 15px/1 var(--sans);
  color: #b3aa9f;
  background: none;
  border: 0;
  padding: 14px 6px;
  width: 100%;
  text-align: left;
  cursor: pointer;
}
.cal__free:hover { color: var(--ink-soft); }

/* --------------------------------------------------- the list, on a phone */

.cal__ag {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-card);
  overflow: hidden;
}
.cal__agday {
  padding: 11px 16px;
  background: #fbf8f3;
  border-bottom: 1px solid var(--rule);
  border-top: 1px solid var(--rule);
}
.cal__agday:first-child { border-top: 0; }
.cal__agdate {
  font: 700 13px/1 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.cal__agday--today .cal__agdate { color: var(--mark); }
.cal__agrow {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 12px;
  align-items: start;
  width: 100%;
  text-align: left;
  padding: 15px 16px;
  border: 0;
  border-top: 1px solid var(--rule);
  background: none;
  cursor: pointer;
}
.cal__agrow:first-of-type { border-top: 0; }
.cal__agtime {
  font: 600 15px/1.35 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.cal__agmain { display: block; }
.cal__agname { display: block; font: 600 18px/1.3 var(--sans); color: var(--ink); }
.cal__agsub { display: block; font-size: 15px; color: var(--ink-soft); margin-top: 2px; }

/* ------------------------------------------------------ the key, and empty */

.cal__key {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin-top: 14px;
  font-size: 15px;
  color: var(--ink-soft);
}
.cal__keyitem { display: inline-flex; align-items: center; gap: 7px; }
.cal__swatch { width: 13px; height: 13px; border-radius: 3px; display: inline-block; background: #a99e8f; }
/* ⚠️ Also applied from a template literal. Keep in step with .cal__ev--*. */
.cal__swatch--blue   { background: #3d7ab0; }
.cal__swatch--green  { background: #2f8259; }
.cal__swatch--purple { background: #7d5aa8; }
.cal__swatch--amber  { background: #9a6313; }   /* matches the block, see .cal__ev--amber */
.cal__swatch--rose   { background: #b0506a; }
.cal__swatch--teal   { background: #2b8189; }
.cal__swatch--off    { background: #d8d0c4; border: 1px dashed #a99e8f; }

.cal__empty {
  font-size: 18px;
  color: var(--ink-soft);
  text-align: center;
  padding: 26px 16px;
  margin: 0;
}

/* A phone gets the list, so the grid rules above never apply there. */
@media (max-width: 720px) {
  .cal__title { font-size: 22px; }
  .cal__views { width: 100%; }
  .cal__view { flex: 1; text-align: center; }
  .cal__agrow { grid-template-columns: 84px 1fr; }
  .cal__drow { grid-template-columns: 62px 1fr; }
}

/* ------------------------------- the panel that opens when she presses a lesson

   Two labelled groups, because the app already calls this the dangerous
   distinction: "this one Tuesday" and "every Tuesday" must never be pressed by
   mistake for each other. D-076. */

.panelgrp { border-top: 1px solid var(--rule); padding-top: 15px; margin-top: 17px; }
.panelgrp__head {
  font: 600 13px/1 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 12px;
}
.panelgrp__acts { display: flex; flex-wrap: wrap; gap: 10px; }
.panelgrp__acts .linkbtn { padding: 14px 16px; }
.panelgrp__acts .btn { width: auto; flex: 1 1 200px; padding: 15px 18px; font-size: 19px; }

/* ------------------------------------------------------- the hours she teaches */

.hoursrow {
  display: grid;
  grid-template-columns: 30px 128px 1fr;
  align-items: center;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid var(--rule);
}
.hoursrow input[type="checkbox"] {
  width: 26px; height: 26px; margin: 0; accent-color: #1d5c3f;
}
.hoursrow__day { font: 600 18px/1 var(--sans); }
.hoursrow__times { display: flex; align-items: center; gap: 9px; }
.hoursrow__t { width: auto; flex: 1; min-width: 0; padding: 11px 12px; }
.hoursrow__sep { color: var(--ink-soft); font-size: 16px; }

@media (max-width: 520px) {
  .hoursrow { grid-template-columns: 30px 1fr; }
  .hoursrow__times { grid-column: 1 / -1; }
}


/* =====================================================================
   THE READING PAGES -- for-parents, privacy, terms                D-081

   ⚠️ THIS BLOCK WAS COPIED, BYTE FOR BYTE, INTO ALL THREE FILES. Verified by
   checksum on 11 September: the same md5 three times. That is L-49 in its
   purest form -- change the heading size once and three documents disagree,
   silently, while rendering perfectly.

   These pages are read, not used: a narrower column than the app, a serif for
   running text, and headings that are obviously headings, for someone who is 75.
   ===================================================================== */

/* The page's own title, where the masthead now carries the wordmark instead. */
.page__title { font: 500 34px/1.1 var(--serif); margin: 0 0 28px; letter-spacing: -.01em; }

/*
 * ⭐ THE FOOTER ON A STUDENT'S PAGE -- D-081's one deliberate exception.
 *
 * That page gets no masthead: D-028 says we never stand between a teacher and a
 * family, and our name above hers on a page a child opens is exactly that. So
 * the product is named at the BOTTOM, where somebody who wants to know goes
 * looking. ⚠️ And the link is a real 44px target, not the 34px grey footnote it
 * replaced -- it is the only route a grown-up has to what we hold.
 */
.pagefoot {
  margin-top: 52px;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
  text-align: center;
}
.pagefoot .masthead__word { font-size: 22px; color: var(--ink-soft); margin: 0 0 6px; }
.pagefoot .masthead__word i { color: var(--mark); }
.pagefoot__links { margin: 0; }
.pagefoot__links a {
  display: inline-block;
  padding: 12px 14px;              /* 17px line + 24 = 41 … plus the 3px border below */
  font: 500 17px/1.35 var(--sans);
  color: var(--go);
  border-bottom: 1.5px solid var(--rule);
}
.pagefoot__links a:hover { border-bottom-color: var(--go); }

.doc { max-width: 640px; }

/*
 * ⚠️ THIS RULE EXISTED ALREADY -- as an inline style attribute, copied by hand
 * onto the <h1> of all three documents:
 *     style="font:500 38px/1.1 var(--serif); margin:0 0 10px; letter-spacing:-.015em"
 * which is `.card__headline` transcribed out of this stylesheet, three times.
 * Change the heading and three pages disagree, silently. L-49, L-65.
 */
.doc__title { font: 500 38px/1.1 var(--serif); margin: 0 0 10px; letter-spacing: -.015em; }
.doc h2 { font: 500 27px/1.25 var(--serif); margin: 44px 0 12px; letter-spacing: -.01em; }
.doc h2:first-of-type { margin-top: 32px; }
.doc p, .doc li { font-size: 19px; line-height: 1.62; }
.doc p { margin: 0 0 16px; }
.doc ul { margin: 0 0 16px; padding-left: 22px; }
.doc li { margin-bottom: 8px; }
.doc strong { font-weight: 600; }

/*
 * ⚠️⚠️ ELEMENT-QUALIFIED ON PURPOSE, AND IT IS A BUG FIX.
 *
 * `.doc p` is specificity 0,1,1. `.doc__lead`, `.doc__version` and `.doc__foot`
 * were 0,1,0 -- so THREE DECLARED SIZES NEVER REACHED THE PAGE. Measured in
 * Chrome: the lead rendered 19px not 21, the footer 19px not 17, and the version
 * stamp 19px not 14 -- while keeping its 600 weight, its uppercase and its
 * letter-spacing.
 *
 * ⭐ That is why "VERSION 2026-09-08A" was the loudest thing on the first screen
 * a parent read, three lines above the first sentence about their child. It
 * looked like a deliberate shout. It was a specificity accident.
 */
.doc p.doc__lead { font-size: 21px; color: var(--ink-soft); margin-bottom: 8px; }
.doc p.doc__version {
  font: 600 14px/1 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 30px;
}
.doc__back {
  display: inline-block;
  font: 500 18px/1 var(--sans);
  color: var(--go);
  text-decoration: none;
  border-bottom: 1.5px solid var(--rule);
  padding-bottom: 4px;
  margin-bottom: 30px;
}
.doc__back:hover { color: var(--go-dark); border-bottom-color: var(--go); }
.doc__note { background: var(--go-tint); border-radius: var(--radius); padding: 20px 22px; margin: 32px 0; }
.doc__note p:last-child { margin-bottom: 0; }
.doc__foot { margin-top: 52px; padding-top: 22px; border-top: 1px solid var(--rule); }
.doc .doc__foot, .doc .doc__foot p { font-size: 17px; color: var(--ink-soft); }
.doc__foot a { color: var(--go); }
