:root {
  --bg: #f6f7f9;
  --panel: #ffffff;
  --ink: #1c2431;
  --muted: #66738a;
  --line: #dfe4ec;
  --accent: #3f6fd8;
  --accent-soft: #eaf0fd;
  --good: #1f9254;
  --warn: #b7791f;
  --bad: #c0392b;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(20, 30, 50, .06), 0 6px 18px rgba(20, 30, 50, .05);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #12161d;
    --panel: #1a2028;
    --ink: #e7ecf3;
    --muted: #96a2b5;
    --line: #2b3441;
    --accent: #6f9bff;
    --accent-soft: #1e2a41;
    --good: #4ac57f;
    --warn: #e0a94a;
    --bad: #ef7367;
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 6px 18px rgba(0, 0, 0, .25);
  }
}

* { box-sizing: border-box; }

/* Everything sized in rem scales from here, so one variable adjusts the whole
   interface. The default is a shade under the browser's 16px, which reads
   better at the width a lesson actually runs at. */
html {
  font-size: calc(16px * var(--text-scale, 0.94));
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 1rem/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--accent); }

/* ---------------------------------------------------------------- layout */


.wrap { max-width: 1000px; margin: 0 auto; padding: 1.75rem 1.25rem 4rem; }
.narrow { max-width: 460px; margin: 0 auto; padding: 3.5rem 1.25rem; }
/* For pages that are a grid rather than a column of prose. A calendar
   squeezed into a reading width is a calendar nobody reads. */
.wide {
  max-width: min(1800px, 97vw);
  margin: 0 auto;
  padding: 1.75rem clamp(1rem, 2vw, 2rem) 4rem;
}

.row { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; }
.row.between { justify-content: space-between; }
.grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }

/* ----------------------------------------------------------------- text */

h1 { font-size: 1.6rem; margin: 0 0 .35rem; letter-spacing: -.01em; }
h2 { font-size: 1.15rem; margin: 2rem 0 .75rem; }
h3 { font-size: 1rem; margin: 0 0 .35rem; }
.sub { color: var(--muted); margin: 0 0 1.5rem; }
.muted { color: var(--muted); }
.small { font-size: .85rem; }
.mono { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: .85rem; }

/* ---------------------------------------------------------------- panels */

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
  box-shadow: var(--shadow);
}

.card + .card { margin-top: .85rem; }
.card h3 a { text-decoration: none; }

.empty {
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  padding: 2.25rem 1.25rem;
  text-align: center;
  color: var(--muted);
}

/* ---------------------------------------------------------------- pills */

.pill {
  display: inline-block;
  padding: .12rem .55rem;
  border-radius: 999px;
  font-size: .76rem;
  font-weight: 600;
  background: var(--accent-soft);
  color: var(--accent);
  white-space: nowrap;
}

.pill.good { background: color-mix(in srgb, var(--good) 15%, transparent); color: var(--good); }
.pill.warn { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
.pill.bad  { background: color-mix(in srgb, var(--bad) 15%, transparent);  color: var(--bad); }
.pill.idle { background: var(--line); color: var(--muted); }

/* ---------------------------------------------------------------- forms */

label { display: block; margin: .9rem 0 .3rem; font-weight: 600; font-size: .9rem; }
.hint { font-weight: 400; color: var(--muted); font-size: .82rem; }

input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=file], select, textarea {
  width: 100%;
  padding: .55rem .7rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
}

textarea { min-height: 5rem; resize: vertical; }
input:focus, textarea:focus, select:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

button, .btn {
  display: inline-block;
  padding: .55rem 1rem;
  border: 1px solid transparent;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}

button:hover, .btn:hover { filter: brightness(1.06); }

.btn.secondary, button.secondary {
  background: transparent;
  border-color: var(--line);
  color: var(--ink);
}

button.link {
  background: none;
  border: none;
  color: var(--accent);
  padding: 0;
  font-weight: 500;
}

.inline { display: inline; }

/* A checkbox and its words on one line. `label` above is display:block and
   600-weight, which would otherwise stack the box over the text and shout. */
label.check {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  font-weight: 400;
  margin: 1rem 0 .2rem;
}
label.check input[type="checkbox"] {
  width: auto;
  margin: .15rem 0 0;
  flex: none;
}
.actions { display: flex; gap: .6rem; align-items: center; margin-top: 1.25rem; flex-wrap: wrap; }

/* --------------------------------------------------------------- alerts */

.alert {
  padding: .7rem .9rem;
  border-radius: 8px;
  border: 1px solid;
  margin-bottom: 1rem;
  font-size: .92rem;
}

.alert.error { background: color-mix(in srgb, var(--bad) 10%, transparent); border-color: var(--bad); }
.alert.warn  { background: color-mix(in srgb, var(--warn) 12%, transparent); border-color: var(--warn); }
.alert.info  { background: var(--accent-soft); border-color: var(--accent); }

.alert ul { margin: .4rem 0 0; padding-left: 1.1rem; }

/* --------------------------------------------------------------- tables */

table { width: 100%; border-collapse: collapse; font-size: .92rem; }
th, td { text-align: left; padding: .5rem .6rem; border-bottom: 1px solid var(--line); }
th { color: var(--muted); font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; }

/* ------------------------------------------------------------ curriculum */

.tree { margin: 0; padding: 0; list-style: none; }
.tree .unit { border-top: 1px solid var(--line); padding-top: .8rem; margin-top: .8rem; }
.tree .topic { margin: .5rem 0 .5rem 1rem; }
.tree .objectives { margin: .25rem 0 0 1rem; padding-left: 1rem; color: var(--muted); font-size: .9rem; }

details > summary { cursor: pointer; font-weight: 600; }
details[open] > summary { margin-bottom: .5rem; }

/* ---------------------------------------------------------------- meter */

.meter { height: 6px; border-radius: 999px; background: var(--line); overflow: hidden; margin-top: .5rem; }
.meter > span { display: block; height: 100%; background: var(--accent); }
.meter.over > span { background: var(--bad); }

/* ---------------------------------------------------- figures and resources */

.attachment { max-width: 88%; }

.viz {
  margin: 0;
  padding: .75rem .85rem .5rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
}

.viz svg { display: block; width: 100%; height: auto; }
.viz figcaption {
  margin-top: .35rem;
  font-size: .85rem;
  font-weight: 600;
  text-align: center;
  color: var(--ink);
}
.viz .viz-note { margin: .25rem 0 0; font-size: .8rem; color: var(--muted); text-align: center; }

.viz-grid line { stroke: var(--line); stroke-width: 1; }
.viz-axis line { stroke: var(--muted); stroke-width: 1.5; }
/* These sizes are in the figure's own units, not screen pixels. A figure is
   drawn on a 540-wide canvas and then shown in a board that may be 300 across,
   so everything here is scaled down by roughly half before the student sees
   it. 11px was arriving at about 5px and was simply unreadable — hence the
   apparently oversized numbers below. Changing the canvas width in visuals.py
   changes what these need to be. */
.viz-tick { fill: var(--muted); font-size: 17px; font-family: inherit; }
.viz-tick line { stroke: var(--muted); stroke-width: 1.5; }
.viz-axis-label { fill: var(--muted); font-size: 18px; font-style: italic; }
.viz-series-label { font-size: 18px; font-weight: 600; }
.viz-point-label { fill: var(--ink); font-size: 17px; font-weight: 600; }
.viz-node-label { fill: var(--ink); font-size: 18px; font-weight: 600; }

.resource-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
}

.resource-video { position: relative; padding-top: 56.25%; background: #000; }
.resource-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.resource-body { padding: .7rem .9rem .85rem; }
.resource-title {
  display: block;
  margin: .35rem 0 .1rem;
  font-weight: 650;
  text-decoration: none;
}
.resource-title:hover { text-decoration: underline; }
.resource-note { margin: .4rem 0 0; font-size: .88rem; color: var(--ink); }

/* ------------------------------------------------------------- review */

/* The library of finished lessons. A lesson stops being taught but does not
   stop being useful — this is where it is revised from. */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0 0 1rem;
}

.chip {
  display: inline-block;
  padding: .3rem .7rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  font-size: .8rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.chip:hover { border-color: var(--chip, var(--accent)); color: var(--ink); }
.chip.on {
  border-color: var(--chip, var(--accent));
  background: color-mix(in srgb, var(--chip, var(--accent)) 14%, transparent);
  color: var(--chip, var(--accent));
}

.review-row {
  gap: 1rem;
  padding: .75rem 0;
  border-bottom: 1px solid var(--line);
  align-items: flex-start;
}
.card > .review-row:last-child { border-bottom: 0; }

.review-what { min-width: 0; }
.review-what strong {
  display: block;
  line-height: 1.35;
  margin-bottom: .2rem;
}

/* What is actually inside, so it is clear whether opening it is worthwhile. */
.review-holds { margin-top: .25rem; }

.review-state {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex: 0 0 auto;
}

/* A secondary action next to a primary one: same shape, less shouting. */
.btn.quiet {
  background: var(--panel);
  color: var(--muted);
  border: 1px solid var(--line);
}
.btn.quiet:hover { color: var(--ink); border-color: var(--accent); }

@media (max-width: 620px) {
  .review-row { flex-direction: column; }
  .review-state { width: 100%; justify-content: flex-start; }
}

/* Used on the lesson bar and in the review list, so it belongs in the
   shared stylesheet. It lived in lesson.css, which the review page does
   not load — the chips there came out plain grey. */
.subject-chip {
  display: inline-block;
  padding: .05rem .5rem;
  border-radius: 999px;
  font-weight: 600;
  background: color-mix(in srgb, var(--chip, var(--accent)) 16%, transparent);
  color: var(--chip, var(--accent));
}

/* ----------------------------------------------------------- calendar */

/* The quarter at a glance. The point is that nobody is surprised: a student
   can see Friday is the unit test, and a parent can see the shape of the term
   without asking. */

.cal {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  min-width: 900px;
}

.cal th {
  padding: .35rem .5rem;
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--muted);
  text-align: left;
  border-bottom: 1px solid var(--line);
}

.cal-day {
  vertical-align: top;
  width: 14.28%;
  height: 120px;
  padding: .35rem;
  border: 1px solid var(--line);
  border-collapse: collapse;
}

/* Days padding the grid out to whole weeks, and weekends. Present so the
   columns line up, but visibly not part of the school week. */
.cal-day.outside { background: var(--bg); opacity: .45; }
.cal-day.off { background: color-mix(in srgb, var(--muted) 5%, transparent); }

.cal-day.today {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.cal-day.flex { background: color-mix(in srgb, var(--warn) 8%, transparent); }

.cal-date {
  font-size: .72rem;
  font-weight: 700;
  color: var(--muted);
  margin-bottom: .25rem;
}
.cal-day.today .cal-date { color: var(--accent); }
.cal-month {
  margin-left: .25rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.cal-note {
  font-size: .64rem;
  font-weight: 600;
  color: var(--warn);
  margin-bottom: .2rem;
}

/* One lesson. The subject colour is the fastest way to read a week. */
.cal-block {
  display: block;
  margin-bottom: .18rem;
  padding: .16rem .34rem;
  border-radius: 5px;
  border-left: 3px solid var(--chip, var(--accent));
  background: color-mix(in srgb, var(--chip, var(--accent)) 10%, transparent);
  font-size: .7rem;
  line-height: 1.3;
  overflow: hidden;
}

.cal-subject {
  display: block;
  font-weight: 700;
  color: var(--chip, var(--accent));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-topic {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-clamp: 2;
  overflow: hidden;
  color: var(--muted);
}

.cal-kind {
  display: inline-block;
  margin-top: .1rem;
  padding: 0 .28rem;
  border-radius: 3px;
  background: var(--warn);
  color: #fff;
  font-size: .58rem;
  font-weight: 700;
  text-transform: uppercase;
}

/* Already taught: keep it legible but let the eye skip past it to what is
   still coming, which is the part worth looking at. */
.cal-block.complete { opacity: .5; }
.cal-block.complete .cal-subject { text-decoration: line-through; }

/* A heading inside a form, grouping fields that belong together. */
.form-section {
  margin: 1.4rem 0 .5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--muted);
  font-weight: 700;
}
.form-section:first-of-type { margin-top: .6rem; }

/* "part 2 of 3" on a calendar block: quiet, but it is the difference between
   a continuation and the same lesson apparently scheduled twice. */
.cal-part {
  display: block;
  margin-top: .1rem;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--chip, var(--accent));
  opacity: .75;
}


/* ---------------------------------------------------- performance tasks */

/* Every card here has the same parts in the same order — title, one line of
   practicalities, the brief, then the actions — and the actions are pinned to
   the bottom so they line up across a row however long the briefs are. That
   alignment is the whole difference between a list you can scan and a wall. */

.task-subject { margin-bottom: 1.5rem; padding-bottom: 1rem; }

.task-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .7rem;
  margin-bottom: .9rem;
  border-bottom: 1px solid var(--line);
}
.task-head .subject-chip { font-size: .9rem; padding: .15rem .7rem; }

/* --- the two slots --- */

.task-slots {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: .8rem;
}

.task-slot,
.task-idea {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: .8rem .9rem;
  background: var(--panel);
}

/* An empty slot is a gap to fill, not a thing in its own right. */
.task-slot:not(.filled) {
  border-style: dashed;
  background: var(--bg);
}
.task-slot.filled { border-color: color-mix(in srgb, var(--good) 40%, var(--line)); }

.task-slot-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  margin-bottom: .5rem;
  font-size: .64rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  font-weight: 700;
  color: var(--muted);
}

.task-slot h3,
.task-idea h3 {
  margin: 0 0 .3rem;
  font-size: .95rem;
  line-height: 1.35;
}

/* Time and materials on one line: the two things that decide whether an idea
   is possible this afternoon. */
.task-meta {
  margin: 0 0 .55rem;
  font-size: .74rem;
  color: var(--muted);
  line-height: 1.45;
}

.task-brief {
  margin: 0 0 .6rem;
  font-size: .84rem;
  line-height: 1.55;
}

/* The marking guidance. Folded away for the student, open for the parent,
   because only one of them is about to mark it. */
.task-detail {
  margin: 0 0 .6rem;
  font-size: .78rem;
}
.task-detail summary {
  cursor: pointer;
  color: var(--accent);
  font-weight: 600;
  font-size: .74rem;
}
.task-detail summary::marker { color: var(--muted); }
.task-detail p {
  margin: .4rem 0 0;
  padding-left: .7rem;
  border-left: 2px solid var(--line);
  color: var(--muted);
  line-height: 1.5;
}

.task-comment {
  margin: 0 0 .6rem;
  padding: .45rem .6rem;
  border-radius: 8px;
  background: var(--bg);
  font-size: .8rem;
  font-style: italic;
  color: var(--muted);
}

.task-empty {
  margin: 0;
  font-size: .82rem;
  color: var(--muted);
}

/* --- actions, always last, always aligned --- */

.task-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .45rem;
  /* Pushes the row to the bottom of the card, which is what makes every
     Choose button in a row sit on the same line. */
  margin-top: auto;
  padding-top: .7rem;
}
.task-actions form {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin: 0;
}
.task-actions select { padding: .3rem .4rem; font-size: .8rem; }
.task-actions button { padding: .35rem .8rem; font-size: .82rem; }

/* --- ideas --- */

.task-ideas-head {
  margin: 1.3rem 0 .6rem;
  font-size: .7rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--muted);
  font-weight: 700;
}
.task-ideas-head .muted { text-transform: none; letter-spacing: 0; font-weight: 400; }

.task-ideas {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  gap: .8rem;
  align-items: stretch;
}

/* --- the footer action --- */

.task-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
  margin-top: 1.1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
}
.task-foot form { margin: 0; }

/* --- marking --- */

.task-grade {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .5rem;
  width: 100%;
}
.task-grade label {
  display: flex;
  flex-direction: column;
  gap: .15rem;
  margin: 0;
  font-size: .72rem;
  color: var(--muted);
}
.task-grade input { padding: .3rem .45rem; font-size: .85rem; }
.task-grade input[type="number"] { width: 5rem; }
.task-grade .grow { flex: 1; min-width: 11rem; }
.task-grade .grow input { width: 100%; }

/* The quieter of two actions. Throwing an idea away should not look as
   inviting as taking one. */
button.quiet {
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
}
button.quiet:hover {
  background: none;
  color: var(--bad);
  border-color: var(--bad);
}

/* A label that only a screen reader needs, because the select beside the
   button already reads as "use as task 1" to everyone else. */
.sr-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.grow { flex: 1; }

/* ------------------------------------------------------- sheet shortcuts */

/* The app writes the log it can; the school wants it in the family's own
   sheet. These are the trip to that sheet, one click from the rows being
   copied rather than a hunt through browser tabs. */
.sheet-links {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  margin: 0 0 1rem;
}

.sheet-link {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex: 1 1 260px;
  padding: .65rem .8rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  text-decoration: none;
  color: var(--ink);
}
.sheet-link:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.sheet-link strong { display: block; font-size: .88rem; line-height: 1.3; }
.sheet-link .small { display: block; }
.sheet-icon { font-size: 1.3rem; line-height: 1; }

.sheet-setup {
  margin: 0 0 1.25rem;
  font-size: .85rem;
}
.sheet-setup summary {
  cursor: pointer;
  color: var(--accent);
  font-weight: 600;
  font-size: .8rem;
}
.sheet-setup > p { margin: .5rem 0 .6rem; }

.sheet-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .6rem;
  margin: 0;
}
.sheet-form label {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  flex: 1 1 280px;
  margin: 0;
  font-size: .74rem;
  color: var(--muted);
}
.sheet-form input { width: 100%; padding: .35rem .5rem; font-size: .85rem; }

/* ------------------------------------------------------------- grades */

.grade-subject { margin-bottom: 1.1rem; }

.grade-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .6rem;
  margin-bottom: .7rem;
  border-bottom: 1px solid var(--line);
}
.grade-standing { display: flex; align-items: center; gap: .5rem; }
.grade-mark { font-size: 1.5rem; font-weight: 700; line-height: 1; }

.grade-average { text-align: right; }
.grade-number { display: block; font-size: 2.2rem; font-weight: 700; line-height: 1; }

.grade-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.grade-table th {
  padding: .3rem .5rem;
  text-align: left;
  font-size: .66rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
}
.grade-table td { padding: .35rem .5rem; border-bottom: 1px solid var(--line); }
.grade-table tr:last-child td { border-bottom: 0; }
.grade-table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* A row that cannot count yet. Visible, but clearly not part of the total. */
.grade-table tr.incomplete td { opacity: .6; }
.warn-text { color: var(--warn); }

.grade-marks { margin-top: .8rem; }
.grade-marks summary {
  cursor: pointer;
  font-size: .74rem;
  font-weight: 600;
  color: var(--accent);
}
.grade-marks table { margin-top: .5rem; }

.grade-missing {
  margin: .7rem 0 0;
  padding: .5rem .7rem;
  border-radius: 8px;
  background: color-mix(in srgb, var(--warn) 10%, transparent);
  font-size: .8rem;
}

.grade-key { margin-top: 1.2rem; font-size: .85rem; }
.grade-key summary { cursor: pointer; color: var(--accent); font-weight: 600; font-size: .8rem; }
.grade-key ul { margin: .5rem 0 0; padding-left: 1.1rem; }

.log-dates {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
  margin: 0 0 1rem;
}
.log-dates select { padding: .3rem .5rem; font-size: .85rem; }

/* A quiz or test on the calendar. Deliberately louder than a lesson: this is
   the entry someone scans the month for. */
.cal-exam {
  display: block;
  margin-bottom: .2rem;
  padding: .18rem .34rem;
  border-radius: 5px;
  border: 1px solid var(--chip, var(--accent));
  background: color-mix(in srgb, var(--chip, var(--accent)) 16%, transparent);
  font-size: .62rem;
  line-height: 1.25;
  overflow: hidden;
}
.cal-exam-kind {
  display: block;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--chip, var(--accent));
}
.cal-exam-subject {
  display: block;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A unit test or a quarterly carries more weight than a quiz, so it reads
   heavier. */
.cal-exam.major {
  border-width: 2px;
  background: color-mix(in srgb, var(--chip, var(--accent)) 26%, transparent);
}

/* And the day itself is tinted, so a week with a test in it is obvious
   without reading any of the cells. */
.cal-day.has-exam { background: color-mix(in srgb, var(--warn) 7%, transparent); }
.cal-day.has-exam.today { background: color-mix(in srgb, var(--warn) 10%, transparent); }

/* --------------------------------------------------------------- quizzes */

.quiz-q { margin-bottom: .8rem; padding: .9rem 1rem; }
.quiz-q.right { border-left: 3px solid var(--good); }
.quiz-q.wrong { border-left: 3px solid var(--bad); }

.quiz-q-head {
  display: flex;
  align-items: baseline;
  gap: .7rem;
  margin-bottom: .7rem;
}
.quiz-num {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: .72rem;
  font-weight: 700;
}
.quiz-prompt { flex: 1; line-height: 1.5; }
.quiz-points {
  flex: 0 0 auto;
  font-size: .72rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

.quiz-body { padding-left: calc(22px + .7rem); }
.quiz-body input[type="text"],
.quiz-body textarea { width: 100%; max-width: 34rem; }

.quiz-choice {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  padding: .4rem .55rem;
  margin: 0 0 .3rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  font-size: .9rem;
  line-height: 1.45;
}
.quiz-choice:hover { border-color: var(--accent); background: var(--accent-soft); }
.quiz-choice input { margin-top: .2rem; }

/* After marking, the right answer is shown whether or not it was chosen. */
.quiz-choice.is-key {
  border-color: var(--good);
  background: color-mix(in srgb, var(--good) 10%, transparent);
}

.quiz-verdict {
  margin-top: .7rem;
  padding: .55rem .7rem 0 calc(22px + .7rem);
  border-top: 1px solid var(--line);
}
.quiz-verdict p { margin: .35rem 0 0; }

.quiz-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .7rem;
  margin-top: 1rem;
}

/* How long the test takes. It has replaced a lesson, so the period it
   occupies is worth stating. */
.cal-exam-mins {
  display: block;
  font-size: .58rem;
  color: var(--muted);
}

/* ------------------------------------------------------------------ nav */

/* The top bar. Links that look like links you might click, a clear mark on
   the page you are on, and sign-out set apart from navigation so it is never
   pressed by accident. */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  /* Real vertical padding and a minimum rather than a fixed height. With a
     fixed 52px the nav wrapped to two rows on a narrow screen and the second
     row was simply cropped, which read as buttons jammed against the top. */
  padding: .5rem clamp(1rem, 3vw, 2rem);
  min-height: 52px;
  flex-wrap: wrap;
  row-gap: .35rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 30;
}

.brand {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-weight: 700;
  font-size: .95rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.brand:hover { color: var(--accent); }

.topbar nav {
  display: flex;
  align-items: center;
  gap: .15rem;
  margin-left: auto;
  overflow-x: auto;
  scrollbar-width: none;
}
.topbar nav::-webkit-scrollbar { display: none; }

.topbar nav a {
  padding: .38rem .7rem;
  border-radius: 8px;
  color: var(--muted);
  text-decoration: none;
  font-size: .86rem;
  font-weight: 600;
  white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
.topbar nav a:hover { background: var(--bg); color: var(--ink); }

/* Where you are. Underlining every link made the whole bar look pressed. */
.topbar nav a.here {
  background: var(--accent-soft);
  color: var(--accent);
}

/* Leaving is not navigation, so it does not sit in the row of pages. */
.topbar nav .signout {
  margin-left: .5rem;
  padding-left: .9rem;
  border-left: 1px solid var(--line);
  color: var(--muted);
  font-weight: 500;
}
.topbar nav .signout:hover { background: none; color: var(--bad); }

/* ----------------------------------------------------------- statements */

/* A sentence worth looking at: a verse, a definition, a law. It used to be
   laid out on a coordinate plane, because that was the only kind that could
   place text — axes, gridlines and all. */
.viz-statement { margin: 0; }

.statement-card {
  margin: 0;
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: 10px;
  background: linear-gradient(
    to bottom right,
    color-mix(in srgb, var(--accent) 6%, var(--panel)),
    var(--panel)
  );
}

.statement-heading {
  margin: 0 0 .6rem;
  font-size: .66rem;
  text-transform: uppercase;
  letter-spacing: .07em;
  font-weight: 700;
  color: var(--accent);
}

.statement-line {
  margin: 0 0 .4rem;
  font-size: .95rem;
  line-height: 1.5;
  color: var(--muted);
}
.statement-line:last-of-type { margin-bottom: 0; }

/* The line the card exists for. */
.statement-focus {
  font-size: 1.25rem;
  font-weight: 650;
  line-height: 1.35;
  color: var(--ink);
  margin: .2rem 0 .5rem;
}

.statement-source {
  margin-top: .7rem;
  padding-top: .55rem;
  border-top: 1px solid var(--line);
  font-size: .76rem;
  color: var(--muted);
}

/* ------------------------------------------------------------ resources */

.res-subject { margin-bottom: 1.1rem; }

.res-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: .5rem;
  margin-top: .85rem;
}

.res {
  display: flex;
  gap: .65rem;
  padding: .6rem .7rem;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--panel);
}
.res:hover { border-color: var(--accent); }

/* What kind of thing it is, before the title is read. */
.res-kind {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 7px;
  font-size: .82rem;
  background: var(--bg);
  color: var(--muted);
}
.res-video { background: color-mix(in srgb, var(--bad) 14%, transparent); color: var(--bad); }
.res-practice { background: color-mix(in srgb, var(--good) 14%, transparent); color: var(--good); }
.res-reference { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }

.res-body { min-width: 0; }
.res-body p { margin: .2rem 0 0; }

.res-title {
  font-weight: 650;
  font-size: .88rem;
  line-height: 1.35;
  text-align: left;
  padding: 0;
}

/* Where the link goes, said before it is followed. */
.res-where { font-variant-numeric: tabular-nums; }

.res-empty { margin: .85rem 0 0; }

/* ----------------------------------------------------------------- log */

/* The school's own shape: a day per row, eight columns across. What made it
   unreadable was the cells, not the layout — each held a run of
   "Subject- text Subject- text" with no breaks in a column too narrow for it.
   The columns are unchanged; the contents are split by subject. */

.log-card { padding: 0; overflow-x: auto; }

.log {
  width: 100%;
  min-width: 1100px;
  border-collapse: collapse;
  table-layout: fixed;
}

.log th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: .6rem .7rem;
  text-align: left;
  vertical-align: bottom;
  font-size: .64rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  font-weight: 700;
  color: var(--muted);
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

.log td {
  padding: .7rem;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
  font-size: .8rem;
  line-height: 1.5;
}
.log tbody tr:last-child td { border-bottom: 0; }
.log tbody tr:hover td { background: color-mix(in srgb, var(--accent) 4%, transparent); }

/* Date and week stay narrow so the prose columns get the room. */
.log-when { width: 84px; }
.log-when strong { display: block; white-space: nowrap; }
.log-when .small { display: block; }

/* A subject's contribution to one column, kept apart from the next one's. */
.log-entry { margin-bottom: .55rem; }
.log-entry:last-child { margin-bottom: 0; }
.log-entry p { margin: 0 0 .25rem; }
.log-entry p:last-child { margin-bottom: 0; }

/* Whose line this is. Small and coloured so the eye can group by subject
   down a column without reading it. */
.log-who {
  display: block;
  font-size: .6rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: .1rem;
}

.log-subject {
  display: block;
  font-weight: 600;
  margin-bottom: .15rem;
}

/* ------------------------------------------------------- sitting it again */

/* Which sitting the grade was built from, and what the earlier ones scored.
   Small and grey: it is context for the mark beside it, not a second mark. */
.sittings {
  display: block;
  margin-top: .15rem;
  font-size: .72rem;
  color: var(--muted);
}

.sittings.warn-text { color: var(--warn); }

/* Review and Sit-it-again sit together at the end of the row. They wrap
   rather than squeeze on a narrow screen, because a mis-tap here starts a
   whole new paper. */
.mark-actions {
  display: flex;
  gap: .6rem;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
}

.mark-actions form { margin: 0; }

.mark-actions .link {
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
}

@media (pointer: coarse) {
  .mark-actions { gap: 1rem; }
  .mark-actions a, .mark-actions .link { padding: .35rem 0; }
}

/* ------------------------------------------------------------ installing */

/* Only ever shown when the browser has actually offered it. */
.install-app {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .35rem .7rem;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: .8rem;
  font-weight: 600;
  cursor: pointer;
}

.install-app .install-why {
  font-weight: 400;
  font-size: .72rem;
  color: var(--muted);
}

/* No room to explain it on a narrow screen; the label carries it. */
@media (max-width: 760px) {
  .install-app .install-why { display: none; }
}

/* ---------------------------------------------- curriculum import progress */

/* A parse runs for minutes with nothing to show. Without a number on it the
   page reads as hung, and the honest answer — how many subjects are done —
   is already known. */
.import-progress-line {
  margin: .6rem 0 .35rem;
  font-size: .85rem;
  font-variant-numeric: tabular-nums;
}

.import-progress {
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

.import-progress span {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: inherit;
  transition: width .4s ease;
}

/* ------------------------------------------------------------- notes page */

.note-entry { margin-bottom: 1rem; }

/* A note is written as prose with line breaks that mean something — a list,
   a worked step. Collapsing them the way HTML normally does would throw that
   away, so the whitespace is kept exactly as it was typed. */
.note-body {
  margin-top: .75rem;
  padding-top: .75rem;
  border-top: 1px solid var(--line);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.55;
}

/* --------------------------------------------- device setup, on the login
   Temporary: goes when every tablet is set up. Deliberately quiet so it
   does not compete with signing in, but reachable without an account,
   because the certificate has to be trusted before anyone can sign in. */

.setup-help { border-style: dashed; }
.setup-steps {
  margin: .6rem 0 0;
  padding-left: 1.1rem;
  font-size: .86rem;
  line-height: 1.5;
}
.setup-steps li { margin-bottom: .5rem; }

/* The hand-rolled install steps, for Safari only.
   Chrome installs itself from a button; Safari makes the person do it, so
   all this can do is say where to press. */

.install-steps {
  max-width: 42rem;
  margin: 1rem auto;
  padding: .9rem 1.1rem;
  border: 1px dashed var(--line);
  border-radius: 12px;
  background: var(--panel);
  font-size: .88rem;
  line-height: 1.55;
}
.install-steps p { margin: 0 0 .6rem; }
.install-steps p:last-child { margin-bottom: 0; }

/* The trade-off is the part worth reading, so it does not look like a
   footnote. */
.install-warn {
  border-left: 3px solid var(--accent);
  padding-left: .7rem;
  color: var(--muted);
}

/* --------------------------------------------------------------- practice */

.practice-row { padding: .55rem 0; border-bottom: 1px solid var(--line); }
.practice-row:last-child { border-bottom: none; }

.practice-q { margin-bottom: .9rem; }
.practice-ask { margin: 0 0 .6rem; }

.practice-choice {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .45rem .6rem;
  border: 1px solid var(--line);
  border-radius: 9px;
  margin-bottom: .35rem;
  cursor: pointer;
}
.practice-choice:hover { border-color: var(--accent); }

/* On the way back, the answer is marked whether or not it was picked, and
   what was picked is marked only when it was wrong. Getting it right needs
   no explanation of what you did instead. */
.practice-choice.is-answer { border-color: var(--good, #30a46c); }
.practice-choice.is-chosen { border-color: var(--bad, #e5484d); }

.practice-choice .tag {
  margin-left: auto;
  font-size: .72rem;
  color: var(--muted);
}

.practice-why {
  margin: .5rem 0 0;
  padding-left: .7rem;
  border-left: 3px solid var(--accent);
  font-size: .88rem;
  color: var(--muted);
}

.practice-q.was-right { border-left: 3px solid var(--good, #30a46c); }
.practice-q.was-wrong { border-left: 3px solid var(--bad, #e5484d); }

/* Working the student sent to the teacher, shown in their own turn. */
.working-sent {
  display: block;
  max-width: min(100%, 460px);
  margin-top: .5rem;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: #fff;
}

/* The send button says it is carrying something. */
#send.has-working::after {
  content: "✎";
  margin-left: .3rem;
  font-size: .85em;
}

/* Lesson notes on the notes page, with the formatting the student gave them.
   pre-wrap is dropped for these: the line breaks are markup now, and keeping
   both would double every gap. */
.note-body.rich { white-space: normal; }
.note-body.rich ul,
.note-body.rich ol { margin: .3rem 0 .3rem 1.2rem; padding: 0; }
.note-body.rich p { margin: 0 0 .5rem; }
.note-body.rich div { margin-bottom: .2rem; }

/* The offer to sit a quiz again, on the result page. Loud after a poor mark,
   quiet after a good one: a student who did well should see it and not be
   nagged by it. */
.retake-offer { border-left: 3px solid var(--accent); }
.retake-offer p { margin: 0 0 .4rem; }
.retake-offer.quiet { border-left-color: var(--line); opacity: .85; }

/* ------------------------------------------------------ flagged figures */

.figure-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.4rem;
  align-items: start;
}
@media (max-width: 900px) {
  .figure-columns { grid-template-columns: 1fr; }
}

.flag { margin-bottom: .8rem; border-left: 3px solid var(--line); }
/* Waiting is the state worth seeing from across the page. */
.flag.new { border-left-color: #b7791f; }
.flag.done { border-left-color: var(--good, #30a46c); opacity: .82; }
.flag-reason { margin: .4rem 0; font-size: .95rem; }

.guidance pre {
  white-space: pre-wrap;
  font: inherit;
  font-size: .88rem;
  line-height: 1.5;
  margin: 0;
}

/* One row on the Today page, wherever it appears — the day itself, work
   carried over, or tomorrow offered early. They used to be three layouts
   with the same information in them. */
.today-row {
  padding: .65rem 0;
  border-bottom: 1px solid var(--line);
  gap: 1rem;
  align-items: center;
}
.today-row:last-child { border-bottom: none; }
.today-row-do {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex: 0 0 auto;
}

/* --------------------------------------------------------------- calendar */

.cal-head { align-items: flex-start; gap: 1rem; flex-wrap: wrap; }

/* Pills, not a joined bar. Each one is a real address you can bookmark, and
   separate pills read as separate choices — a joined control reads as one
   thing with a slider inside it. */
.cal-views {
  display: flex;
  flex: 0 0 auto;
  gap: .3rem;
  flex-wrap: wrap;
}
.cal-views a {
  padding: .34rem .85rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: .8rem;
  line-height: 1.4;
  color: var(--muted);
  text-decoration: none;
  background: var(--panel);
  transition: border-color .12s ease, color .12s ease, background .12s ease;
}
.cal-views a:hover { border-color: var(--accent); color: var(--accent); }
.cal-views a.on {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}

/* The step arrows take the same shape, so the header reads as one row of
   controls rather than two kinds of button. */
.cal-step .btn.quiet {
  border-radius: 999px;
  padding: .3rem .8rem;
  font-size: .8rem;
}

.cal-step {
  display: flex;
  gap: .35rem;
  margin: .6rem 0 .2rem;
}

/* --------------------------------------------- room for a finger to land */

/* Apple asks for 44px. The honest way to get there is padding on the few
   controls that were genuinely too small, not a min-height on every button
   in the app — that stretched chips, tabs and inline text links into tall
   boxes that no longer sat on their own baselines, which is how the
   calendar and resources pages came to look wrong. */
@media (pointer: coarse) {
  /* Text links pretending to be buttons: sign out, "Sit it again". They are
     one line of text, so the height has to come from padding, and they must
     not wrap — a two-line "Sign out" is 59px tall and reads as a mistake.
     Titles are excluded: a resource title is meant to wrap, and its whole
     row is already a comfortable target. */
  button.link:not(.res-title),
  .link.signout {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    padding: .4rem .55rem;
    white-space: nowrap;
  }

  /* The calendar step arrows, which were 36x30. */
  .cal-step .btn {
    min-height: 40px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* A wide table cannot be made narrow, so it scrolls inside its own box.
   Done on a wrapper, never on the table: display:block on a <table> throws
   away the table layout and the columns stop lining up. */
.table-scroll {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* ------------------------------------------------------------- what it cost */

.costs-head { align-items: center; }

/* Today's figure gets the room, because it is the one being watched. The
   month and the running total sit beside it as context, not as rivals. */
.spend-today {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: 1rem;
  margin-bottom: 1.6rem;
}
@media (max-width: 760px) {
  .spend-today { grid-template-columns: 1fr; }
}

.spend-hero { display: flex; flex-direction: column; justify-content: center; }
.spend-label {
  margin: 0;
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--muted);
}
.spend-figure {
  margin: .1rem 0 .35rem;
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.spend-figure-small { font-size: 1.35rem; font-variant-numeric: tabular-nums; }

.spend-side { display: flex; flex-direction: column; justify-content: center; }
.spend-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.spend-side .spend-row + .meter { margin-bottom: .1rem; }
.spend-side .spend-row:last-child {
  margin-top: .9rem;
  padding-top: .7rem;
  border-top: 1px solid var(--line);
}

.cost-table { width: 100%; border-collapse: collapse; }
.cost-table th, .cost-table td {
  padding: .5rem .4rem;
  text-align: left;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.cost-table tr:last-child td { border-bottom: none; }
.cost-table th {
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
  font-weight: 600;
}
/* Money and counts line up on the right and share a digit width, so two
   figures can be compared by looking rather than by reading. */
.cost-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

.cost-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.2rem;
  align-items: start;
}
@media (max-width: 860px) {
  .cost-columns { grid-template-columns: 1fr; }
}

/* One bar per day for the last fortnight. */
.daybars { list-style: none; margin: 0; padding: 0; }
.daybars li {
  display: grid;
  grid-template-columns: 5.5rem 1fr 4.5rem;
  align-items: center;
  gap: .7rem;
  padding: .3rem 0;
}
.daybars li.now { font-weight: 650; }
.daybar-label { font-size: .82rem; color: var(--muted); }
.daybars li.now .daybar-label { color: var(--ink); }
.daybar-track {
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}
.daybar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  /* A day with a few cents on it should still draw something, or the row
     reads as a day nothing happened. */
  min-width: 2px;
}
.daybars li.now .daybar-fill { background: var(--good, var(--accent)); }
.daybar-cost { text-align: right; font-size: .82rem; font-variant-numeric: tabular-nums; }


/* Costs page: a child's name and their total on one line, so the eye can run
   down the right-hand edge and compare without reading the names. */
.student-cost-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin: 0 0 .2rem;
  font-size: 1rem;
}
.student-cost-head .num { font-variant-numeric: tabular-nums; font-weight: 600; }
.period-switch { margin: -.4rem 0 .8rem; }

/* ---------------------------------------------------------------- costs dashboard
 *
 * Series colours are assigned by student id, never by spend. A child keeps the
 * same colour between visits; sorting the chart by cost and colouring by rank
 * would repaint everybody the moment one of them had a busy afternoon.
 *
 * The eight-hue order is a validated categorical palette - worst adjacent CVD
 * separation 9.1 (light) / 8.4 (dark) in OKLab x100, against a >=8 target. Do
 * not substitute hues by eye; two that look distinct to you can be the same
 * colour to a colourblind reader.
 */
:root {
  --series-1: #2a78d6;   /* blue   */
  --series-2: #eb6834;   /* orange */
  --series-3: #1baf7a;   /* aqua   */
  --series-4: #eda100;   /* yellow */
  --series-none: #97a1b0;  /* deliberately grey: not a child, not an identity */
}
@media (prefers-color-scheme: dark) {
  :root {
    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-none: #6b7686;
  }
}

.dash-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: .75rem;
  margin-bottom: 1rem;
}
.tile { background: var(--panel); border: 1px solid var(--line);
        border-radius: var(--radius); padding: .8rem .9rem; }
.tile .tile-label { color: var(--muted); font-size: .78rem; text-transform: uppercase;
                    letter-spacing: .04em; }
.tile .tile-value { font-size: 1.5rem; font-weight: 650; font-variant-numeric: tabular-nums;
                    line-height: 1.25; }
.tile .tile-note { color: var(--muted); font-size: .78rem; }
.tile.is-over .tile-value { color: var(--bad); }

/* Budget meter. A single measure against a cap, so a bar, not a chart. */
.meter { height: 10px; border-radius: 5px; background: var(--line);
         overflow: hidden; margin: .45rem 0 .3rem; }
.meter > span { display: block; height: 100%; border-radius: 5px;
                background: var(--accent); }
.meter.is-over > span { background: var(--bad); }

/* Horizontal bars: magnitude by identity. 4px rounded data-end, flat at the
   baseline, so the end that carries the value is the end that is shaped. */
.bar-row { display: grid; grid-template-columns: 1fr auto; gap: .15rem .75rem;
           align-items: baseline; margin: .55rem 0; }
.bar-row .bar-name { font-size: .88rem; }
.bar-row .bar-value { font-variant-numeric: tabular-nums; font-size: .88rem; font-weight: 600; }
.bar-track { grid-column: 1 / -1; height: 14px; background: var(--line);
             border-radius: 0 4px 4px 0; }
.bar-fill { height: 100%; border-radius: 0 4px 4px 0; min-width: 2px; display: block; }

/* Stacked daily columns. The 2px gap between segments is the surface showing
   through - without it two adjacent colours read as one longer block. */
.stack-chart { display: flex; align-items: flex-end; gap: 3px; height: 150px;
               padding-top: .4rem; }
.stack-col { flex: 1 1 0; display: flex; flex-direction: column-reverse;
             justify-content: flex-start; height: 100%; min-width: 0; gap: 2px; }
.stack-seg { width: 100%; min-height: 2px; }
.stack-col > .stack-seg:last-child { border-radius: 4px 4px 0 0; }
.stack-empty { height: 2px; background: var(--line); border-radius: 2px; margin-top: auto; }
.stack-axis { display: flex; gap: 3px; margin-top: .35rem; }
.stack-axis > span { flex: 1 1 0; text-align: center; font-size: .68rem;
                     color: var(--muted); min-width: 0; overflow: hidden;
                     white-space: nowrap; }

/* Legend. Present whenever there is more than one series, so identity is
   never carried by colour alone. */
.legend { display: flex; flex-wrap: wrap; gap: .3rem .9rem; margin: .6rem 0 0; }
.legend span { display: inline-flex; align-items: center; gap: .35rem;
               font-size: .8rem; color: var(--muted); }
.legend i { width: 10px; height: 10px; border-radius: 3px; flex: none; }

.ahead-head { margin: 1.4rem 0 .5rem; font-size: 1.02rem; color: var(--muted); font-weight: 600; }
