/*
 * Classivo application styles.
 *
 * Tokens are copied from web/tailwind.config.js — brand is a terracotta coral
 * (#E4735C), mist is a sage/dusty blue, accent is a warm gold (#F1C45E), and
 * the ground is warm cream (#FCF5EC). Do not invent replacements; the app
 * sits beside the marketing site and any drift is obvious.
 *
 * Contrast note: --accent is used BOTH as a link colour and as the fill of
 * .btn-primary, so it cannot be the bright coral. brand-500 on cream is
 * 2.8:1 and white on brand-500 is 3.0:1 — both fail. brand-700 (#AC4332)
 * clears 5.8:1 in both directions and still reads terracotta. The bright
 * coral stays available as --brand-500 for rules, dots and tints, which are
 * graphics rather than text.
 *
 * Dark theme is a full second token set, not a filter. Colours are chosen for
 * dark backgrounds rather than derived from the light ones: brand-700 is
 * unreadable on near-black, so dark mode steps up to brand-300.
 */

/* ---------------------------------------------------------------------------
 * Webfonts, self-hosted.
 *
 * Playfair Display (display serif) and Inter (UI sans), both SIL OFL 1.1 and
 * both variable across the 400-700 weight axis, so one file covers every
 * weight either family is used at.
 *
 * NOT linked from fonts.googleapis.com: the app sends `font-src 'self'` and
 * would block it, and keeping visitor IPs away from a third party matters for
 * a product that sells on its privacy posture. See assets/fonts/README.txt.
 *
 * latin-ext downloads only when a page actually contains a character in its
 * range, so English pages pay for latin alone.
 * ------------------------------------------------------------------------ */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/playfair-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/playfair-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/playfair-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/playfair-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

:root {
  --ink-50:#F5F6F8; --ink-100:#EAECF0; --ink-200:#D5D9E0; --ink-300:#B0B7C3;
  --ink-400:#838C9D; --ink-500:#616B7D; --ink-600:#4B5566; --ink-700:#3C4553;
  --ink-800:#2F3743; --ink-900:#232B36; --ink-950:#151C25;

  --brand-50:#FDF5F2; --brand-100:#FAE7E0; --brand-200:#F5CEC2; --brand-300:#EFAE9C;
  --brand-400:#E98F79; --brand-500:#E4735C; --brand-600:#C64F39; --brand-700:#AC4332;
  --brand-800:#8B382B; --brand-900:#733126; --brand-950:#3E1711;

  --mist-50:#F2F8FA; --mist-100:#DFEDF1; --mist-200:#C3DEE6; --mist-300:#9CC6D3;
  --mist-400:#6FA7B9; --mist-500:#4F8A9E; --mist-600:#417185; --mist-700:#3A5D6C;
  --mist-800:#364D59; --mist-900:#31424C; --mist-950:#1D2A32;

  --accent-50:#FEF8EC; --accent-100:#FBEECB; --accent-200:#F7DC98; --accent-300:#F1C45E;
  --accent-500:#D9901D; --accent-600:#BC6F16; --accent-700:#9B5115; --accent-800:#7F4018;

  /* Semantic layer — everything below uses these, never the scales directly,
     so the dark theme is one block of overrides rather than a hunt. */
  --bg:            #FCF5EC;
  --bg-raised:     #FFFFFF;
  --bg-sunken:     #F6EFE5;
  --border:        #E7DED2;
  --border-strong: #D6C9B8;
  --text:          var(--ink-600);
  --text-strong:   var(--ink-900);
  --text-muted:    var(--ink-400);
  --accent:        var(--brand-700);
  --accent-hover:  var(--brand-800);
  --accent-soft:   var(--brand-50);
  --accent-soft-t: var(--brand-800);
  --eyebrow:       var(--mist-600);
  --on-accent:     #FFFFFF;
  --warn-bg:       var(--accent-50);
  --warn-br:       var(--accent-100);
  --warn-tx:       var(--accent-800);
  --bad-bg:#FDF2F2; --bad-br:#FBD5D5; --bad-tx:#9B1C1C;
  /* Tinted counter tiles: A is the sage blue, B the warm gold. Deliberately
     NOT coral — coral is the action colour, and a tile is not an action. */
  --tile-a-bg: var(--mist-50);    --tile-a-br: var(--mist-200);   --tile-a-tx: var(--mist-700);
  --tile-b-bg: var(--accent-50);  --tile-b-br: var(--accent-100); --tile-b-tx: var(--accent-700);

  --radius:16px;
  --shadow-card: 0 1px 2px rgba(84,56,42,.05), 0 4px 12px rgba(84,56,42,.07);
  --shadow-lift: 0 2px 4px rgba(84,56,42,.05), 0 12px 32px rgba(84,56,42,.12);
  --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI Variable Text",
               "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Playfair Display", ui-serif, Georgia, Cambria, "Times New Roman", serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Without this, a browser on a dark OS renders native chrome (scrollbars,
     checkboxes, date pickers) in ITS dark style even on this light page —
     theme.js now always defaults data-theme to light, but that only controls
     colours THIS stylesheet draws, not the browser's own widgets. The <meta
     name="color-scheme"> tag in ui.php says both are available; this is what
     actually pins the default to light for the ones CSS doesn't paint. */
  color-scheme: light;
}

[data-theme="dark"] {
  --bg:            #151C25;
  --bg-raised:     #1E2732;
  --bg-sunken:     #10161E;
  --border:        #2C3745;
  --border-strong: #3D4B5C;
  --text:          #C8D0DB;
  --text-strong:   #F3F5F8;
  --text-muted:    #7F8A9A;
  --accent:        var(--brand-300);
  --accent-hover:  var(--brand-200);
  --accent-soft:   #3A2019;
  --accent-soft-t: var(--brand-300);
  --eyebrow:       var(--mist-300);
  --on-accent:     #2A100B;
  --warn-bg:#2E230F; --warn-br:#4E3D18; --warn-tx:#F1C45E;
  --bad-bg:#2A1414; --bad-br:#4A2020; --bad-tx:#F19B9B;
  --tile-a-bg:#16303A; --tile-a-br:#274A57; --tile-a-tx: var(--mist-300);
  --tile-b-bg:#2E230F; --tile-b-br:#4E3D18; --tile-b-tx: var(--accent-300);
  --shadow-card: 0 1px 2px rgba(0,0,0,.3), 0 4px 12px rgba(0,0,0,.25);
  --shadow-lift: 0 2px 4px rgba(0,0,0,.3), 0 12px 32px rgba(0,0,0,.45);
  color-scheme: dark;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; min-height: 100%;
  font: 15px/1.6 var(--font-sans);
  color: var(--text); background: var(--bg);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  transition: background .18s ease, color .18s ease;
}
h1, h2, h3 { margin: 0; color: var(--text-strong); font-weight: 600; letter-spacing: -.02em; text-wrap: balance; }
/* Page titles only. h2/h3 are section and card labels and stay in the sans —
   this is a tool people read all day, not a landing page. */
h1 { font-size: 1.55rem; font-family: var(--font-display); font-weight: 600; letter-spacing: -.015em; }
h2 { font-size: 1.0625rem; }
a { color: var(--accent); text-underline-offset: 2px; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
::selection { background: var(--brand-200); color: var(--brand-950); }
img, svg { max-width: 100%; }

/* --- Chrome -------------------------------------------------------------- */

.topbar {
  display: flex; align-items: center; gap: 1rem;
  padding: .7rem 1.5rem;
  background: var(--bg-raised); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 30;
}
.brand { display: inline-flex; align-items: center; gap: .55rem; text-decoration: none; flex: none; }
.brand svg, .brand img { width: 26px; height: 26px; border-radius: 7px; display: block; }
.brand b { font-weight: 700; font-size: 1.02rem; letter-spacing: -.03em; color: var(--text-strong); }
.brand b i { font-style: normal; color: var(--accent); }

.nav { display: flex; gap: .15rem; flex: 1; flex-wrap: wrap; min-width: 0; }
.nav a {
  padding: .42rem .75rem; border-radius: 9px; text-decoration: none;
  color: var(--text); font-weight: 500; font-size: .89rem; white-space: nowrap;
}
.nav a:hover { background: var(--bg-sunken); color: var(--text-strong); }
.nav a[aria-current] { background: var(--accent-soft); color: var(--accent-soft-t); }

.bar-end { display: flex; align-items: center; gap: .5rem; flex: none; }
.who { font-size: .82rem; color: var(--text-muted); white-space: nowrap; }

/* Theme toggle */
.theme-btn {
  width: 34px; height: 34px; padding: 0; display: grid; place-items: center;
  border: 1px solid var(--border); border-radius: 9px; background: var(--bg-raised);
  color: var(--text-muted); cursor: pointer;
}
.theme-btn:hover { color: var(--text-strong); border-color: var(--border-strong); }
.theme-btn svg { width: 17px; height: 17px; }
[data-theme="dark"] .theme-btn .i-sun { display: block; }
[data-theme="dark"] .theme-btn .i-moon { display: none; }
.theme-btn .i-sun { display: none; }
.theme-btn .i-moon { display: block; }

/* Mobile nav */
.nav-toggle { display: none; }
@media (max-width: 860px) {
  .topbar { flex-wrap: wrap; padding: .7rem 1rem; gap: .6rem; }
  .nav-toggle {
    display: grid; place-items: center; margin-left: auto; order: 2;
    width: 36px; height: 36px; border: 1px solid var(--border); border-radius: 9px;
    background: var(--bg-raised); color: var(--text); cursor: pointer;
  }
  .bar-end { order: 3; }
  .nav {
    order: 4; flex-basis: 100%; flex-direction: column; gap: .1rem;
    display: none; padding-top: .4rem; border-top: 1px solid var(--border); margin-top: .3rem;
  }
  .nav.open { display: flex; }
  .nav a { padding: .6rem .75rem; }
  .who { display: none; }
}

/* Fluid, not a single fixed number — user decision, 2026-09-30, one step past
   the previous fix. A flat 88rem (1408px) was wider than the old 74rem, but
   still either a cap that wastes space on a genuinely large monitor or, if
   raised further, more than a laptop screen needs. clamp() scales the cap
   with the viewport instead of picking one width for every screen:
     - never narrower than 74rem — the ORIGINAL value becomes the floor, so
       nothing regresses past where this started
     - grows at 90% of the viewport on ordinary screens, so a 1280px laptop
       and a 1920px desktop each get a width proportional to what they have
     - never wider than 104rem (1664px) — a ceiling, not a suggestion: past
       this a five-column board row or a wide table gets HARDER to scan, not
       easier, so more monitor should not keep buying more width forever
   Below 640px this is moot — .wrap has no explicit width, so it already
   shrinks to fit a narrow viewport regardless of what max-width allows; the
   media query below only adjusts padding, exactly as before.
   Sitewide, not board-specific: .wrap is the one container every page
   renders through via page_top()/page_end(). staff.php's one form card
   still self-limits to 34rem independent of this, so a single-column form
   does not end up floating in extra width on a wide monitor either. */
/* width: 100% is NOT decorative here — found by direct measurement on the
   live board, 2026-09-30. .wrap is a flex item of `body` (the "page has a
   bottom" rule below makes body a column flex container so a short page
   still pushes the footer down), and it relies on flexbox's cross-axis
   "stretch" to reach this max-width in the first place. That stretch was
   silently NOT filling the available width once .wrap contained a
   position:sticky child (.bd-sticky, the board's header) — measured stuck at
   808px against a 1664px max-width on a 1920px screen, no matter how wide
   the clamp() above allowed it to go. Forcing width:100% makes the box fill
   its container explicitly instead of depending on that stretch resolving
   correctly, and margin:0 auto still centers it once max-width caps it. */
.wrap { width: 100%; max-width: clamp(74rem, 90vw, 104rem); margin: 0 auto; padding: 1.75rem 1.5rem 5rem; }
@media (max-width: 640px) { .wrap { padding: 1.25rem 1rem 4rem; } }
.head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 1rem; margin-bottom: 1.5rem; }
.sub { color: var(--text-muted); font-size: .9rem; margin: .3rem 0 0; }
/* Sage, not coral, and deliberately so: coral is the action colour here and
   on the marketing site, and an eyebrow is a label, not something to click. */
.eyebrow { font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--eyebrow); margin: 0 0 .35rem; }

/* --- Notices ------------------------------------------------------------- */

.banner { padding: .7rem 1.5rem; font-size: .86rem; border-bottom: 1px solid var(--warn-br); background: var(--warn-bg); color: var(--warn-tx); }
.note { border-radius: 12px; padding: .85rem 1.1rem; font-size: .88rem; margin-bottom: 1.25rem; border: 1px solid; }
.note-ok   { background: var(--accent-soft); color: var(--accent-soft-t); border-color: transparent; }
.note-warn { background: var(--warn-bg); color: var(--warn-tx); border-color: var(--warn-br); }
.note-bad  { background: var(--bad-bg);  color: var(--bad-tx);  border-color: var(--bad-br); }

/* --- Cards, tables, forms ------------------------------------------------ */

.card { background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-card); }
.pad { padding: 1.25rem 1.5rem; }

/* A slim, on-brand scrollbar for a table that overflows its card — the OS's
   own full-width scrollbar reads as a stray UI element sitting under a table. */
.table-wrap {
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
.table-wrap::-webkit-scrollbar { height: 7px; }
.table-wrap::-webkit-scrollbar-track { background: transparent; }
.table-wrap::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 999px; }
.table-wrap::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; font-size: .69rem; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); font-weight: 700; padding: .8rem 1.25rem; border-bottom: 1px solid var(--border); white-space: nowrap; }
td { padding: .8rem 1.25rem; border-bottom: 1px solid var(--border); font-size: .9rem; color: var(--text); }
tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--bg-sunken); }
td strong { color: var(--text-strong); font-weight: 600; }

/*
 * On a phone a six-column table is unusable. Rather than scroll it sideways,
 * each row becomes a stacked block with its column name as a label. The labels
 * come from data-l attributes in the markup so there is no duplicated header.
 */
@media (max-width: 760px) {
  .stacked thead { display: none; }
  .stacked, .stacked tbody, .stacked tr, .stacked td { display: block; width: 100%; }
  .stacked tr { border-bottom: 1px solid var(--border); padding: .35rem 0; }
  .stacked tr:last-child { border-bottom: 0; }
  .stacked td { border: 0; padding: .3rem 1rem; display: flex; gap: 1rem; align-items: baseline; }
  .stacked td::before {
    content: attr(data-l); flex: none; width: 6.5rem;
    font-size: .68rem; text-transform: uppercase; letter-spacing: .07em;
    color: var(--text-muted); font-weight: 700;
  }
  .stacked td:empty { display: none; }
  .stacked td.actions { justify-content: flex-start; padding-top: .5rem; padding-bottom: .7rem; }
  .stacked td.actions::before { content: ""; width: 6.5rem; }
}

label { display: block; font-size: .82rem; font-weight: 600; color: var(--text-strong); margin-bottom: .35rem; }
input[type=text], input[type=email], input[type=password], input[type=search],
input[type=tel], input[type=date], input[type=time], select, textarea {
  width: 100%; padding: .6rem .8rem; font: inherit; font-size: .9rem;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-raised); color: var(--text-strong);
}
input::placeholder { color: var(--text-muted); }
input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: transparent; }
.field { margin-bottom: 1rem; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
@media (max-width: 700px) { .grid2 { grid-template-columns: 1fr; } }
.grid2 > .field-wide { grid-column: 1 / -1; }
.opt { font-weight: 400; color: var(--text-muted); }
/* A label for a group of controls, not for one input — a <label> would point at
   nothing. Looks exactly like the labels above. */
.lbl { display: block; font-size: .82rem; font-weight: 600; color: var(--text-strong); margin-bottom: .35rem; }

/* Day-of-week picker: seven checkbox pills. The real checkbox stays in the page
   (visually hidden, not display:none) so it keeps its keyboard and screen-reader
   behaviour; the span next to it is what is drawn. Selected is carried by fill AND
   a tick, not by colour alone. */
.daypick { display: flex; flex-wrap: wrap; gap: .4rem; }
.daychip { position: relative; display: inline-block; margin: 0; font-weight: 600; cursor: pointer; }
.daychip input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.daychip span {
  display: inline-block; min-width: 3.1rem; text-align: center; padding: .42rem .7rem;
  border: 1px solid var(--border-strong); border-radius: 999px; background: var(--bg-raised);
  color: var(--text); font-size: .82rem; transition: background .12s, border-color .12s, color .12s;
}
.daychip:hover span { border-color: var(--accent); }
.daychip input:checked + span { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.daychip input:checked + span::before { content: "\2713\00a0"; }
.daychip input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  padding: .58rem 1.05rem; border-radius: 10px; border: 1px solid transparent;
  font: inherit; font-size: .89rem; font-weight: 600; cursor: pointer; text-decoration: none;
  transition: background .15s, border-color .15s, color .15s;
}
.btn-primary { background: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-ghost { background: var(--bg-raised); border-color: var(--border); color: var(--text); }
.btn-ghost:hover { border-color: var(--border-strong); color: var(--text-strong); }
.btn-sm { padding: .35rem .7rem; font-size: .8rem; border-radius: 8px; }

/* The roster's check-in / check-out button while a press is in flight, and for
   a few seconds after it lands (app.js, "The 5-second lock"). In flight it is
   merely dimmed; locked it is plainly NOT pressable — sunken, muted, padlock and
   countdown beside the label — so nobody takes the flipped label for an
   invitation to press again. */
[data-toggle] .btn:disabled { opacity: .7; cursor: wait; }
[data-toggle] .btn[data-locked] {
  opacity: 1; cursor: not-allowed;
  background: var(--bg-sunken); border-color: var(--border); color: var(--text-muted);
}
.btn .lock-ico { display: inline-flex; flex: none; }
.btn .lock-t { min-width: 1.5em; font-size: .75rem; font-variant-numeric: tabular-nums; text-align: left; }

/* Destructive. Deliberately not the accent colour: the affirmative button in a
   confirmation is the one that deletes something, and it should not look like
   the button people press all day to save things. */
.btn-danger { background: var(--bad-tx); color: var(--bad-bg); border-color: transparent; }
.btn-danger:hover { filter: brightness(1.1); }

.tag { display: inline-block; padding: .2rem .55rem; border-radius: 999px; font-size: .72rem; font-weight: 600; white-space: nowrap; }
.tag-in  { background: var(--accent-soft); color: var(--accent-soft-t); }
.tag-out { background: var(--bg-sunken); color: var(--text-muted); }
/* On site, but past their enrolled time (visit_is_over()). The name in the same
   row turns the same red through :has(), so the row is marked by the one tag and
   the two cannot disagree — including after a check-out swaps this cell's markup. */
.tag-over { background: var(--bad-bg); color: var(--bad-tx); border: 1px solid var(--bad-br); }
tr:has(.tag-over) .who-cell strong { color: var(--bad-tx); }
.code { font-family: var(--font-mono); font-size: .8rem; color: var(--text-muted); }

/* --- Live board's shared bits ---------------------------------------------
   .people / .person / .avatar (bare) are NOT board-only any more — staff.php's
   on-the-clock strip (on_clock_html()) uses all three, so they stay here even
   though the board itself now renders its own list under the bd- prefix
   below. The board's own layout, counters and feed rules were here and have
   moved to "THE LIVE BOARD, FOURTH PASS" at the end of this file. */

.people { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: .85rem; }
@media (max-width: 480px) { .people { grid-template-columns: 1fr 1fr; } }

.person {
  background: var(--bg-raised); border: 1px solid var(--border); border-left: 3px solid var(--brand-500);
  border-radius: 14px; padding: .85rem .95rem; box-shadow: var(--shadow-card);
  display: flex; gap: .75rem; align-items: center; animation: fade-up .3s ease-out;
}
@keyframes fade-up { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.avatar {
  width: 40px; height: 40px; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-soft-t); font-weight: 700; font-size: .8rem;
}
.person .nm { font-weight: 600; font-size: .92rem; line-height: 1.3; color: var(--text-strong); }
.person .mt { color: var(--text-muted); font-size: .76rem; margin-top: .1rem; }

.empty { padding: 3rem 1rem; text-align: center; color: var(--text-muted); font-size: .9rem; }
.stat { display: flex; gap: 2rem; }
.stat b { display: block; font-size: 1.75rem; font-weight: 700; letter-spacing: -.03em; color: var(--text-strong); }
.stat span { font-size: .7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .08em; }

/* --- Check-In Station ---------------------------------------------------- */
/*
 * Always dark: it runs full screen on a wall or a tablet by the door, often in
 * a dim entrance, and a white screen at that size is glare. It does not follow
 * the theme toggle for that reason.
 */

.station {
  position: relative;          /* containing block for .scan-capture */
  min-height: 100dvh; width: 100%; max-width: 100%; overflow-x: hidden;
  display: flex; flex-direction: column;
  background: radial-gradient(120% 90% at 50% 0%, #16243a 0%, #0A0E16 60%);
  color: #fff; padding: 1.5rem;
}
.station-bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.station-bar .brand b { color: #fff; }
.station-bar .where { font-size: .85rem; color: #7C8798; text-align: right; }
.station-main { flex: 1; display: grid; place-items: center; padding: 2rem 0; }
.station-inner { position: relative; width: 100%; max-width: 32rem; text-align: center; }

.scan-target {
  width: min(58vw, 210px); aspect-ratio: 1; margin: 0 auto 2rem;
  border-radius: 28px; position: relative; display: grid; place-items: center;
  background: rgba(255,255,255,.03); border: 2px dashed rgba(255,255,255,.14);
}
.scan-target svg { width: 46%; height: 46%; color: var(--brand-400); opacity: .9; }
.scan-target::after {
  content: ""; position: absolute; left: 12%; right: 12%; height: 2px;
  background: linear-gradient(90deg, transparent, var(--brand-400), transparent);
  animation: sweep 2.4s ease-in-out infinite; border-radius: 2px;
}
@keyframes sweep { 0%,100% { top: 20%; opacity: .25; } 50% { top: 78%; opacity: .9; } }

/* Camera scan mode: the same dashed circle, filled with the live feed
   instead of the static icon — the sweep line still plays over it. Not
   mirrored: this reads a badge held up to the camera, not a selfie. */
.scan-target-cam { overflow: hidden; background: #000; }
.scan-target-cam video { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

.station h1 { color: #fff; font-size: clamp(1.35rem, 4vw, 1.9rem); }
.station .hint { color: #7C8798; font-size: .95rem; margin-top: .5rem; }

/*
 * The scanner types into this. A real, focusable input — just not one anybody
 * is invited to use, because the station is scanner-only.
 *
 * Pinned to the top-left of its RELATIVE parent with clip-path, not floated
 * out with a negative margin. The negative-margin recipe leaves a 1px box
 * outside the layout, which browsers count in scrollWidth — that is what put a
 * phantom extra page to the side of the station.
 */
.scan-capture {
  position: absolute; top: 0; left: 0;
  width: 1px; height: 1px; padding: 0; margin: 0; border: 0;
  overflow: hidden; white-space: nowrap;
  clip-path: inset(50%); opacity: 0; pointer-events: none;
}

.result {
  margin-top: 1.5rem; border-radius: 20px; padding: clamp(1.25rem, 4vw, 2rem);
  animation: fade-up .28s ease-out; display: flex; align-items: center; gap: 1.1rem;
  text-align: left;
}
.result .ico { width: 52px; height: 52px; flex: none; border-radius: 50%; display: grid; place-items: center; background: rgba(255,255,255,.16); }
.result .ico svg { width: 26px; height: 26px; }
.result-in  { background: var(--brand-600); }
.result-out { background: #2B3546; }
.result-bad { background: #8C2222; }
.result .big { overflow-wrap: anywhere; font-size: clamp(1.15rem, 3.6vw, 1.55rem); font-weight: 700; letter-spacing: -.02em; }
.result .small { opacity: .85; font-size: .88rem; margin-top: .15rem; }

.station-foot { text-align: center; font-size: .78rem; color: #4A5568; }
.station-foot a { color: #7C8798; }

/* --- Badges (QR) ---------------------------------------------------------
   A badge is an Avery 5160 LABEL: 1in x 2-5/8in, 30 to a Letter sheet, 3 across
   and 10 down — so it prints onto sticker stock and goes on a student's folder.
   Every size below is a multiple of --u (1in on paper), so the same markup can be
   drawn larger on screen by changing one variable instead of redoing the sums.
   The QR is the biggest thing on it on purpose; see app/src/badge-view.php. */

.badge-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); gap: 1.25rem 1rem; justify-items: center; }
/* On screen the sheets are invisible wrappers, so the cells flow in one grid. In
   print each one becomes a page — see the @media print block below. */
.badge-sheet { display: contents; }
.badge-cell { display: flex; flex-direction: column; align-items: center; gap: .55rem; }

.badge {
  --u: 1in;
  box-sizing: border-box; display: flex; align-items: center; text-align: left;
  gap: calc(var(--u) * .09);
  width: calc(var(--u) * 2.625); height: var(--u);
  padding: calc(var(--u) * .06) calc(var(--u) * .1);
  background: #fff; border: 1px solid var(--border); border-radius: calc(var(--u) * .07);
  box-shadow: var(--shadow-card); break-inside: avoid; overflow: hidden;
}
/* Badges stay light even in dark mode: they are printed, and a dark QR card
   wastes toner and scans worse. */
[data-theme="dark"] .badge { background: #fff; border-color: #D5D9E0; }
[data-theme="dark"] .badge .nm { color: #151C25; }
[data-theme="dark"] .badge .mt, [data-theme="dark"] .badge .org { color: #616B7D; }
.badge .qr { flex: none; width: calc(var(--u) * .86); height: calc(var(--u) * .86); display: block; }
.badge .qr svg { width: 100%; height: 100%; display: block; }
.badge .qr-none { display: block; font-size: calc(var(--u) * .09); line-height: 1.25; color: var(--ink-500); text-align: center; padding-top: calc(var(--u) * .3); }
.badge .bt { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: calc(var(--u) * .025); }
.badge .nm {
  font-weight: 700; color: var(--ink-950); font-size: calc(var(--u) * .15); line-height: 1.12;
  /* Two lines, then an ellipsis: a long surname must not push the ID off the label. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere;
}
.badge .mt { color: var(--ink-500); font-size: calc(var(--u) * .1); line-height: 1.15; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.badge .mt:empty { display: none; }
.badge .id { font-family: var(--font-mono); font-size: calc(var(--u) * .15); font-weight: 700; color: var(--brand-700); letter-spacing: .06em; line-height: 1.1; }
.badge .org { font-size: calc(var(--u) * .075); color: var(--ink-400); text-transform: uppercase; letter-spacing: .08em; line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The Students screen shows one badge in a dialog: drawn larger so the QR can be
   judged by eye, but still the same label. The min() keeps it inside a phone. */
.modal .badge { --u: min(1.3in, calc((100vw - 5.5rem) / 2.625)); margin: 0 auto; }

/* Print / Download under each badge. Kept quiet — they are on every badge on
   the sheet, so at full weight they would out-shout the QR codes themselves. */
.badge-actions { display: flex; gap: .4rem; justify-content: center; flex-wrap: wrap; }

/* The badges page header: the buttons stay a compact group at the right of the title
   (they wrap under it only when the screen is genuinely too narrow), and the how-to-print
   lines sit below, full width. */
.head-actions { flex: none; }
/* The title column gives way first: its description is a long line, and left at its natural
   width it never fit beside the buttons, so they dropped below the heading. */
.head-main { flex: 1 1 24rem; min-width: 0; }
.badge-hints { margin: -.75rem 0 1.25rem; }
.badge-hints .sub { margin: 0 0 .25rem; }
.badge-hints .sub + .sub { font-size: .78rem; }

/* An optional line switched off in the settings preview. Explicit, so nothing in
   the badge's own rules can out-rank the [hidden] attribute. */
.badge [hidden] { display: none !important; }

/* The type colour: a band down the badge's left edge. The palette is the site's own
   (the logo's coral, spruce and sky, and the gold and amber beside them), taken from
   the scale tokens so it can never drift from the rest of the product. Keep this list
   in step with BADGE_COLORS in app/src/badges.php. */
.bc-coral  { background: var(--brand-500); }
.bc-rust   { background: var(--brand-800); }
.bc-amber  { background: var(--accent-500); }
.bc-gold   { background: var(--accent-300); }
.bc-sky    { background: var(--mist-400); }
.bc-spruce { background: var(--mist-700); }
.bc-blush  { background: var(--brand-300); }
.bc-slate  { background: var(--ink-500); }
.badge { position: relative; }
.badge .bband {
  position: absolute; left: calc(var(--u) * .045); top: calc(var(--u) * .045); bottom: calc(var(--u) * .045);
  width: calc(var(--u) * .075); border-radius: calc(var(--u) * .03);
}
.badge .bband.bc-none { display: none; }
/* Room for the band, so the QR does not sit against it. */
.badge.has-band { padding-left: calc(var(--u) * .19); }

/* --- Badge settings dialog ------------------------------------------------- */

/* A square button holding only an icon (the gear). Same height as its neighbours. */
.icon-btn { padding-inline: .7rem; }
.icon-btn svg { width: 1.15rem; height: 1.15rem; display: block; }
.icon-btn[hidden] { display: none; }

dialog.modal.modal-md { max-width: 30rem; max-height: calc(100vh - 2rem); overflow-y: auto; }

/* The live preview: a sunken tray so the white label reads as a label. */
.bs-preview {
  display: flex; justify-content: center; padding: 1.1rem .75rem; margin: 0 0 1.1rem;
  background: var(--bg-sunken); border: 1px solid var(--border); border-radius: 12px;
}

/* A checkbox with a title and a one-line explanation. The whole row is the click
   target, so ticking one does not mean aiming at a 1rem box. */
.check-row {
  display: flex; align-items: flex-start; gap: .75rem; margin: 0; padding: .7rem .15rem;
  font-weight: 400; cursor: pointer; border-top: 1px solid var(--border);
}
.check-row:first-of-type { border-top: 0; }
.check-row input { margin-top: .15rem; flex: none; }
.check-row b { display: block; font-size: .88rem; font-weight: 600; color: var(--text-strong); }
.check-row small { display: block; margin-top: .1rem; font-size: .78rem; color: var(--text-muted); line-height: 1.4; }

/* Colour by type, in the badge settings: one row per type, a radio group of swatches. */
.bs-colors { margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.bs-colors h3 { margin: 0 0 .25rem; font-size: .92rem; }
.bs-type { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding: .4rem 0; flex-wrap: wrap; }
.bs-type-name { font-size: .86rem; font-weight: 600; color: var(--text-strong); overflow-wrap: anywhere; }
.swatches { display: flex; gap: .3rem; flex-wrap: wrap; }
/* The real radio stays in the page, invisible, so the group keeps its keyboard
   behaviour (arrow keys) and its screen-reader meaning; the dot beside it is drawn. */
.sw { position: relative; display: inline-block; margin: 0; line-height: 0; cursor: pointer; }
.sw input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.sw-dot {
  display: block; width: 1.45rem; height: 1.45rem; border-radius: 50%; box-sizing: border-box;
  border: 2px solid var(--bg-raised); box-shadow: 0 0 0 1px var(--border-strong);
  transition: box-shadow .12s, transform .12s;
}
.sw-none { background: var(--bg-raised) repeating-linear-gradient(135deg, transparent 0 4px, var(--border-strong) 4px 5px); }
.sw:hover .sw-dot { transform: scale(1.1); }
.sw input:checked + .sw-dot { box-shadow: 0 0 0 2px var(--text-strong); }
.sw input:focus-visible + .sw-dot { outline: 2px solid var(--accent); outline-offset: 3px; }

/* --- Choosing badges to print ---------------------------------------------- */

.bsel-bar {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin: 0 0 1.25rem;
  padding: .65rem .9rem; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 12px;
}
.bsel-bar[hidden] { display: none; }
.bsel-all { display: inline-flex; align-items: center; gap: .55rem; margin: 0; font-size: .86rem; font-weight: 600; cursor: pointer; }
.bsel-bar .sub { margin: 0; }
.bsel-bar .btn { margin-left: auto; }
.bsel-bar .btn:disabled { opacity: .5; cursor: default; }
.bsel-hint { flex-basis: 100%; margin: -.2rem 0 0; font-size: .78rem; }

/* The tick sits on the badge's top-left corner, half outside it, so it never
   covers the QR. Screen only — it is hidden in print along with the rest of the
   chrome, and none of this is inside the .badge element that gets printed. */
.badge-cell { position: relative; }
.bsel {
  position: absolute; top: -.5rem; left: -.5rem; z-index: 2; margin: 0; padding: 3px;
  background: var(--bg-raised); border: 1px solid var(--border-strong); border-radius: 8px; line-height: 0;
}
.bsel[hidden] { display: none; }
.has-badge-select .badge-cell .badge { cursor: pointer; }
@media screen {
  .badge-cell.is-selected .badge { outline: 2px solid var(--accent); outline-offset: 2px; }
}

/* --- The three dots beside a student ---------------------------------------- */

.row-more {
  margin-left: auto; flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; padding: 0; color: var(--text-muted); cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: 8px;
}
.row-more svg { width: 1.1rem; height: 1.1rem; display: block; }
.row-more:hover, .row-more[aria-expanded="true"] { background: var(--bg-sunken); color: var(--text-strong); border-color: var(--border); }
.row-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.row-details { display: none; }

/* The floating card. Fixed, appended to <body> by app.js: the table lives in an
   overflow container that would clip anything hanging off a row near its foot. */
.row-pop {
  position: fixed; z-index: 70; width: min(19rem, calc(100vw - 1rem)); box-sizing: border-box;
  padding: 1rem 1.1rem 1.05rem; background: var(--bg-raised); color: var(--text);
  border: 1px solid var(--border); border-radius: 14px; box-shadow: var(--shadow-lift);
}
.row-pop:focus { outline: none; }
.rd-name { font-size: 1rem; font-weight: 700; color: var(--text-strong); margin: 0 0 .6rem; overflow-wrap: anywhere; }
.rd { display: grid; grid-template-columns: auto 1fr; gap: .3rem .9rem; margin: 0 0 .95rem; font-size: .84rem; }
.rd dt { color: var(--text-muted); }
.rd dd { margin: 0; color: var(--text-strong); overflow-wrap: anywhere; }
.rd-edit { width: 100%; }

/* Fetching a different range. Faded, not emptied: the numbers being replaced
   stay readable, and a chart that blanks for 80ms reads as a fault. */
[data-attendance-results][aria-busy="true"] { opacity: .5; pointer-events: none; transition: opacity .12s ease; }

/* --- Modal --------------------------------------------------------------- */

dialog.modal {
  border: 1px solid var(--border); border-radius: var(--radius); padding: 0;
  background: var(--bg-raised); color: var(--text);
  box-shadow: var(--shadow-lift); max-width: 24rem; width: calc(100vw - 2rem);
}
dialog.modal::backdrop { background: rgba(21,28,37,.55); backdrop-filter: blur(2px); }
/* The student edit dialog. The default 24rem is a phone-sized card, and the form
   inside is a two-column grid with a day picker and two time inputs — it was
   squeezed. Wide enough for that grid, and scrollable so it never outgrows a
   short laptop screen. */
dialog.modal.modal-lg { max-width: 44rem; max-height: calc(100vh - 2rem); overflow-y: auto; }
dialog.modal.modal-lg .modal-body { padding: 1.5rem; }
dialog.modal.modal-lg .modal-head { padding: 1.05rem 1.5rem; position: sticky; top: 0; background: var(--bg-raised); z-index: 1; }
dialog.modal.modal-lg .modal-foot { padding: 1rem 1.5rem; position: sticky; bottom: 0; background: var(--bg-raised); }

/* The confirmation dialog. Replaces window.confirm(), which has no place on a
   customer-facing site — see initConfirm() in app.js. */
.modal-ask .modal-body { padding: 1.35rem 1.35rem 1rem; }
.modal-ask h2 { font-size: 1.02rem; letter-spacing: -.01em; margin: 0 0 .4rem; }
.modal-ask p { margin: 0; color: var(--text-muted); font-size: .88rem; line-height: 1.5; }
.modal-ask .modal-foot { border-top: 0; padding-top: 0; }
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .9rem 1.1rem; border-bottom: 1px solid var(--border); }
.modal-head h2 { font-size: .98rem; }
.modal-body { padding: 1.25rem; }
.modal-foot { display: flex; gap: .5rem; justify-content: flex-end; padding: .85rem 1.1rem; border-top: 1px solid var(--border); flex-wrap: wrap; }
.x { background: none; border: 0; font-size: 1.35rem; line-height: 1; color: var(--text-muted); cursor: pointer; padding: .1rem .3rem; border-radius: 6px; }
.x:hover { color: var(--text-strong); background: var(--bg-sunken); }

/* Payment-successful dialog on /billing (shown after an add-on is bought). The check uses the
   same --tile-a pair as every other "good state" in the app, so it follows the theme. */
.modal-paid .paid-body { text-align: center; padding: 1.75rem 1.5rem 1.1rem; }
.paid-check {
  display: inline-grid; place-items: center; width: 3.1rem; height: 3.1rem; border-radius: 999px;
  background: var(--tile-a-bg); color: var(--tile-a-tx); border: 1px solid var(--tile-a-br); margin-bottom: .85rem;
}
.paid-check svg { width: 1.5rem; height: 1.5rem; }
.modal-paid h2 { font-size: 1.15rem; letter-spacing: -.01em; margin: 0 0 .35rem; }
.paid-lead { margin: 0; color: var(--text); font-size: .95rem; line-height: 1.5; }
.paid-rows { margin: 1.15rem 0 0; border: 1px solid var(--border); border-radius: 12px; text-align: left; overflow: hidden; }
.paid-rows > div { display: flex; justify-content: space-between; gap: 1rem; padding: .7rem .95rem; font-size: .88rem; }
.paid-rows > div + div { border-top: 1px solid var(--border); }
.paid-rows dt { color: var(--text-muted); }
.paid-rows dd { margin: 0; text-align: right; font-weight: 600; color: var(--text-strong); }
.paid-rows dd small { display: block; font-weight: 400; font-size: .78rem; color: var(--text-muted); }
.paid-note { margin: .95rem 0 0; color: var(--text-muted); font-size: .8rem; line-height: 1.5; }
.modal-paid .modal-foot { border-top: 0; padding-top: .35rem; justify-content: center; }

/* Filled from the QR modal at print time and emptied afterwards. Never seen on
   screen; it exists so that printing one badge does not mean printing the whole
   roster around it. */
.print-area { display: none; }

/* The label stock. A named page, so only the badge sheet is forced onto Letter
   with Avery 5160's margins — every other printout keeps whatever paper the
   person's printer is set to. 0.5in top and bottom and 0.1875in at the sides
   leave exactly 3 x 2.625in labels (+ 2 x 0.125in gaps) across and 10 x 1in down. */
@page labels { size: 8.5in 11in; margin: 0.5in 0.1875in; }

@media print {
  .topbar, .banner, .head, .no-print, .btn, .modal-head, .modal-foot, .app-foot { display: none !important; }
  /* Notices are for the screen. Visible, they would print on the label stock; and
     even empty, #nt-banners is an in-flow box in front of the labels. */
  #nt-banners, .nt-banner { display: none !important; }

  /* Label stock for the WHOLE document, not just the sheet inside it. The page
     name used to sit on .badge-sheet / .print-area, and any in-flow sibling in
     front of those (the empty #nt-banners div is enough) has a different page
     name, which makes the browser force a break between them — a blank first
     page, so one badge printed as two pages and a 34-student sheet as three.
     With the name on <html> there is nothing to differ from. The badge sheet
     and the one-badge print are the only two documents that carry it. */
  html:has(.badge-sheet), html.printing-badge { page: labels; }

  /* Printing ONE badge from the modal: everything else goes, including the
     dialog itself — its contents were copied into .print-area precisely because
     top-layer elements cannot be relied on to print. The badge lands on the first
     label of the sheet, so a half-used sheet can be fed back in. */
  .printing-badge .wrap,
  .printing-badge dialog.modal { display: none !important; }
  .printing-badge .print-area { display: block; }
  .print-area .badge { margin: 0; }
  body { background: #fff; }
  /* The drifting pastel field is a screen effect: fixed-position, so it would be
     repeated on every page, and a tint on sticker stock is never wanted. Entrance
     animations are switched off too — a page printed inside their first half
     second would otherwise come out with its content still transparent. */
  body::before { display: none !important; }
  .wrap > *, .badge-grid > *, .people > *, .form-list > * { animation: none !important; }
  /* The app's own "keep the footer at the bottom" rule gives <body> a full-viewport
     min-height; on paper that is a second, blank page after the last sheet. */
  html, body, body:has(.app-foot) { height: auto !important; min-height: 0 !important; display: block !important; }
  .wrap { padding: 0; max-width: none; width: auto; }

  /* One .badge-sheet per page, laid out on the label stock's own grid. */
  .badge-grid { display: block; }
  .badge-sheet {
    display: grid;
    grid-template-columns: repeat(3, 2.625in); column-gap: .125in;
    grid-auto-rows: 1in; row-gap: 0;
    break-after: page;
  }
  .badge-sheet:last-child { break-after: auto; }
  .badge-cell { display: block; }
  /* A thin outline round each badge, so it can be cut out of plain paper and so the
     edge of every label is visible on the page. An OUTLINE, not a border: it takes
     no space, so the layout measured against the label stock is unchanged. Drawn
     0.03in INSIDE the label edge, not on it — a sheet that feeds a hair off would
     otherwise put the line half on the sticker and half on the backing. */
  .badge {
    --u: 1in; box-shadow: none; border: 0; border-radius: .06in; background: #fff; break-inside: avoid;
    outline: .75pt solid #6B6B6B; outline-offset: -.03in;
  }
  /* Chrome's "Background graphics" box is off by default, and a person should not
     have to find it for a badge to print properly. `exact` makes the label — its
     white ground, the QR and its outline — print the same whether the box is
     ticked or not. Scoped to the badge: the page around it keeps the default,
     so ticking the box can never bring the app's own tinted backgrounds back. */
  .badge, .badge * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  /* ...except the type colour. That one is DEFINED as a background, and printing when
     Background graphics is ticked is exactly what it is for: ticked, the band comes out
     in colour; unticked, the badge is plain. (More specific than the rule above.) */
  .badge .bband { -webkit-print-color-adjust: economy; print-color-adjust: economy; }
  /* Whatever theme the screen was in, paper is white: the dark theme's canvas colour
     must not follow the page onto a printed sheet when the box IS ticked. */
  html, html[data-theme="dark"], body, [data-theme="dark"] body { background: #fff !important; }
  /* The dark theme also sets color-scheme: dark, which makes the browser's OWN canvas
     colour dark — and the canvas is what fills a printed page's margins, outside any
     element. Measured: with the dark theme and "Background graphics" ticked, a white
     sheet came out inside a black frame. Paper is light, so print is light. */
  html, html[data-theme="dark"] { color-scheme: light !important; }
  .badge .id { color: #000; }
  dialog.modal { position: static; border: 0; box-shadow: none; max-width: none; width: auto; }
  @page { margin: 12mm; }
}

/* ===========================================================================
   Document Organizer
   ---------------------------------------------------------------------------
   These forms replace paper that gets photocopied and filed. So the parent's
   view is a SHEET: white, edged, finite. Not a texture and not skeuomorphism —
   just the structural fact that a document has boundaries and an end.
   The builder renders the same sheet with affordances on top.
   =========================================================================== */

.fs-sheet {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-card);
  padding: clamp(1.5rem, 5vw, 2.75rem);
}
.fs-sheet-head { margin-bottom: 1.75rem; }
.fs-sheet-head h1 { font-size: clamp(1.3rem, 4vw, 1.6rem); letter-spacing: -.025em; }
.fs-intro { color: var(--text-muted); font-size: .92rem; margin: .5rem 0 0; }

.fs-field { margin-bottom: 1.4rem; }
.fs-field > label { font-size: .875rem; font-weight: 600; color: var(--text-strong); margin-bottom: .4rem; }
.fs-req { color: var(--accent-600); font-weight: 700; }
.fs-help { font-size: .8rem; color: var(--text-muted); margin: -.15rem 0 .45rem; }
.fs-error { font-size: .8rem; color: var(--bad-tx); margin: .35rem 0 0; font-weight: 500; }
.fs-field.has-error input, .fs-field.has-error textarea, .fs-field.has-error select { border-color: var(--bad-tx); }
.fs-error-top {
  background: var(--bad-bg); color: var(--bad-tx); border: 1px solid var(--bad-br);
  border-radius: 10px; padding: .75rem 1rem; font-size: .87rem; margin: 0 0 1.5rem;
}

/* A section title inside a form is a real divider, so it looks like one. */
.fs-heading {
  font-size: 1rem; letter-spacing: -.01em;
  margin: 2.25rem 0 1.1rem; padding-top: 1.1rem;
  border-top: 1px solid var(--border);
}
.fs-sheet > div:first-child .fs-heading:first-child { margin-top: 0; border-top: 0; padding-top: 0; }
.fs-note { font-size: .9rem; color: var(--text); margin: 0 0 1.4rem; }

.fs-choices { display: grid; gap: .45rem; }
.fs-choice, .fs-consent {
  display: flex; gap: .6rem; align-items: flex-start;
  padding: .6rem .75rem; border: 1px solid var(--border); border-radius: 10px;
  font-size: .9rem; font-weight: 400; color: var(--text); cursor: pointer; margin: 0;
}
.fs-choice:hover, .fs-consent:hover { border-color: var(--border-strong); }
.fs-choice input, .fs-consent input { margin: .15rem 0 0; flex: none; accent-color: var(--accent); }
.fs-choice:has(input:checked), .fs-consent:has(input:checked) {
  border-color: var(--accent); background: var(--accent-soft);
}

.fs-submit { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--border); }
.fs-submit .btn { width: 100%; padding: .8rem 1rem; font-size: .95rem; }
.fs-fine { font-size: .78rem; color: var(--text-muted); text-align: center; margin: .75rem 0 0; }
.fs-submit-preview { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--border); opacity: .5; pointer-events: none; }
.fs-submit-preview .btn { width: 100%; }
.fs-empty { color: var(--text-muted); font-size: .9rem; text-align: center; padding: 2rem 0; }

/* The honeypot must be reachable by a bot reading the DOM and invisible to a
   person. Not display:none — some bots skip those. */
.fs-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* --- The public page ----------------------------------------------------- */

.fs-page { background: var(--bg); }
.fs-wrap { max-width: 38rem; margin: 0 auto; padding: 2rem 1rem 4rem; }
@media (max-width: 640px) { .fs-wrap { padding: 1rem .75rem 3rem; } }
.fs-org {
  font-size: .82rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-muted); text-align: center; margin-bottom: 1rem;
}
.fs-foot { text-align: center; font-size: .72rem; color: var(--text-muted); margin-top: 1.5rem; letter-spacing: .06em; }

.fs-done, .fs-closed { text-align: center; }
.fs-done h1, .fs-closed h1 { font-size: 1.4rem; }
.fs-done p, .fs-closed p { color: var(--text-muted); font-size: .93rem; margin: .6rem auto 0; max-width: 26rem; }
.fs-done-when { font-family: var(--font-mono); font-size: .76rem !important; margin-top: 1.25rem !important; }
.fs-tick {
  width: 54px; height: 54px; border-radius: 50%; margin: 0 auto 1.1rem;
  display: grid; place-items: center; background: var(--accent-soft); color: var(--accent);
}
.fs-tick svg { width: 26px; height: 26px; }

/* --- The builder --------------------------------------------------------- */

.fb-layout { display: grid; grid-template-columns: minmax(0,1fr) 20rem; gap: 1.5rem; align-items: start; }
@media (max-width: 940px) { .fb-layout { grid-template-columns: 1fr; } }
.fb-side { position: sticky; top: 5rem; }
@media (max-width: 940px) { .fb-side { position: static; } }

.fb-title {
  font: inherit; font-size: clamp(1.3rem,4vw,1.6rem); font-weight: 600;
  letter-spacing: -.025em; color: var(--text-strong);
  border: 1px solid transparent; border-radius: 8px; padding: .25rem .5rem; margin: -.25rem -.5rem;
  width: calc(100% + 1rem); background: transparent;
}
.fb-intro {
  font: inherit; font-size: .92rem; color: var(--text-muted); resize: vertical;
  border: 1px solid transparent; border-radius: 8px; padding: .3rem .5rem; margin: .35rem -.5rem 0;
  width: calc(100% + 1rem); background: transparent;
}
.fb-title:hover, .fb-intro:hover { border-color: var(--border); }
.fb-title:focus, .fb-intro:focus { border-color: transparent; background: var(--bg-raised); }

.fb-block { position: relative; border-radius: 10px; padding: .5rem; margin: 0 -.5rem; }
.fb-block:hover { background: var(--bg-sunken); }
.fb-block .fs-field, .fb-block .fs-heading, .fb-block .fs-note { margin-bottom: .4rem; }
.fb-block input, .fb-block textarea, .fb-block select { pointer-events: none; }

.fb-tools {
  position: absolute; top: .35rem; right: .35rem; display: flex; gap: .15rem;
  opacity: 0; transition: opacity .12s;
}
.fb-block:hover .fb-tools, .fb-block:focus-within .fb-tools { opacity: 1; }
.fb-tool {
  width: 26px; height: 26px; display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--border); border-radius: 7px; background: var(--bg-raised); color: var(--text-muted);
}
.fb-tool svg { width: 14px; height: 14px; }
.fb-tool:hover:not(:disabled) { color: var(--text-strong); border-color: var(--border-strong); }
.fb-tool:disabled { opacity: .3; cursor: default; }
.fb-tool-del:hover:not(:disabled) { color: var(--bad-tx); border-color: var(--bad-tx); }

/*
 * The insert rail. A hairline that only resolves into a button on hover, so
 * the sheet reads as a document at rest and as an editor when you reach for it.
 */
.fb-rail { position: relative; height: 14px; display: grid; place-items: center; }
.fb-rail::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px;
  background: var(--border); opacity: 0; transition: opacity .12s;
}
.fb-rail:hover::before { opacity: 1; }
.fb-add {
  width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  border: 1px dashed var(--border-strong); background: var(--bg-raised); color: var(--text-muted);
  cursor: pointer; opacity: 0; transition: opacity .12s, transform .12s; position: relative;
}
.fb-rail:hover .fb-add, .fb-add:focus-visible { opacity: 1; }
.fb-add:hover { color: var(--accent); border-color: var(--accent); border-style: solid; transform: scale(1.1); }
.fb-add svg { width: 12px; height: 12px; }

/* --- Forms list ---------------------------------------------------------- */

.form-list { display: grid; gap: .6rem; }
.form-row {
  display: grid; grid-template-columns: 1fr auto auto; gap: 1.25rem; align-items: center;
  background: var(--bg-raised); border: 1px solid var(--border); border-radius: 12px;
  padding: .95rem 1.2rem; box-shadow: var(--shadow-card);
}
@media (max-width: 700px) {
  .form-row { grid-template-columns: 1fr auto; gap: .75rem; }
  .form-row-actions { grid-column: 1 / -1; }
}
.form-row-title { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; font-weight: 600; color: var(--text-strong); }
.form-row-title a { color: inherit; text-decoration: none; }
.form-row-title a:hover { color: var(--accent); }
.form-row-meta { font-size: .8rem; color: var(--text-muted); margin: .2rem 0 0; }
.form-row-meta .sep { opacity: .5; margin: 0 .25rem; }
.form-row-count { text-align: center; text-decoration: none; color: inherit; padding: 0 .5rem; }
.form-row-count b { display: block; font-size: 1.2rem; font-weight: 700; color: var(--text-strong); letter-spacing: -.02em; }
.form-row-count span { font-size: .68rem; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); }
.form-row-count:hover b { color: var(--accent); }
.form-row-actions { display: flex; gap: .4rem; }

.status { font-size: .66rem; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; padding: .18rem .45rem; border-radius: 5px; }
.status-draft     { background: var(--bg-sunken); color: var(--text-muted); }
.status-published { background: var(--accent-soft); color: var(--accent-soft-t); }
.status-closed    { background: var(--warn-bg); color: var(--warn-tx); }

/* --- Inbox --------------------------------------------------------------- */

.ib { display: grid; grid-template-columns: 19rem minmax(0,1fr); gap: 1.25rem; align-items: start; }
@media (max-width: 900px) { .ib { grid-template-columns: 1fr; } .ib-list { max-height: 18rem; } }

.ib-list { overflow: auto; max-height: 75vh; padding: .35rem; }
.ib-item {
  display: block; text-decoration: none; color: inherit;
  padding: .7rem .8rem; border-radius: 9px; border-left: 3px solid transparent;
}
.ib-item:hover { background: var(--bg-sunken); }
.ib-item.is-open { background: var(--accent-soft); border-left-color: var(--accent); }
.ib-item.is-unread .ib-form { font-weight: 700; }
.ib-item.is-unread .ib-form::before {
  content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent-600); margin-right: .45rem; vertical-align: middle;
}
.ib-item-top { display: flex; justify-content: space-between; gap: .5rem; align-items: baseline; }
.ib-form { font-size: .87rem; color: var(--text-strong); font-weight: 500; }
.ib-item-top time { font-size: .72rem; color: var(--text-muted); white-space: nowrap; }
.ib-item-bot { display: flex; justify-content: space-between; gap: .5rem; align-items: center; margin-top: .2rem; }
.ib-center { font-size: .74rem; color: var(--text-muted); }

.ib-doc { display: grid; gap: 1rem; }
.ib-answers { margin: 0; display: grid; gap: 0; }
.ib-answers > div { padding: .85rem 0; border-bottom: 1px solid var(--border); }
.ib-answers > div:last-child { border-bottom: 0; }
.ib-answers dt { font-size: .74rem; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); font-weight: 700; }
.ib-answers dd { margin: .3rem 0 0; font-size: .95rem; color: var(--text-strong); }
.ib-blank { color: var(--text-muted); }

.ib-tools { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }
.ib-tools select { font-size: .85rem; }

/* A tag's own colour, set with --sw inline — see the shared .swatch/.color-picker
   component this reuses the same variable convention from. */
.ib-chips { display: inline-flex; gap: .3rem; flex-wrap: wrap; }
.tag-chip {
  display: inline-flex; align-items: center; gap: .3rem;
  background: var(--sw, var(--bg-sunken)); color: #fff;
  padding: .2rem .55rem; border-radius: 999px; font-size: .72rem; font-weight: 600;
  white-space: nowrap;
}
.tag-chip-sm { padding: .12rem .5rem; font-size: .66rem; }
.tag-chip-x {
  border: 0; background: rgba(255,255,255,.25); color: #fff; cursor: pointer;
  width: 1rem; height: 1rem; line-height: 1; border-radius: 50%; font-size: .72rem;
  display: inline-flex; align-items: center; justify-content: center; padding: 0;
}
.tag-chip-x:hover { background: rgba(255,255,255,.4); }
.ib-tags { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-top: .6rem; }

.tag-pick-list { display: flex; flex-direction: column; gap: .15rem; max-height: 12rem; overflow: auto; }
.tag-pick {
  display: flex; align-items: center; gap: .5rem; border: 0; background: none; cursor: pointer;
  padding: .4rem .5rem; border-radius: 8px; font-size: .87rem; color: var(--text); text-align: left;
}
.tag-pick:hover:not(:disabled) { background: var(--bg-sunken); }
.tag-pick:disabled { opacity: .45; cursor: default; }
.tag-pick-sw { width: .8rem; height: .8rem; border-radius: 50%; background: var(--sw); flex-shrink: 0; }

@media print {
  .ib-list, .ib-triage, .fs-submit, .fb-side, .fb-tools, .fb-rail { display: none !important; }
  .ib { grid-template-columns: 1fr; }
  .fs-sheet { box-shadow: none; border: 0; padding: 0; }
}

/* ===========================================================================
   ATMOSPHERE
   ---------------------------------------------------------------------------
   The origin this product comes from is not a grey dashboard: it runs a slowly
   drifting pastel gradient with frosted panels floating on it, and the owner
   has used it daily for years. A flat surface here reads as a downgrade from
   the thing being replaced.
   
   So: a living field of colour behind the app, in Classivo's own palette —
   spruce teal and warm orange — at low enough saturation to stay a background
   rather than a decoration. Cards stay opaque and float on it. Glass over a
   moving gradient is where legibility goes to die, and this is a tool people
   read all day.
   
   All of it is disabled under prefers-reduced-motion.
   =========================================================================== */

body::before {
  content: "";
  position: fixed; inset: -20vmax; z-index: -1; pointer-events: none;
  /* Fully transparent stops of each hue, not the `transparent` keyword —
     that is transparent BLACK, and interpolating towards it puts a grey
     halo where the wash should simply stop existing. */
  background:
    radial-gradient(40vmax 36vmax at 10% 6%,
      rgba(156,198,211,.26) 0%, rgba(156,198,211,.08) 46%, rgba(156,198,211,0) 72%),
    radial-gradient(34vmax 32vmax at 90% 2%,
      rgba(239,174,156,.20) 0%, rgba(239,174,156,.06) 46%, rgba(239,174,156,0) 72%),
    radial-gradient(44vmax 38vmax at 80% 94%,
      rgba(111,167,185,.20) 0%, rgba(111,167,185,.06) 46%, rgba(111,167,185,0) 72%),
    radial-gradient(32vmax 30vmax at 2% 90%,
      rgba(241,196,94,.14) 0%, rgba(241,196,94,.04) 46%, rgba(241,196,94,0) 72%);
  animation: drift 42s ease-in-out infinite alternate;
}
@keyframes drift {
  0%   { transform: translate3d(0,0,0)      scale(1); }
  50%  { transform: translate3d(2%,-1.5%,0) scale(1.06); }
  100% { transform: translate3d(-1.5%,2%,0) scale(1.02); }
}

[data-theme="dark"] body::before {
  background:
    radial-gradient(40vmax 36vmax at 10% 6%,
      rgba(65,113,133,.30) 0%, rgba(65,113,133,.10) 46%, rgba(65,113,133,0) 72%),
    radial-gradient(34vmax 32vmax at 90% 2%,
      rgba(198,79,57,.16) 0%, rgba(198,79,57,.05) 46%, rgba(198,79,57,0) 72%),
    radial-gradient(44vmax 38vmax at 80% 94%,
      rgba(58,93,108,.34) 0%, rgba(58,93,108,.11) 46%, rgba(58,93,108,0) 72%),
    radial-gradient(32vmax 30vmax at 2% 90%,
      rgba(217,144,29,.10) 0%, rgba(217,144,29,.03) 46%, rgba(217,144,29,0) 72%);
}

/* The bar sits over the field, so it gets the frosting rather than the cards. */
.topbar {
  background: color-mix(in srgb, var(--bg-raised) 82%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
}

/* --- Arrival --------------------------------------------------------------
   Content settles in rather than appearing. One short, staggered pass on page
   load — not on every re-render, which is how motion becomes noise. */

@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.wrap > * { animation: rise .42s cubic-bezier(.22,.9,.3,1) both; }
.wrap > *:nth-child(1) { animation-delay: .02s; }
.wrap > *:nth-child(2) { animation-delay: .07s; }
.wrap > *:nth-child(3) { animation-delay: .12s; }
.wrap > *:nth-child(4) { animation-delay: .17s; }
.wrap > *:nth-child(n+5) { animation-delay: .2s; }

.people > *, .form-list > *, .badge-grid > * { animation: rise .4s cubic-bezier(.22,.9,.3,1) both; }
.people > *:nth-child(1),.form-list > *:nth-child(1),.badge-grid > *:nth-child(1){animation-delay:.03s}
.people > *:nth-child(2),.form-list > *:nth-child(2),.badge-grid > *:nth-child(2){animation-delay:.06s}
.people > *:nth-child(3),.form-list > *:nth-child(3),.badge-grid > *:nth-child(3){animation-delay:.09s}
.people > *:nth-child(4),.form-list > *:nth-child(4),.badge-grid > *:nth-child(4){animation-delay:.12s}
.people > *:nth-child(5),.form-list > *:nth-child(5),.badge-grid > *:nth-child(5){animation-delay:.15s}
.people > *:nth-child(n+6),.form-list > *:nth-child(n+6),.badge-grid > *:nth-child(n+6){animation-delay:.18s}

/* The live board, once it is polling.
 *
 * These cards are reconciled rather than rebuilt, so they must stop animating:
 * the staggered entrance above is keyed on :nth-child, and the list is ordered
 * newest check-in first, so a single arrival changes every card's index and
 * therefore every card's delay. Replaying that every four seconds is what made
 * the board flash.
 *
 * Placed after the stagger deliberately — .people.is-live > * and
 * .people > *:nth-child(n) have the same specificity, so source order is what
 * decides. Both keyframes end at the element's own default state, so switching
 * the animation off leaves every card exactly where it already is.
 *
 * A genuine new arrival still animates in, once, with no stagger: there is only
 * ever one of them. */
.people.is-live > * { animation: none; }
.people.is-live > .is-new { animation: rise .4s cubic-bezier(.22,.9,.3,1) both; animation-delay: 0s; }

/* --- Lift ---------------------------------------------------------------- */

.card, .person, .form-row {
  transition: transform .18s cubic-bezier(.22,.9,.3,1), box-shadow .18s, border-color .18s;
}
.person:hover, .form-row:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}

/* --- Stats, with weight -------------------------------------------------- */
/* Superseded by .stat-tile in THE BOARD, SECOND PASS below. Kept only as the
   fallback for any .stat that still holds bare divs. */

.stat > div:not([class]) {
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: 14px; padding: .8rem 1.15rem; min-width: 7rem;
  box-shadow: var(--shadow-card);
}

/* --- Avatars carry identity ----------------------------------------------
   A ring of colour picked from the name, so the same child is the same colour
   on every screen. Six hues, all far enough apart to tell apart at a glance
   and all drawn from the brand family rather than a random wheel. */

.avatar { position: relative; box-shadow: inset 0 0 0 2px color-mix(in srgb, currentColor 22%, transparent); }
.avatar.c1 { background: #DFEDF1; color: #36505C; }
.avatar.c2 { background: #FBEECB; color: #7F4018; }
.avatar.c3 { background: #FAE7E0; color: #8B382B; }
.avatar.c4 { background: #E6E3F2; color: #4C4179; }
.avatar.c5 { background: #C3DEE6; color: #2C4753; }
.avatar.c6 { background: #E6EFD9; color: #45602F; }

/* --- The live board earns its keep --------------------------------------- */

.person { position: relative; overflow: hidden; }
.person::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 40%, rgba(255,255,255,.28) 50%, transparent 60%);
  transform: translateX(-120%);
}
.person:hover::after { transform: translateX(120%); transition: transform .85s ease; }
[data-theme="dark"] .person::after { background: linear-gradient(115deg, transparent 40%, rgba(255,255,255,.07) 50%, transparent 60%); }

/* --- Empty states invite, they do not apologise -------------------------- */

.empty { font-size: .92rem; }
.card.empty, .empty {
  background-image: radial-gradient(circle at 50% 0%, color-mix(in srgb, var(--accent) 6%, transparent), transparent 70%);
}

@media (prefers-reduced-motion: reduce) {
  body::before { animation: none; }
  .wrap > *, .people > *, .form-list > *, .badge-grid > * { animation: none; }
  .person:hover, .form-row:hover { transform: none; }
  .person::after { display: none; }
}

/* ===========================================================================
   THE SHEET, SECOND PASS
   Driven by a screenshot rather than a guess. The faults were: the sheet sat
   at the top of a tall empty page, the masthead was a line of grey caps with
   nothing of the product in it, the inputs in dark mode were hollow outlines
   that read as disabled, the sheet had no colour tie to the brand at all, and
   a column of orange asterisks ran down the right of every label.
   =========================================================================== */

/* Centre the sheet in the viewport instead of stranding it under a header.
   A one-question form should sit in the middle of the screen, a twenty-
   question one should still scroll from the top — hence min-height, not
   height, and auto margins that collapse when the content is taller. */
.fs-page { min-height: 100dvh; display: flex; }
.fs-wrap { width: 100%; display: flex; flex-direction: column; justify-content: center; }

/* The masthead carries the mark. It is the only place on this page a parent
   sees who built the thing, and it makes the top of the page an object
   rather than a caption. */
.fs-org {
  display: flex; align-items: center; justify-content: center; gap: .55rem;
  font-size: 1rem; font-weight: 650; letter-spacing: -.01em; text-transform: none;
  color: var(--text-strong); margin-bottom: 1.15rem;
}
.fs-org svg { flex: none; }

/* A brand rule across the top edge. Four pixels of the palette is enough to
   stop the sheet reading as a default browser box. */
.fs-sheet { position: relative; overflow: hidden; }
.fs-sheet::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 4px;
  background: linear-gradient(90deg, var(--brand-700), var(--brand-500) 45%, var(--accent-500));
}
.fb-sheet::before { opacity: .55; }

/* Inputs as wells, not outlines. In dark mode a transparent field with a
   1px border is indistinguishable from a disabled one; sinking it below the
   sheet makes it obviously a place to type. */
.fs-sheet input[type="text"], .fs-sheet input[type="email"], .fs-sheet input[type="tel"],
.fs-sheet input[type="number"], .fs-sheet input[type="date"], .fs-sheet input[type="search"],
.fs-sheet textarea, .fs-sheet select {
  background: var(--bg-sunken);
}
.fs-sheet input:focus, .fs-sheet textarea:focus, .fs-sheet select:focus {
  background: var(--bg-raised);
}

/* Say the rule once. A form where two fields are optional should mark those
   two, not asterisk the other eighteen. */
.fs-req { display: none; }
.fs-required-note {
  font-size: .8rem; color: var(--text-muted); margin: .6rem 0 0;
}
.fs-field > label { display: flex; align-items: baseline; gap: .4rem; }
.fs-field:not(.is-required) > label::after {
  content: "optional"; font-size: .72rem; font-weight: 500; letter-spacing: .02em;
  color: var(--text-muted); text-transform: lowercase;
}
/* The builder's own field editor already says "Must be answered", and the
   consent row is a sentence, not a label — neither wants the suffix. */
.fb-sheet .fs-field > label::after, .fs-consent::after { content: none; }

.fs-foot { margin-top: 1.75rem; opacity: .75; }

/* ===========================================================================
   THE BOARD, SECOND PASS
   Screenshot faults: the counters were two hairline numbers marooned in the
   top-right corner, and an empty building produced a full-width white slab
   containing one grey sentence and nine hundred pixels of nothing under it.
   =========================================================================== */

/* Counters as objects. Same numbers, but they now read as instruments rather
   than as stray text — and the two that change are the two that are tinted. */
.stat { display: flex; gap: .6rem; flex-wrap: wrap; }
.stat-tile {
  min-width: 6.2rem; padding: .7rem .9rem;
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow-card);
}
.stat-tile b {
  display: block; font-size: 1.85rem; line-height: 1.05; font-weight: 700;
  letter-spacing: -.03em; color: var(--text-strong); font-variant-numeric: tabular-nums;
}
.stat-tile span {
  display: block; margin-top: .15rem;
  font-size: .68rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-muted);
}
.stat-in    { border-color: var(--tile-a-br); background: var(--tile-a-bg); }
.stat-in b  { color: var(--tile-a-tx); }
.stat-today { border-color: var(--tile-b-br); background: var(--tile-b-bg); }
.stat-today b { color: var(--tile-b-tx); }

/* An empty state that is a destination, not an apology. It states the fact,
   says what will fill it, and offers the one action that does. */
.empty-state {
  border: 1px dashed var(--border-strong); border-radius: 16px;
  background:
    radial-gradient(120% 100% at 50% 0%, var(--accent-soft) 0%, transparent 62%),
    var(--bg-raised);
  padding: 2.75rem 1.5rem 2.5rem; text-align: center;
}
.empty-state[hidden] { display: none; }
.empty-glyph {
  width: 3.25rem; height: 3.25rem; margin: 0 auto 1.1rem;
  display: grid; place-items: center; border-radius: 16px;
  background: var(--bg); border: 1px solid var(--border);
  color: var(--accent); box-shadow: var(--shadow-card);
}
.empty-glyph svg { width: 1.6rem; height: 1.6rem; }
.empty-state h2 { font-size: 1.1rem; letter-spacing: -.02em; }
.empty-state p {
  color: var(--text-muted); font-size: .9rem; margin: .5rem auto 0; max-width: 27rem;
}
.empty-actions { display: flex; gap: .5rem; justify-content: center; flex-wrap: wrap; margin-top: 1.4rem; }

/* --- The page has a bottom ------------------------------------------------
   The shell is a column: header, content, footer. The content grows, so a
   short page pushes the footer to the bottom of the viewport instead of
   leaving several hundred pixels of unexplained background under it. */

body:has(.app-foot) { min-height: 100dvh; display: flex; flex-direction: column; }
.wrap { flex: 1; }

.app-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1rem clamp(1rem, 4vw, 2rem);
  border-top: 1px solid var(--border);
  font-size: .78rem; color: var(--text-muted);
}
.app-foot-mark { display: inline-flex; align-items: center; gap: .45rem; font-weight: 600; color: var(--text); }

/* A name with a face beside it. Same hash, same colour, same person as on the
   board — the roster row and the board tile are recognisably the same record. */
.who-cell { display: flex; align-items: center; gap: .65rem; }
.who-cell .code { display: block; margin-top: .1rem; }
.avatar-sm { width: 2rem; height: 2rem; font-size: .7rem; flex: none; }

/* ===========================================================================
   CONTROLS
   The real input styled with appearance:none, never a hidden input behind a
   span — focus, :checked, label association and screen readers all keep
   working. Mirrors the marketing site's kit in web/src/tw-input.css; the two
   should look like the same product.
   =========================================================================== */

input[type="checkbox"], input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  position: relative; display: grid; place-content: center;
  width: 1.15rem; height: 1.15rem; flex: none; margin: 0;
  border: 2px solid var(--border-strong); border-radius: .35rem;
  background: var(--bg-raised); cursor: pointer;
  transition: border-color .15s ease, background-color .15s ease, transform .1s ease;
}
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]::before, input[type="radio"]::before {
  content: ""; background: var(--on-accent);
  transform: scale(0);
  transition: transform .16s cubic-bezier(.2,.9,.3,1.35);
}
input[type="checkbox"]::before {
  width: .62rem; height: .62rem;
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
input[type="radio"]::before { width: .45rem; height: .45rem; border-radius: 50%; }
input[type="checkbox"]:hover, input[type="radio"]:hover { border-color: var(--accent); }
input[type="checkbox"]:checked, input[type="radio"]:checked {
  border-color: var(--accent); background: var(--accent);
}
input[type="checkbox"]:checked::before, input[type="radio"]:checked::before { transform: scale(1); }
input[type="checkbox"]:active, input[type="radio"]:active { transform: scale(.92); }
input[type="checkbox"]:disabled, input[type="radio"]:disabled {
  cursor: not-allowed; border-color: var(--border); background: var(--bg-sunken);
}

/* A switch is for something that takes effect immediately. A checkbox is for
   something a Save button confirms. They are not interchangeable. */
.switch {
  appearance: none; -webkit-appearance: none;
  position: relative; width: 2.75rem; height: 1.5rem; flex: none;
  border: 0; border-radius: 999px; background: var(--border-strong);
  cursor: pointer; transition: background-color .2s ease;
}
.switch::before {
  content: ""; position: absolute; top: .125rem; left: .125rem;
  width: 1.25rem; height: 1.25rem; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3);
  transform: none; clip-path: none;
  transition: transform .2s cubic-bezier(.2,.8,.2,1);
}
.switch:checked { background: var(--accent); }
.switch:checked::before { transform: translateX(1.25rem); }

/* Disclosure that opens by sliding. 0fr -> 1fr is the only way to animate to
   an unknown content height in CSS. */
.disclosure > summary { cursor: pointer; list-style: none; }
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure-body {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows .24s cubic-bezier(.2,.8,.2,1);
}
.disclosure[open] .disclosure-body { grid-template-rows: 1fr; }
.disclosure-body > * { overflow: hidden; }

/* --- The add-ons screen --------------------------------------------------- */

.addon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 1rem; }
.addon-card { display: flex; flex-direction: column; text-align: left; transition: border-color .15s ease, box-shadow .15s ease; }
.addon-card:hover { box-shadow: var(--shadow-lift); }
.addon-card.is-on { border-left: 3px solid var(--accent); }
.addon-top { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.addon-top h2 { font-size: 1rem; letter-spacing: -.015em; }
.addon-blurb { margin-top: .5rem; }
.tag-soon { background: var(--warn-bg); color: var(--warn-tx); }

.addon-more { margin-top: .85rem; }
.addon-more > summary {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  padding: .4rem 0; font-size: .82rem; font-weight: 600; color: var(--accent);
  border-top: 1px solid var(--border);
}
.addon-more > summary svg { width: .85rem; height: .85rem; transition: transform .2s ease; }
.addon-more[open] > summary svg { transform: rotate(45deg); }
.addon-points { margin: .5rem 0 0; padding: 0; list-style: none; display: grid; gap: .4rem; }
.addon-points li {
  position: relative; padding-left: 1.1rem; font-size: .83rem; line-height: 1.45; color: var(--text);
}
.addon-points li::before {
  content: ""; position: absolute; left: 0; top: .48em;
  width: .35rem; height: .35rem; border-radius: 50%; background: var(--accent);
}
.addon-where { margin: .7rem 0 0; font-size: .78rem; color: var(--text-muted); }
.addon-where strong { color: var(--text); }

.addon-foot { margin-top: auto; padding-top: 1rem; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.addon-note { font-size: .8rem; color: var(--text-muted); }
.addon-footnote { margin-top: 1.5rem; max-width: 44rem; }

/* ===========================================================================
   THE SHARED WEEK — Parent Orientation & Conference Scheduling
   Seven columns, not an hour grid. A center's week has a handful of things in
   it, and an hour grid would be nine tenths empty space with the content
   squeezed into slivers. Columns collapse to a list on narrow screens.
   =========================================================================== */

.wk-nav { display: flex; gap: .4rem; }

.week {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: .5rem;
  margin-bottom: 1.5rem;
  transition: opacity .12s ease;
}

/* Fetching the next week. Faded rather than emptied: the week you were reading
   stays legible, and a grid that blanks for 80ms reads as a fault. Pointer
   events stay off so a second click cannot queue behind the first. */
.week[aria-busy="true"] { opacity: .5; pointer-events: none; }
@media (max-width: 900px) { .week { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .week { grid-template-columns: 1fr; } }

.wk-day {
  background: var(--bg-raised); border: 1px solid var(--border); border-radius: 12px;
  padding: .6rem .55rem; min-height: 7rem;
}
.wk-day.is-today { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.wk-day > header {
  display: flex; align-items: baseline; gap: .35rem;
  padding-bottom: .45rem; margin-bottom: .5rem; border-bottom: 1px solid var(--border);
}
.wk-dow { font-size: .68rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); }
.wk-num { font-size: .95rem; font-weight: 700; color: var(--text-strong); margin-left: auto; }
.is-today .wk-dow, .is-today .wk-num { color: var(--accent); }
.wk-none { color: var(--border-strong); font-size: .85rem; text-align: center; margin: .75rem 0 0; }

.wk-ev {
  position: relative; margin-bottom: .4rem; padding: .45rem .5rem;
  border-radius: 8px; border-left: 3px solid var(--accent);
  background: var(--bg-sunken); font-size: .78rem; line-height: 1.35;
}
.wk-ev:last-child { margin-bottom: 0; }
.wk-slot { border-left-color: var(--warn-tx); }
.wk-slot.is-booked { border-left-color: var(--accent); background: var(--accent-soft); }
.wk-time { font-weight: 700; color: var(--text-strong); font-variant-numeric: tabular-nums; }
.wk-what { color: var(--text); }
.wk-who  { color: var(--text-muted); font-size: .72rem; }
.wk-state {
  display: inline-block; margin-top: .2rem; font-size: .64rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted);
}
.is-booked .wk-state { color: var(--accent-soft-t); }

/* The cancel affordance stays out of the way until the card is touched. */
.wk-x { position: absolute; top: .2rem; right: .2rem; opacity: 0; transition: opacity .15s ease; }
.wk-ev:hover .wk-x, .wk-x:focus-within { opacity: 1; }
.wk-x button {
  border: 0; background: none; cursor: pointer; line-height: 1;
  font-size: 1rem; color: var(--text-muted); padding: .1rem .2rem; border-radius: 4px;
}
.wk-x button:hover { color: var(--bad-tx); background: var(--bad-bg); }

/* A colour chosen when the event was added, overriding the kind-based
   default above regardless of shift / slot / booked. Same specificity as
   .wk-slot.is-booked and placed after it, so it wins in every case. */
.wk-ev[data-colored] { border-left-color: var(--ev-color); }

/* --- Schedule: agenda and month views -------------------------------------
   Week view's grid is exactly what these two views are not: agenda is one
   scannable column, month trades per-event detail for a whole month at a
   glance. Both still render `.wk-ev`-shaped cards (event_chip_html() /
   month's own compact chip) so a coloured event looks the same everywhere. */

/* The .week container keeps its own class between views (app.js swaps
   wk-view-*), so its 7-column grid has to stand down for the two views that
   don't want it — the inner .agenda / .month element owns its own layout. */
.wk-view-agenda.week, .wk-view-month.week { display: block; }

.agenda-day { margin-bottom: 1.5rem; }
.agenda-day:last-child { margin-bottom: 0; }
.agenda-day h3 {
  font-size: .85rem; font-weight: 700; color: var(--text-strong);
  margin: 0 0 .5rem; padding-bottom: .35rem; border-bottom: 1px solid var(--border);
}
.agenda-evs { display: grid; gap: .4rem; }
.ag-ev {
  position: relative; padding: .55rem .7rem .55rem .85rem;
  border-radius: 8px; border-left: 3px solid var(--accent);
  background: var(--bg-raised); border: 1px solid var(--border); border-left-width: 3px;
  font-size: .85rem; line-height: 1.4; display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
}
.ag-ev.wk-slot { border-left-color: var(--warn-tx); }
.ag-ev.wk-slot.is-booked { border-left-color: var(--accent); background: var(--accent-soft); }
.ag-ev[data-colored] { border-left-color: var(--ev-color); }
.ag-ev .wk-time { flex-shrink: 0; }
.ag-ev .wk-who { margin-left: auto; }
.ag-ev .wk-x { position: static; opacity: 1; margin-left: .25rem; }

.month {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px;
  background: var(--border); border: 1px solid var(--border); border-radius: 12px;
  overflow: hidden; margin-bottom: 1.5rem;
}
.month-dow {
  background: var(--bg-sunken); padding: .4rem .5rem; font-size: .68rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); text-align: center;
}
.month-day {
  background: var(--bg-raised); min-height: 5.5rem; padding: .4rem .45rem;
}
.month-day.is-other-month { background: var(--bg-sunken); }
.month-day.is-other-month .month-num { color: var(--text-muted); opacity: .6; }
.month-day.is-today { box-shadow: inset 0 0 0 2px var(--accent); }
.month-num { font-size: .82rem; font-weight: 700; color: var(--text-strong); }
.is-today .month-num { color: var(--accent); }
.month-evs { margin-top: .3rem; display: grid; gap: .2rem; }
.month-ev {
  border-left: 3px solid var(--accent); border-radius: 4px; background: var(--bg-sunken);
  padding: .1rem .35rem; font-size: .68rem; line-height: 1.3; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
}
.month-ev.wk-slot { border-left-color: var(--warn-tx); }
.month-ev[data-colored] { border-left-color: var(--ev-color); }
.month-ev-time { font-weight: 700; font-variant-numeric: tabular-nums; }
.month-ev-what { margin-left: .3rem; color: var(--text-muted); }
@media (max-width: 720px) {
  .month { grid-template-columns: repeat(7, minmax(0, 1fr)); font-size: .8rem; }
  .month-dow { font-size: .58rem; }
  .month-ev-what { display: none; }
}

/* --- A small colour swatch picker -----------------------------------------
   Shared shape for anywhere something needs a colour from a fixed palette —
   an event on the schedule, a tag in the inbox. A radio group underneath, so
   it works with JavaScript off and needs no script to submit correctly. */
.color-picker { display: flex; flex-wrap: wrap; gap: .4rem; }
.swatch { position: relative; cursor: pointer; display: block; }
.swatch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.swatch span {
  display: block; width: 1.6rem; height: 1.6rem; border-radius: 999px;
  background: var(--sw, var(--bg-sunken)); border: 2px solid transparent;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.08);
  transition: transform .1s ease;
}
.swatch input:checked + span {
  border-color: var(--bg-raised); box-shadow: 0 0 0 2px var(--sw, var(--text-muted));
}
.swatch input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.swatch:hover span { transform: scale(1.08); }
.swatch-none span {
  background: var(--bg-sunken); color: var(--text-muted);
  display: flex; align-items: center; justify-content: center; font-size: .9rem;
}

.sched-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; align-items: start; }
@media (max-width: 900px) { .sched-cols { grid-template-columns: 1fr; } }

/* A segmented control: two mutually exclusive modes, shown side by side
   because the difference between them is the thing being chosen. */
.seg { display: inline-flex; padding: .2rem; gap: .2rem; border-radius: 10px; background: var(--bg-sunken); }
.seg-opt { margin: 0; cursor: pointer; }
.seg-opt input { position: absolute; opacity: 0; width: 0; height: 0; }
.seg-opt span {
  display: block; padding: .4rem .85rem; border-radius: 8px;
  font-size: .82rem; font-weight: 600; color: var(--text-muted);
  transition: background-color .15s ease, color .15s ease;
}
.seg-opt input:checked + span { background: var(--bg-raised); color: var(--text-strong); box-shadow: var(--shadow-card); }
.seg-opt input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.opt { font-weight: 400; color: var(--text-muted); }

/* Bookings, staff side. */
.bk-list { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: .6rem; }
.bk {
  display: grid; grid-template-columns: 5.5rem 1fr; gap: .75rem;
  padding: .7rem .8rem; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-sunken);
}
.bk-when { font-size: .78rem; line-height: 1.3; }
.bk-when strong { display: block; color: var(--text-strong); }
.bk-when span { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.bk-who { font-size: .85rem; }
.bk-who strong { color: var(--text-strong); }
.bk-student { color: var(--text-muted); font-size: .8rem; margin-left: .3rem; }
.bk-who .code { display: block; margin-top: .15rem; font-size: .75rem; }
.bk-note { margin: .35rem 0 0; font-size: .8rem; color: var(--text); }
.bk-with { grid-column: 2; font-size: .75rem; color: var(--text-muted); }
.bk-locked { color: var(--bad-tx); font-size: .8rem; }

/* Bookings, parent side: times as targets, not a dropdown. */
.bk-day {
  margin: 1.1rem 0 .5rem; font-size: .78rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted);
}
.bk-day:first-of-type { margin-top: .35rem; }
.bk-times { display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); gap: .5rem; }
.bk-time { margin: 0; cursor: pointer; }
.bk-time input { position: absolute; opacity: 0; width: 0; height: 0; }
.bk-time span {
  display: block; padding: .6rem .7rem; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg-raised);
  transition: border-color .15s ease, background-color .15s ease, transform .1s ease;
}
.bk-time span b { display: block; font-size: .9rem; color: var(--text-strong); font-variant-numeric: tabular-nums; }
.bk-time span em { display: block; font-style: normal; font-size: .74rem; color: var(--text-muted); margin-top: .1rem; }
.bk-time:hover span { border-color: var(--border-strong); }
.bk-time span:active { transform: scale(.98); }
.bk-time input:checked + span { border-color: var(--accent); background: var(--accent-soft); box-shadow: 0 0 0 1px var(--accent) inset; }
.bk-time input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ===========================================================================
   CUSTOMIZED LANGUAGE PACK
   =========================================================================== */

.lang-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: .6rem; margin-top: 1rem; }
.lang-opt {
  display: flex; align-items: center; gap: .7rem; margin: 0; cursor: pointer;
  padding: .7rem .8rem; border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-raised);
  transition: border-color .15s ease, background-color .15s ease, transform .1s ease;
}
.lang-opt:hover { border-color: var(--border-strong); }
.lang-opt:active { transform: scale(.995); }
.lang-opt:has(:checked) { border-color: var(--accent); background: var(--accent-soft); }
.lang-opt:has(:disabled) { cursor: default; opacity: .7; }
.lang-body { display: block; line-height: 1.3; }
.lang-native { display: block; font-size: .95rem; font-weight: 600; color: var(--text-strong); }
.lang-name { display: block; font-size: .74rem; color: var(--text-muted); }
.lang-actions { display: flex; align-items: center; gap: 1rem; margin-top: 1.1rem; }

.tr-cell {
  display: inline-block; padding: .2rem .55rem; border-radius: 999px;
  font-size: .75rem; font-weight: 600; text-decoration: none;
  background: var(--bg-sunken); color: var(--text-muted);
  transition: background-color .15s ease, color .15s ease;
}
a.tr-cell:hover { background: var(--border); color: var(--text-strong); }
.tr-cell.is-done { background: var(--accent-soft); color: var(--accent-soft-t); }

.tr-badge {
  display: inline-block; padding: .3rem .7rem; border-radius: 999px;
  font-size: .75rem; font-weight: 600;
  background: var(--warn-bg); color: var(--warn-tx);
}
.tr-badge.is-done { background: var(--accent-soft); color: var(--accent-soft-t); }

/* English beside the translation, one row per string. Two columns is the whole
   point — a translator who cannot see the original is guessing. */
.tr-sheet { overflow: hidden; }
.tr-head {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem;
  padding: .7rem 1.25rem; background: var(--bg-sunken);
  border-bottom: 1px solid var(--border);
  font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-muted);
}
.tr-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; align-items: start;
  padding: .9rem 1.25rem; border-bottom: 1px solid var(--border);
}
.tr-row:last-child { border-bottom: 0; }
.tr-row.is-minor { background: var(--bg-sunken); }
@media (max-width: 720px) {
  .tr-head { display: none; }
  .tr-row { grid-template-columns: 1fr; gap: .5rem; }
}
.tr-what {
  display: block; font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted); margin-bottom: .2rem;
}
.tr-en p { margin: 0; font-size: .9rem; color: var(--text-strong); line-height: 1.45; }
.tr-to input, .tr-to textarea { width: 100%; }
.tr-save { display: flex; gap: .6rem; margin-top: 1.25rem; }

/* The language switcher on a parent-facing form.
   Centred over the sheet and quiet: a family who wants English will not look
   for it, and a family who needs Bengali will find it in a second. */
.fs-langs {
  display: flex; flex-wrap: wrap; justify-content: center; gap: .3rem;
  margin: -.4rem 0 .9rem;
}
.fs-langs a {
  padding: .25rem .7rem; border-radius: 999px;
  font-size: .8rem; font-weight: 500; text-decoration: none;
  color: var(--text-muted); border: 1px solid transparent;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.fs-langs a:hover { color: var(--text-strong); background: var(--bg-sunken); }
.fs-langs a[aria-current="true"] {
  color: var(--accent-soft-t); background: var(--accent-soft); border-color: var(--accent);
}

/* ===========================================================================
   GRADE / LEVEL PICKER
   The native <select> stays in the DOM as the submitted value and is hidden
   with clip-path rather than display:none, so it can still be focused
   programmatically and still participates in form validation.
   =========================================================================== */

.lvl { position: relative; }
.lvl-native {
  position: absolute; width: 1px; height: 1px;
  clip-path: inset(50%); overflow: hidden; pointer-events: none;
}
/* Without JavaScript there is no button, so the select must show itself. */
.lvl:not(.is-ready) .lvl-native:only-child {
  position: static; width: 100%; height: auto; clip-path: none; pointer-events: auto;
}

.lvl-btn {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  width: 100%; padding: .55rem .75rem; text-align: left;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-raised); color: var(--text-strong);
  font: inherit; font-size: .9rem; cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.lvl-btn:hover { border-color: var(--border-strong); }
.lvl-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.is-open .lvl-btn { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.lvl-btn-text.is-empty { color: var(--text-muted); }

.lvl-caret {
  width: .5rem; height: .5rem; flex: none; margin-right: .15rem;
  border-right: 2px solid var(--text-muted); border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .18s ease;
}
.is-open .lvl-caret { transform: rotate(-135deg) translate(-3px, -3px); }

.lvl-panel {
  position: absolute; z-index: 40; top: calc(100% + .3rem); left: 0; right: 0;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-raised); box-shadow: var(--shadow-lift);
  overflow: hidden; animation: lvl-in .14s ease-out;
}
@keyframes lvl-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .lvl-panel { animation: none; } }

.lvl-options { max-height: 15rem; overflow-y: auto; padding: .25rem; }
.lvl-opt {
  display: block; width: 100%; padding: .5rem .6rem; text-align: left;
  border: 0; border-radius: 8px; background: none; cursor: pointer;
  font: inherit; font-size: .88rem; color: var(--text);
  transition: background-color .12s ease, color .12s ease;
}
.lvl-opt:hover, .lvl-opt:focus-visible { background: var(--bg-sunken); color: var(--text-strong); outline: none; }
.lvl-opt.is-empty { color: var(--text-muted); }
.lvl-opt.is-on { background: var(--accent-soft); color: var(--accent-soft-t); font-weight: 600; }

/* Levels a student still carries that are not on the list any more. Shown so
   an existing value survives an edit, marked so nobody picks one by accident. */
.lvl-group {
  padding: .5rem .6rem .2rem; font-size: .65rem; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--text-muted);
}
.lvl-opt.is-orphan { color: var(--text-muted); font-style: italic; }

/* The add row is pinned under the list and separated by a rule, so it reads as
   an action rather than as one more level to pick. */
.lvl-add-row { border-top: 1px solid var(--border); padding: .35rem; background: var(--bg-sunken); }
.lvl-add-btn {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: .5rem .6rem; border: 0; border-radius: 8px; background: none;
  font: inherit; font-size: .85rem; font-weight: 600; color: var(--accent);
  cursor: pointer; transition: background-color .12s ease;
}
.lvl-add-btn:hover, .lvl-add-btn:focus-visible { background: var(--bg-raised); outline: none; }
.lvl-plus {
  position: relative; width: 1.05rem; height: 1.05rem; flex: none; border-radius: 50%;
  background: var(--accent); display: inline-block;
}
.lvl-plus::before, .lvl-plus::after {
  content: ""; position: absolute; background: var(--on-accent); border-radius: 1px;
}
.lvl-plus::before { left: 50%; top: .27rem; bottom: .27rem; width: 2px; transform: translateX(-50%); }
.lvl-plus::after  { top: 50%; left: .27rem; right: .27rem; height: 2px; transform: translateY(-50%); }

/* A class that sets `display` outranks the user-agent's [hidden] rule, so
   hiding these from JavaScript needs saying explicitly. Without this the add
   form and its button were both on screen at once. */
.lvl-add-btn[hidden], .lvl-add-form[hidden], .lvl-add-error[hidden] { display: none; }

.lvl-add-form { display: flex; gap: .35rem; padding: .25rem; }
.lvl-add-form input { flex: 1; min-width: 0; padding: .45rem .6rem; font-size: .85rem; }
.lvl-add-save {
  flex: none; padding: .45rem .8rem; border: 0; border-radius: 8px;
  background: var(--accent); color: var(--on-accent);
  font: inherit; font-size: .82rem; font-weight: 600; cursor: pointer;
}
.lvl-add-save:disabled { opacity: .6; cursor: default; }
.lvl-add-error { margin: .1rem .35rem .35rem; font-size: .78rem; color: var(--bad-tx); }

/* --- Settings: the level list -------------------------------------------- */

.lvl-list { list-style: none; margin: 1.1rem 0 0; padding: 0; display: grid; gap: .5rem; }
.lvl-row {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  padding: .55rem .7rem; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-sunken);
}
.lvl-move { display: flex; flex-direction: column; gap: .1rem; }
.lvl-move button {
  border: 0; background: none; cursor: pointer; padding: 0 .15rem;
  color: var(--text-muted); line-height: 1;
}
.lvl-move button:hover:not(:disabled) { color: var(--accent); }
.lvl-move button:disabled { opacity: .3; cursor: default; }
.lvl-move svg { width: .85rem; height: .85rem; display: block; }
.lvl-name { display: flex; gap: .4rem; flex: 1; min-width: 12rem; }
.lvl-name input { flex: 1; min-width: 0; }
.lvl-count { font-size: .78rem; color: var(--text-muted); white-space: nowrap; }

.lvl-add { display: flex; gap: .5rem; margin-top: 1rem; max-width: 28rem; }
.lvl-add input { flex: 1; min-width: 0; }

.lvl-orphans { margin-top: 1.75rem; padding-top: 1.25rem; border-top: 1px solid var(--border); }
.lvl-orphans h3 { font-size: .95rem; }
.lvl-orphans ul { list-style: none; margin: .85rem 0 0; padding: 0; display: grid; gap: .4rem; }
.lvl-orphans li {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .45rem .7rem; border: 1px dashed var(--border-strong); border-radius: 10px;
  font-size: .87rem;
}

/* --- Nav menus ----------------------------------------------------------
   <details> based, so they open with no JavaScript. Everything below is
   presentation over that: the arrow rotates, the panel floats, and app.js only
   closes them on Escape and on an outside click. */
.menu { position: relative; }
.menu > summary {
  display: flex; align-items: center; gap: .3rem;
  padding: .42rem .7rem; border-radius: 9px; cursor: pointer;
  font-size: .89rem; font-weight: 500; color: var(--text);
  white-space: nowrap; list-style: none; user-select: none;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover { background: var(--bg-sunken); color: var(--text-strong); }
.menu > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.menu > summary svg { width: 13px; height: 13px; opacity: .6; transition: transform .12s ease; }
.menu[open] > summary svg { transform: rotate(180deg); }
.menu[data-live] > summary { background: var(--accent-soft); color: var(--accent-soft-t); }

.menu-panel {
  position: absolute; top: calc(100% + .4rem); left: 0; z-index: 40;
  min-width: 220px; padding: .35rem;
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow-pop, 0 12px 30px rgba(0,0,0,.14));
}
.menu-right { left: auto; right: 0; }
.menu-panel a {
  display: block; padding: .5rem .6rem; border-radius: 8px;
  text-decoration: none; color: var(--text);
}
.menu-panel a:hover { background: var(--bg-sunken); }
.menu-panel a[aria-current] { background: var(--accent-soft); color: var(--accent-soft-t); }
.menu-panel b { display: block; font-size: .88rem; font-weight: 600; }
.menu-panel span { display: block; font-size: .76rem; color: var(--text-muted); margin-top: .1rem; }
.menu-out { border-top: 1px solid var(--border); margin-top: .35rem; padding-top: .35rem; }
.menu-out button {
  width: 100%; text-align: left; padding: .5rem .6rem; border: 0; border-radius: 8px;
  background: none; color: var(--text); font: inherit; font-size: .88rem;
  font-weight: 600; cursor: pointer;
}
.menu-out button:hover { background: var(--bg-sunken); }

/* Account menu */
.menu-you > summary { gap: .45rem; padding-left: .35rem; }
.you-av {
  width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-soft-t);
  font-size: .68rem; font-weight: 700; letter-spacing: .02em;
}
.you-nm { font-size: .84rem; color: var(--text-muted); max-width: 12ch;
          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The station is an action, not a page, so it sits with the controls. */
.station-go { display: inline-flex; align-items: center; gap: .4rem; }
.station-go svg { width: 15px; height: 15px; }

@media (max-width: 900px) { .you-nm { display: none; } }
@media (max-width: 860px) {
  /* In the stacked menu a floating panel has nothing to float over: let the
     children sit inline under their heading instead. */
  .menu-panel { position: static; min-width: 0; box-shadow: none; border: 0;
                padding: 0 0 0 .75rem; background: none; }
  .menu-right { padding-left: .75rem; }
  .station-go { width: 100%; justify-content: center; }
}

/* --- Locked add-ons ------------------------------------------------------
   Add-ons the org has not bought: a padlock in the nav, and a page that shows
   a blurred sketch of the feature with the way to get it laid over the top.
   The sketch is skeleton shapes (src/locked-view.php) — nothing real is drawn.
   The padlock is muted rather than red or warning-coloured: this is a door that
   is closed, not an error. #by claude */
.lk { width: .95em; height: .95em; flex: none; vertical-align: -.12em; margin-right: .35em; }
.nav > a.is-locked { color: var(--text-muted); display: inline-flex; align-items: center; }
.nav > a.is-locked:hover { color: var(--text-strong); }
.menu-lock > summary { color: var(--text-muted); }
.menu-lock > summary:hover { color: var(--text-strong); }
/* The summary's own arrow animates and fades; the padlock beside it must not. */
.menu > summary svg.lk { opacity: 1; width: .95em; height: .95em; margin-right: .1em; }
.menu[open] > summary svg.lk { transform: none; }
.menu-panel a.is-locked b { color: var(--text-muted); }
.menu-panel a.is-locked:hover b { color: var(--text-strong); }
.menu-panel .lk { margin-right: .4em; }
/* On the padlock page itself the lit entry keeps the ordinary highlight colour. */
.nav > a.is-locked[aria-current],
.menu-panel a.is-locked[aria-current] b { color: var(--accent-soft-t); }

.lk-stage {
  position: relative; isolation: isolate; overflow: hidden;
  display: grid; place-items: center;
  min-height: 34rem; padding: 2.5rem 1rem;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-sunken);
}
/* The blurred sketch. scale() pushes the soft, faded edge the blur produces
   outside the clipped box, so the picture reaches every side of the stage. */
.lk-preview {
  position: absolute; inset: 0; z-index: -1; padding: 1.25rem;
  filter: blur(6px); transform: scale(1.04);
  pointer-events: none; user-select: none;
}
/* A light wash over it so the card is the clear subject. */
.lk-stage::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--bg); opacity: .22; pointer-events: none;
}
.lk-card { position: relative; width: min(100%, 31rem); padding: 2rem 1.75rem 1.5rem; text-align: center; box-shadow: var(--shadow-lift); }
.lk-badge {
  width: 3.5rem; height: 3.5rem; margin: 0 auto 1rem; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-soft-t);
}
.lk-big { width: 1.55rem; height: 1.55rem; }
.lk-blurb { margin-top: .5rem; }
.lk-points { text-align: left; margin: 1.2rem auto 0; max-width: 24rem; }
.lk-note { margin: 1.2rem 0 0; font-size: .88rem; color: var(--text); }
.lk-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: .6rem; margin-top: 1.3rem; }
.lk-fine { margin: 1.25rem 0 0; font-size: .76rem; color: var(--text-muted); }
.lk-back { margin-top: 1rem; }

/* Skeleton shapes for the sketch. Muted tokens only, so it follows the theme. */
.pv { max-width: 62rem; margin: 0 auto; background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.pv-head { display: flex; align-items: center; gap: .8rem; padding: 1.1rem 1.2rem; border-bottom: 1px solid var(--border); }
.pv-head > .pv-bar:first-child { height: 1rem; }
.pv-head > .pv-bar + .pv-pill { margin-left: auto; }
.pv-row { display: flex; align-items: center; gap: 1rem; padding: .9rem 1.2rem; border-bottom: 1px solid var(--border); }
.pv-thead { background: var(--bg-sunken); padding-block: .6rem; }
.pv-col { flex: 1; display: grid; gap: .45rem; min-width: 0; }
.pv-bar { display: block; height: .62rem; border-radius: 99px; background: var(--text-muted); opacity: .3; }
.pv-bar.dk { opacity: .6; }
.pv-pill { display: block; flex: none; width: 4.2rem; height: 1.45rem; border-radius: 99px; background: var(--accent); opacity: .3; }
.pv-av { display: block; flex: none; width: 2.3rem; height: 2.3rem; border-radius: 50%; background: var(--accent); opacity: .3; }
.pv-row > .pv-bar { flex: none; }
.pv-week { display: grid; grid-template-columns: repeat(5, 1fr); gap: .8rem; padding: 1.2rem; }
.pv-day { display: grid; align-content: start; gap: .6rem; }
.pv-ev { display: block; border-radius: 10px; background: var(--accent); opacity: .28; }
.pv-ev.h1 { height: 3rem; }
.pv-ev.h2 { height: 4.6rem; }
.pv-ev.h3 { height: 6.4rem; }
.pv-langs { display: grid; grid-template-columns: repeat(3, 1fr); gap: .9rem; padding: 1.2rem; }
.pv-card { display: grid; gap: .55rem; padding: 1rem; border: 1px solid var(--border); border-radius: 12px; }
.pv-meter { display: block; height: .5rem; margin-top: .35rem; border-radius: 99px; background: var(--bg-sunken); overflow: hidden; }
.pv-fill { display: block; height: 100%; border-radius: 99px; background: var(--accent); }
.w20 { width: 20%; } .w30 { width: 30%; } .w40 { width: 40%; } .w50 { width: 50%; }
.w60 { width: 60%; } .w70 { width: 70%; } .w80 { width: 80%; }

@media (max-width: 620px) {
  .lk-stage { min-height: 0; padding: 1.5rem .75rem; }
  .pv-week { grid-template-columns: repeat(3, 1fr); }
  .pv-week .pv-day:nth-child(n+4) { display: none; }
  .pv-langs { grid-template-columns: repeat(2, 1fr); }
}

/* --- Days that were never closed ---------------------------------------- */
.unshut { margin-top: 1.25rem; border-color: var(--warn-br); }
.unshut .pad { padding-bottom: .75rem; }
.unshut-list { list-style: none; margin: 0; padding: 0 1.5rem 1.25rem; }
.unshut-list li {
  display: flex; align-items: center; gap: .8rem;
  padding: .7rem 0; border-top: 1px solid var(--border);
}
.unshut-who { flex: 1; min-width: 0; }
.unshut-who .nm { font-weight: 600; font-size: .92rem; }
.unshut-who .mt { font-size: .78rem; color: var(--text-muted); margin-top: .1rem; }
.unshut-fix { display: flex; align-items: center; gap: .4rem; }
.unshut-fix input[type="time"] { width: auto; padding: .3rem .5rem; font-size: .82rem; }
.unshut-tag {
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--warn-tx); background: var(--warn-bg); padding: .2rem .5rem; border-radius: 999px;
}
@media (max-width: 620px) {
  .unshut-list li { flex-wrap: wrap; }
  .unshut-fix { width: 100%; }
  .unshut-fix input[type="time"] { flex: 1; }
}

/* Visually hidden, still read aloud. */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --- Attendance ----------------------------------------------------------
   The chart is a fixed-viewBox SVG scaled by CSS, so nothing here measures
   anything at runtime and it prints at whatever size the paper is. */
.at-tools { display: flex; flex-direction: column; align-items: flex-end; gap: .5rem; }
.at-ranges { display: flex; gap: .3rem; flex-wrap: wrap; }
.at-ranges .is-on, .seg-opt.is-on {
  background: var(--accent-soft); color: var(--accent-soft-t); border-color: transparent;
}
.seg-sm { display: inline-flex; }
.seg-sm .seg-opt {
  padding: .3rem .8rem; font-size: .82rem; text-decoration: none; color: var(--text-muted);
  border-radius: 8px;
}

.at-custom {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-bottom: 1.25rem; font-size: .84rem; color: var(--text-muted);
}
.at-custom input[type="date"] { width: auto; padding: .32rem .55rem; font-size: .84rem; }
.at-custom label { margin: 0; font-weight: 500; }
.at-csv { margin-left: auto; }

.at-stat { margin-bottom: 1.25rem; }
.at-none { text-align: center; padding: 3rem 1.5rem; }

.chart-card { padding-bottom: 1rem; }
.chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
              flex-wrap: wrap; margin-bottom: 1rem; }
.chart-head .sub { margin: 0; }
.chart { position: relative; padding-left: 2rem; }
.chart svg { display: block; width: 100%; height: 220px; overflow: visible; }
.chart .grid { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.chart .bar { fill: var(--accent); }
.chart .bar.is-weekend { fill: var(--accent); opacity: .45; }
.chart .bar:hover { fill: var(--text-strong); }
.chart-y { position: absolute; left: 0; top: 0; bottom: 0; width: 2rem; }
.chart-y span {
  position: absolute; left: 0; transform: translateY(50%);
  font-size: .7rem; color: var(--text-muted); font-variant-numeric: tabular-nums;
}
.chart-x {
  display: flex; justify-content: space-between; padding-left: 2rem; margin-top: .5rem;
  font-size: .74rem; color: var(--text-muted);
}

.at-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; margin-top: 1.25rem; }
@media (max-width: 800px) {
  .at-cols { grid-template-columns: 1fr; }
  .at-tools { align-items: stretch; }
}

.wkbars { list-style: none; margin: 1rem 0 0; padding: 0; }
.wkbars li { display: flex; align-items: center; gap: .7rem; padding: .3rem 0; }
.wkbars-d { width: 2.4rem; font-size: .8rem; color: var(--text-muted); }
.wkbars-t { flex: 1; height: 9px; border-radius: 999px; background: var(--bg-sunken); overflow: hidden; }
.wkbars-t i { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.wkbars-n { width: 2.6rem; text-align: right; font-size: .8rem;
            font-variant-numeric: tabular-nums; color: var(--text-strong); }

.of { color: var(--text-muted); font-size: .8rem; }

/* A row mid-move. Brief, and only so a slow connection does not look frozen. */
.lvl-row.is-moving { opacity: .5; transition: opacity .1s ease; }

/* --- Form QR poster ------------------------------------------------------
   Sized for a sheet of paper first and a screen second: this exists to be
   taped to a desk. */
.qr-tools { display: flex; gap: .4rem; flex-wrap: wrap; }
.qr-poster {
  background: #fff; color: #14110f;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 3rem 2rem; text-align: center; max-width: 40rem; margin: 0 auto;
}
.qr-org { font-size: .8rem; font-weight: 700; text-transform: uppercase;
          letter-spacing: .12em; color: #6b625c; margin: 0 0 .75rem; }
.qr-title { font-size: 1.9rem; line-height: 1.15; margin: 0; color: #14110f; }
.qr-intro { font-size: .95rem; color: #514944; max-width: 26rem; margin: .75rem auto 0; }
.qr-code { margin: 2rem auto 1.5rem; width: min(280px, 70%); }
.qr-code svg { display: block; width: 100%; height: auto; shape-rendering: crispEdges; }
.qr-scan { font-size: 1rem; font-weight: 600; margin: 0 0 .4rem; color: #14110f; }
.qr-url { font-family: var(--mono, ui-monospace, monospace); font-size: .82rem;
          color: #6b625c; margin: 0; word-break: break-all; }
.qr-aside { text-align: center; margin-top: 1.5rem; }
.qr-aside .sub { margin: .75rem 0 0; }

@media print {
  /* The poster is the print-out. Everything else is the app around it. */
  .topbar, .app-foot, .head, .qr-aside, .banner, .note { display: none !important; }
  .wrap { padding: 0; max-width: none; }
  .qr-poster { border: 0; padding: 2rem 0; max-width: none; }
  .qr-code { width: 300px; }
}

/* ===========================================================================
   THE LIVE BOARD, FOURTH PASS — a departures board, not a dashboard
   ===========================================================================

   Three passes back this page was a two-column dashboard: a roster grid, a
   Material-styled activity panel, filter pills, an app bar, a FAB. The user's
   verdict was blunt — the layout itself didn't work, not the colours — so
   this pass starts over from a different reference entirely: an airport or
   train-station departures board. One flat list, grouped by day, arrival
   time always in the same column, a status word instead of a chip. Nothing
   is boxed inside anything else.

   Theme: user decision — the board follows the app's ordinary light/dark
   toggle rather than being pinned dark. Every colour below is one of the
   EXISTING semantic tokens (--bg, --tile-a-*, --warn-*, …), never a new hue,
   so the board is unmistakably still Classivo in both themes rather than a
   dark mode that only this one page has.

   Font: the departures-board character comes from a monospace column for
   times, durations and day headings — a system stack, not a webfont. There
   is no self-hosted monospace face (app/public/assets/fonts/ has Inter and
   Playfair only) and font-src is 'self' (app.css:28), so a webfont from
   anywhere else would be blocked with no visible error.

   Every class here is prefixed bd- (board-departures). Bare names like
   .row, .status and .who already mean something else in this file — .status
   is the form-publish badge, .who is the topbar's signed-in user — so reusing
   them would silently restyle those instead. See app/src/board-view.php for
   the renderer that emits this markup; both /board and /api/feed render
   through the same function, so the two pages can never draw a different
   shape of row for the same data.
   ------------------------------------------------------------------------- */

.bd-time, .bd-dur, .bd-counter b, .bd-day-name, .bd-mark, .bd-key-hint, .bd-labels, .bd-col-n {
  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, 'Liberation Mono', monospace;
}

/* --- sticky header: title/counters and search+filters -------------------- */
.bd-sticky {
  position: sticky; top: var(--bd-top, 0px); z-index: 15; background: var(--bg);
  padding: .25rem 0 .75rem; margin-top: -.25rem;
}
/* Only once something has actually scrolled underneath — a permanent shadow
   on a board short enough to need no scrolling is just noise. Toggled by
   app.js via an IntersectionObserver on a sentinel placed just above it. */
.bd-sticky.is-stuck { box-shadow: 0 8px 16px -12px rgba(84,56,42,.35); }
[data-theme="dark"] .bd-sticky.is-stuck { box-shadow: 0 8px 16px -12px rgba(0,0,0,.55); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bd-sticky.is-stuck { box-shadow: 0 8px 16px -12px rgba(0,0,0,.55); }
}

.bd-head {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 1.25rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border);
}
.bd-head h1 { margin-top: .1rem; }
.bd-live { display: inline-flex; align-items: center; gap: .4rem; font-size: .78rem; font-weight: 600; color: var(--tile-a-tx); }
.bd-live i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--brand-500); flex: none;
  animation: bd-pulse 2.2s ease-out infinite;
}
@keyframes bd-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand-500) 55%, transparent); }
  70%  { box-shadow: 0 0 0 8px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* --- counters, iconed — same tinting as the old .stat-tile (sage for the
   number that means "here right now", gold for the one that only grows) --- */
.bd-counters { display: flex; gap: .6rem; flex-wrap: wrap; }
.bd-counter {
  display: flex; align-items: center; gap: .6rem;
  min-width: 8.5rem; padding: .6rem .85rem;
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow-card);
}
.bd-icn {
  width: 30px; height: 30px; flex: none; border-radius: 8px;
  display: grid; place-items: center; background: var(--bg-sunken); color: var(--text-muted);
}
.bd-icn svg { width: 16px; height: 16px; }
.bd-counter b {
  display: block; font-size: 1.3rem; line-height: 1.05; font-weight: 700;
  letter-spacing: -.02em; color: var(--text-strong); font-variant-numeric: tabular-nums;
}
.bd-lbl {
  display: block; margin-top: .1rem; font-size: .64rem; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-muted); font-family: var(--font-sans);
}
.bd-counter-on .bd-icn    { background: var(--tile-a-bg); color: var(--tile-a-tx); }
.bd-counter-on b          { color: var(--tile-a-tx); }
.bd-counter-today .bd-icn { background: var(--tile-b-bg); color: var(--tile-b-tx); }
.bd-counter-today b       { color: var(--tile-b-tx); }

/* --- search + grade/center filters --------------------------------------- */
.bd-toolbar { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; padding-top: .85rem; }
.bd-search {
  display: flex; align-items: center; gap: .5rem; flex: 1; min-width: 12rem;
  background: var(--bg-raised); border: 1px solid var(--border); border-radius: 10px; padding: .5rem .7rem;
}
.bd-search:focus-within { border-color: var(--accent); outline: 2px solid var(--accent); outline-offset: 1px; }
.bd-search svg { width: 15px; height: 15px; color: var(--text-muted); flex: none; }
.bd-search input { all: unset; flex: 1; min-width: 0; font-family: var(--font-sans); font-size: .86rem; color: var(--text); }
.bd-search input::placeholder { color: var(--text-muted); }
.bd-key-hint {
  font-size: .68rem; color: var(--text-muted); border: 1px solid var(--border-strong);
  border-radius: 5px; padding: .05rem .4rem; flex: none;
}
.bd-key-hint[hidden] { display: none; }
.bd-clear { all: unset; cursor: pointer; color: var(--text-muted); display: grid; place-items: center; width: 18px; height: 18px; flex: none; }
.bd-clear svg { width: 12px; height: 12px; }
.bd-clear:hover { color: var(--text-strong); }
.bd-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.bd-clear[hidden] { display: none; }

.bd-fselect {
  font-family: var(--font-sans); font-size: .82rem; color: var(--text);
  background: var(--bg-raised); border: 1px solid var(--border); border-radius: 10px;
  padding: .5rem 1.7rem .5rem .7rem; cursor: pointer;
}
.bd-fselect:hover { border-color: var(--border-strong); }

.bd-clear-filters {
  font-family: var(--font-sans); font-size: .8rem; font-weight: 600; cursor: pointer;
  border: 1px solid var(--border-strong); background: var(--bg-raised); color: var(--text-strong);
  border-radius: 999px; padding: .5rem 1rem;
}
.bd-clear-filters:hover { background: var(--bg-sunken); }
.bd-clear-filters[hidden] { display: none; }

/* --- day groups, collapsible ---------------------------------------------- */
.bd-day-grp { border-top: 1px solid var(--border); }
.bd-day-grp:last-child { border-bottom: 1px solid var(--border); }
.bd-day-grp[hidden] { display: none; }

.bd-day-head {
  all: unset; box-sizing: border-box; cursor: pointer;
  display: flex; align-items: center; gap: .6rem; width: 100%; padding: .7rem .25rem;
}
.bd-day-head:hover .bd-day-name { color: var(--text-strong); }
.bd-day-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.bd-chev { width: 16px; height: 16px; flex: none; display: grid; place-items: center; color: var(--text-muted); transition: transform .18s ease; }
.bd-chev svg { width: 11px; height: 11px; }
.bd-day-grp[data-open="false"] .bd-chev { transform: rotate(-90deg); }
.bd-day-name { font-size: .68rem; letter-spacing: .09em; text-transform: uppercase; color: var(--text-muted); }
.bd-day-count { margin-left: auto; font-size: .72rem; color: var(--text-muted); font-variant-numeric: tabular-nums; font-family: var(--font-sans); }
.bd-day-badge {
  font-size: .62rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  background: var(--warn-bg); color: var(--warn-tx); padding: .12rem .5rem; border-radius: 999px;
  font-family: var(--font-sans);
}

.bd-day-grp[data-open="false"] .bd-day-rows { display: none; }
/* A search match inside a COLLAPSED day still has to show — app.js adds this
   class to a group that has visible matches while the admin left it closed.
   Collapse governs browsing; it never hides a result someone searched for. */
.bd-day-grp.bd-force-open[data-open="false"] .bd-day-rows { display: block; }
.bd-day-grp.bd-force-open[data-open="false"] .bd-chev { opacity: .4; }

/* --- rows ------------------------------------------------------------------
   The time column always shows ARRIVAL time in every state — the one fact
   every row has. Everything a state adds or replaces lives in .bd-sub or
   .bd-dur instead of a second time column, which is what kept this list to
   one line per row on a tablet-width screen. */
.bd-row {
  display: grid; grid-template-columns: 1.2rem 4.4rem 1fr 4rem 5.2rem; gap: .75rem; align-items: center;
  padding: .55rem .25rem;
}
.bd-row[hidden] { display: none; }
.bd-row + .bd-row { box-shadow: inset 0 1px 0 var(--border); }
.bd-row:hover { background: var(--bg-sunken); border-radius: 8px; }

.bd-mark { font-size: .8rem; line-height: 1; text-align: center; }
.bd-mark-on   { color: var(--tile-a-tx); }
.bd-mark-done { color: var(--border-strong); }
.bd-mark-fix  { color: var(--warn-tx); }

.bd-time { font-weight: 700; font-size: .86rem; color: var(--text-strong); white-space: nowrap; }

.bd-who { min-width: 0; }
.bd-nm { font-size: .87rem; font-weight: 500; color: var(--text-strong); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bd-nm mark { background: var(--warn-bg); color: var(--warn-tx); border-radius: 3px; padding: 0 .1rem; }
.bd-sub { font-size: .74rem; color: var(--text-muted); margin-top: .05rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.bd-dur { font-size: .76rem; color: var(--text-muted); text-align: right; white-space: nowrap; }

.bd-status { font-size: .66rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; text-align: right; white-space: nowrap; font-family: var(--font-sans); }
.bd-status-on   { color: var(--tile-a-tx); }
.bd-status-done { color: var(--text-muted); }
.bd-status-fix  { color: var(--warn-tx); }

@media (max-width: 560px) {
  .bd-row { grid-template-columns: 1.2rem 3.8rem 1fr 5.2rem; row-gap: .3rem; }
  .bd-row .bd-dur { display: none; }
}

/* --- TODAY, as two columns ------------------------------------------------
   Left: in class right now. Right: already checked out. The two are what a
   front desk actually asks ("who is here?" / "who just left?"), so they are
   separate cards rather than one list with a status word on every row. The
   left card's header is tinted the same sage as the "on site" counter, so the
   number up top and the column below read as one thing. */
.bd-cols2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; padding: .1rem 0 1rem; align-items: start; }
@media (max-width: 860px) { .bd-cols2 { grid-template-columns: minmax(0, 1fr); } }

.bd-col {
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-card); overflow: hidden;
}
.bd-col-head {
  display: flex; align-items: center; gap: .6rem; padding: .75rem 1rem;
  background: var(--bg-sunken); border-bottom: 1px solid var(--border);
}
.bd-col-in .bd-col-head { background: var(--tile-a-bg); border-bottom-color: var(--tile-a-br); }
.bd-col-head h2 { font-size: .95rem; font-weight: 600; letter-spacing: -.01em; }
.bd-col-in .bd-col-head h2 { color: var(--tile-a-tx); }
.bd-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.bd-dot-in  { background: var(--tile-a-tx); }
.bd-dot-out { border: 2px solid var(--border-strong); }
.bd-col-n {
  margin-left: auto; padding: .1rem .6rem; border-radius: 999px;
  font-size: .78rem; font-weight: 700; font-variant-numeric: tabular-nums;
  background: var(--bg-raised); border: 1px solid var(--border-strong); color: var(--text-strong);
}
.bd-col-in .bd-col-n { border-color: var(--tile-a-br); color: var(--tile-a-tx); }

/* Label row and data rows share ONE grid, or the labels stop lining up. */
.bd-labels, .bd-r {
  display: grid; grid-template-columns: 4.2rem minmax(0, 1fr) auto; gap: .75rem; align-items: center;
  padding-inline: 1rem;
}
.bd-labels {
  padding-block: .55rem .25rem; font-size: .62rem; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text-muted);
}
.bd-labels span:last-child { text-align: right; }
.bd-r { padding-block: .55rem; }
.bd-r[hidden] { display: none; }
.bd-r + .bd-r { box-shadow: inset 0 1px 0 var(--border); }
.bd-r:hover { background: var(--bg-sunken); }
.bd-dur-on { color: var(--tile-a-tx); font-weight: 600; }
/* Still here past their enrolled time: the name and "here for" go red, and the
   line under the name says why (board_in_row_html()). Not the amber the "needs
   fixing" rows use — that one means a record is wrong, this one means a child is
   still in the room. */
.bd-over .bd-nm, .bd-over .bd-dur-on { color: var(--bad-tx); }
.bd-over .bd-nm { font-weight: 700; }
.bd-over .bd-sub { color: var(--bad-tx); }
.bd-sub:empty { display: none; }
.bd-col .bd-empty { padding: 2rem 1rem; }
.bd-col .bd-empty[hidden] { display: none; }

/* --- the fix, inline on the row that is wrong, not a separate card ------- */
.bd-fixbar {
  grid-column: 1 / -1; display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  background: var(--warn-bg); border: 1px solid var(--warn-br); border-radius: 10px;
  padding: .55rem .7rem; margin-top: .2rem;
}
.bd-fixbar-txt { flex: 1; min-width: 11rem; font-size: .78rem; color: var(--warn-tx); }
.bd-fixbar input[type="time"] { width: auto; padding: .3rem .5rem; font-size: .82rem; background: var(--bg-raised); }
.bd-fixbar-btn {
  font: inherit; font-size: .78rem; font-weight: 600; cursor: pointer;
  padding: .38rem .9rem; border-radius: 999px;
  border: 1px solid var(--border-strong); background: var(--bg-raised); color: var(--text-strong);
}
.bd-fixbar-btn:hover { background: var(--bg-sunken); }

.bd-empty, .bd-no-results { padding: 2.5rem 1rem; text-align: center; color: var(--text-muted); font-size: .9rem; }
.bd-no-results[hidden] { display: none; }

/* Small screens: the whole header (title + counters + search) is far too tall to
   pin, and would leave a sliver of list to scroll under. Only the search bar
   sticks; the title and counters scroll away normally. */
@media (max-width: 860px) {
  .bd-sticky { position: static; padding-bottom: 0; }
  .bd-toolbar { position: sticky; top: var(--bd-top, 0px); z-index: 15; background: var(--bg); padding-block: .6rem; }
}
@media (max-width: 560px) {
  .bd-counters { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .4rem; width: 100%; }
  .bd-counter { min-width: 0; padding: .5rem .55rem; }
  .bd-icn { display: none; }
  .bd-r .bd-sub { white-space: normal; }
}
@media (hover: none) { .bd-key-hint { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .bd-chev { transition: none; }
  .bd-live i { animation: none; }
}


/* ===========================================================================
 * Help Desk — conversations with the Classivo team. #by claude
 * Tokens only (no raw colours) so the dark theme works without a second block.
 * ========================================================================= */
.hd-nav-n { display: inline-block; min-width: 1.15rem; padding: 0 .35rem; margin-left: .25rem; border-radius: 999px;
  background: var(--brand-500); color: #fff; font-size: .66rem; font-weight: 700; line-height: 1.15rem; text-align: center; }
.hd-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.hd-filters { display: flex; gap: .4rem; margin-bottom: 1rem; flex-wrap: wrap; }
.hd-filters a { padding: .35rem .8rem; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-raised);
  font-size: .8rem; font-weight: 600; color: var(--text); text-decoration: none; }
.hd-filters a[aria-current] { background: var(--accent-soft); color: var(--accent-soft-t); border-color: transparent; }
.hd-list { display: grid; overflow: hidden; }
.hd-row { display: grid; grid-template-columns: auto 1fr auto; gap: 1rem; align-items: center; padding: 1rem 1.4rem;
  border-top: 1px solid var(--border); text-decoration: none; color: inherit; }
.hd-row:first-child { border-top: 0; }
.hd-row:hover { background: var(--bg-sunken); }
.hd-row .t { font-weight: 600; color: var(--text-strong); font-size: .95rem; }
.hd-row.is-unread .t { font-weight: 700; }
.hd-row .m { font-size: .78rem; color: var(--text-muted); margin-top: .15rem; }
.hd-row .pv { font-size: .84rem; color: var(--text); margin-top: .25rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46rem; }
.hd-row .r { text-align: right; font-size: .75rem; color: var(--text-muted); display: grid; gap: .35rem; justify-items: end; }
.hd-dot { width: .6rem; height: .6rem; border-radius: 50%; background: var(--brand-500); display: inline-block; }
.hd-dot.off { background: transparent; border: 1.5px solid var(--border-strong); }
.hd-st { font-size: .66rem; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; padding: .18rem .5rem; border-radius: 5px; white-space: nowrap; }
.hd-st-open { background: var(--tile-a-bg); color: var(--tile-a-tx); }
.hd-st-waiting { background: var(--accent-soft); color: var(--accent-soft-t); }
.hd-st-resolved { background: var(--bg-sunken); color: var(--text-muted); }
.hd-layout { display: grid; grid-template-columns: minmax(0, 1fr) 19rem; gap: 1.25rem; align-items: start; }
.hd-thread { display: grid; gap: 1rem; padding: 1.4rem; }
.hd-msg { display: flex; gap: .8rem; max-width: 42rem; }
.hd-msg.me { margin-left: auto; flex-direction: row-reverse; }
.hd-bub { background: var(--bg-sunken); border: 1px solid var(--border); border-radius: 14px; padding: .8rem 1rem; font-size: .92rem;
  color: var(--text-strong); white-space: pre-wrap; overflow-wrap: anywhere; }
.hd-msg.me .hd-bub { background: var(--accent-soft); border-color: transparent; color: var(--accent-soft-t); }
.hd-who { font-size: .74rem; color: var(--text-muted); margin-bottom: .3rem; }
.hd-who b { color: var(--text-strong); }
.hd-msg.me .hd-who { text-align: right; }
.hd-sys { justify-self: center; font-size: .74rem; color: var(--text-muted); background: var(--bg-sunken); border-radius: 999px; padding: .25rem .8rem; }
.hd-file { display: inline-block; margin-top: .5rem; font-size: .8rem; font-weight: 600; }
.hd-reply { border-top: 1px solid var(--border); padding: 1.1rem 1.4rem; }
.hd-reply textarea { min-height: 5.5rem; width: 100%; }
.hd-bar { display: flex; justify-content: space-between; align-items: center; margin-top: .7rem; gap: 1rem; flex-wrap: wrap; }
.hd-acts { display: flex; gap: .5rem; flex-wrap: wrap; }
.hd-side dl { margin: 0; display: grid; gap: .8rem; font-size: .86rem; }
.hd-side dt { font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); font-weight: 700; }
.hd-side dd { margin: .15rem 0 0; color: var(--text-strong); }
.hd-side h3 { font-size: .95rem; margin-bottom: .7rem; }
.hd-cats { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: .6rem; }
.hd-cat { display: block; border: 1px solid var(--border); border-radius: 12px; padding: .8rem .9rem; cursor: pointer; background: var(--bg-raised); margin: 0; }
.hd-cat b { display: block; font-size: .88rem; color: var(--text-strong); }
.hd-cat span { font-size: .75rem; color: var(--text-muted); font-weight: 400; }
.hd-cat input { position: absolute; opacity: 0; pointer-events: none; }
.hd-cat:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.hd-cat:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.hd-lbl { display: block; font-weight: 600; font-size: .82rem; margin-bottom: .35rem; color: var(--text-strong); }
.hd-hint { font-size: .76rem; color: var(--text-muted); margin-top: .3rem; }
.hd-end { display: flex; gap: .6rem; justify-content: flex-end; }
@media (max-width: 900px) {
  .hd-layout { grid-template-columns: 1fr; }
  .hd-row { grid-template-columns: auto 1fr; }
  .hd-row .r { grid-column: 2; justify-items: start; text-align: left; }
}
.hd-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
.hd-mono { font-family: var(--font-mono); }

/* Help Desk — "request sent" page. #by claude */
.hd-done { max-width: 34rem; margin: 2rem auto; text-align: center; }
.hd-done-ic { width: 4rem; height: 4rem; margin: 0 auto 1.1rem; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-soft-t); }
.hd-done h1 { margin-bottom: .5rem; }
.hd-done .sub { max-width: 28rem; margin-left: auto; margin-right: auto; }
.hd-done-box { text-align: left; margin: 1.5rem 0 1.25rem; }
.hd-done-box dl { margin: 0; display: grid; gap: .7rem; font-size: .88rem; }
.hd-done-box dt { font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); font-weight: 700; }
.hd-done-box dd { margin: .15rem 0 0; color: var(--text-strong); }
.hd-done-next { text-align: left; font-size: .88rem; color: var(--text); margin: 0 0 1.5rem; padding-left: 1.1rem; }
.hd-done-next li + li { margin-top: .35rem; }
.hd-done-acts { display: flex; gap: .6rem; justify-content: center; flex-wrap: wrap; }
.hd-done-demo { margin-top: 1.25rem; font-size: .78rem; color: var(--text-muted); }

/* One center per customer: its name, shown in the top bar. #by claude */
.bar-center { display: inline-flex; align-items: center; gap: .35rem; max-width: 15rem; padding: .3rem .65rem; border-radius: 999px;
  background: var(--bg-sunken); color: var(--text-muted); font-size: .78rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bar-center svg { width: 14px; height: 14px; flex: none; }
.hd-center-name { margin: 0; padding: .55rem 0; font-weight: 600; color: var(--text-strong); }
@media (max-width: 900px) { .bar-center { display: none; } }

/* ===========================================================================
 * Notifications: the bell, banner, history and What's new. #by claude
 * ========================================================================= */
.nt-menu { position: relative; }
.nt-menu > summary.nt-bell { list-style: none; position: relative; width: 2.25rem; height: 2.25rem; padding: 0; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg-raised); color: var(--text); display: grid; place-items: center; cursor: pointer; }
.nt-menu > summary.nt-bell::-webkit-details-marker { display: none; }
.nt-menu > summary.nt-bell svg { width: 18px; height: 18px; }
.nt-menu > summary.nt-bell:hover, .nt-menu[open] > summary.nt-bell { border-color: var(--border-strong); color: var(--text-strong); }
.nt-count { position: absolute; top: -.35rem; right: -.35rem; min-width: 1.1rem; height: 1.1rem; padding: 0 .3rem; border-radius: 999px;
  background: var(--brand-500); color: #fff; font-size: .64rem; font-weight: 700; display: grid; place-items: center; border: 2px solid var(--bg-raised); }
.nt-panel { position: absolute; right: 0; top: calc(100% + .5rem); width: 25rem; max-width: 92vw; background: var(--bg-raised);
  border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--shadow-lift); z-index: 40; overflow: hidden; }
.nt-head { display: flex; justify-content: space-between; align-items: center; padding: .85rem 1.1rem .4rem; }
.nt-head b { color: var(--text-strong); font-size: .95rem; }
.nt-head form { margin: 0; }
.nt-link { background: none; border: 0; padding: 0; font: inherit; font-size: .78rem; font-weight: 600; color: var(--accent); cursor: pointer; }
.nt-link:hover { text-decoration: underline; }
.nt-radio { position: absolute; opacity: 0; pointer-events: none; }
.nt-tabs { display: flex; gap: .25rem; padding: 0 .8rem; border-bottom: 1px solid var(--border); }
.nt-tabs label { padding: .5rem .7rem; font-size: .82rem; font-weight: 600; color: var(--text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer; }
.nt-tabs i { font-style: normal; background: var(--accent-soft); color: var(--accent-soft-t); border-radius: 999px; padding: 0 .4rem; font-size: .68rem; margin-left: .3rem; }
#nt-t1:checked ~ .nt-tabs label[for="nt-t1"], #nt-t2:checked ~ .nt-tabs label[for="nt-t2"] { color: var(--accent); border-color: var(--accent); }
#nt-t1:focus-visible ~ .nt-tabs label[for="nt-t1"], #nt-t2:focus-visible ~ .nt-tabs label[for="nt-t2"] { outline: 2px solid var(--accent); outline-offset: -2px; }
.nt-list { max-height: 22rem; overflow: auto; }
.nt-pane { display: none; }
#nt-t1:checked ~ .nt-list .p1, #nt-t2:checked ~ .nt-list .p2 { display: block; }
.nt-item { display: grid; grid-template-columns: auto 1fr auto; gap: .75rem; padding: .8rem 1.1rem; border-bottom: 1px solid var(--border);
  text-decoration: none; color: inherit; align-items: start; }
.nt-item:last-child { border-bottom: 0; }
.nt-item:hover { background: var(--bg-sunken); }
.nt-item.unread { background: color-mix(in srgb, var(--accent-soft) 55%, transparent); }
.nt-item.is-target { outline: 2px solid var(--accent); outline-offset: -2px; }
.nt-ic { width: 2.1rem; height: 2.1rem; border-radius: 10px; display: grid; place-items: center; }
.nt-ic svg { width: 17px; height: 17px; }
.ic-help { background: var(--accent-soft); color: var(--accent-soft-t); }
.ic-addon { background: var(--tile-b-bg); color: var(--tile-b-tx); }
.ic-maint { background: var(--warn-bg); color: var(--warn-tx); }
.ic-update { background: var(--tile-a-bg); color: var(--tile-a-tx); }
.ic-notice { background: var(--bg-sunken); color: var(--text-muted); }
.ic-incident { background: var(--bad-bg); color: var(--bad-tx); }
.nt-t { font-size: .86rem; font-weight: 600; color: var(--text-strong); line-height: 1.35; }
.nt-s { font-size: .78rem; color: var(--text); margin-top: .15rem; overflow-wrap: anywhere; }
.nt-full { font-size: .86rem; white-space: pre-wrap; margin-top: .35rem; }
.nt-tag { display: inline-block; font-size: .62rem; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; padding: .1rem .4rem; border-radius: 5px; margin-right: .35rem; vertical-align: 1px; }
.tg-maintenance { background: var(--warn-bg); color: var(--warn-tx); }
.tg-update { background: var(--tile-a-bg); color: var(--tile-a-tx); }
.tg-notice { background: var(--bg-sunken); color: var(--text-muted); }
.tg-incident { background: var(--bad-bg); color: var(--bad-tx); }
.nt-meta { text-align: right; }
.nt-time { font-size: .72rem; color: var(--text-muted); white-space: nowrap; }
.nt-dot { width: .55rem; height: .55rem; border-radius: 50%; background: var(--brand-500); display: inline-block; margin-top: .35rem; }
.nt-empty { padding: 1.6rem 1.1rem; text-align: center; font-size: .86rem; color: var(--text-muted); }
.nt-foot { padding: .7rem 1.1rem; border-top: 1px solid var(--border); text-align: center; font-size: .82rem; font-weight: 600; background: var(--bg-sunken); }
.nt-banner { display: flex; gap: .8rem; align-items: center; padding: .65rem 1.5rem; background: var(--warn-bg); color: var(--warn-tx);
  border-bottom: 1px solid var(--warn-br); font-size: .86rem; }
.nt-banner.bad { background: var(--bad-bg); color: var(--bad-tx); border-color: var(--bad-br); }
.nt-banner > svg { width: 18px; height: 18px; flex: none; }
.nt-banner .sp { flex: 1; }
.nt-banner a { font-weight: 600; color: inherit; text-decoration: underline; white-space: nowrap; }
.nt-banner form { margin: 0; }
.nt-banner button { background: transparent; border: 0; color: inherit; font-size: 1.2rem; line-height: 1; cursor: pointer; padding: .1rem .4rem; border-radius: 6px; }
.nt-banner button:hover { background: rgba(0, 0, 0, .08); }
/* The free-trial banner (trial_banner_html): the incident banner's look, much slimmer, because it sits above every page. */
.nt-banner.trial { padding: .22rem 1rem; font-size: .78rem; gap: .5rem; line-height: 1.35; }
.nt-banner.trial button { font-size: 1.05rem; padding: 0 .35rem; }
.nt-filters { display: flex; gap: .4rem; margin: 0 0 1rem; flex-wrap: wrap; }
.nt-filters a { padding: .35rem .8rem; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-raised); font-size: .8rem; font-weight: 600; color: var(--text); text-decoration: none; }
.nt-filters a[aria-current] { background: var(--accent-soft); color: var(--accent-soft-t); border-color: transparent; }
.nt-day { font-size: .7rem; text-transform: uppercase; letter-spacing: .09em; font-weight: 700; color: var(--text-muted); margin: 1.25rem 0 .5rem; }
.nt-card { overflow: hidden; }
.nt-rel .card + .card { margin-top: 1rem; }
.nt-rel .v { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.nt-rel h2 { font-size: 1.05rem; }
.nt-rel .d { font-size: .78rem; color: var(--text-muted); }
.nt-rel ul { margin: .6rem 0 0; padding-left: 1.1rem; color: var(--text); font-size: .9rem; }
.nt-rel li + li { margin-top: .3rem; }
.app-foot-ver { margin-left: auto; }
@media (max-width: 700px) { .nt-banner { padding: .6rem 1rem; } .nt-panel { position: fixed; left: .5rem; right: .5rem; top: 4rem; width: auto; max-width: none; } }

/* Check-in station: PIN screen and camera buttons. #by claude */
.station .pin-form { display: flex; gap: .6rem; justify-content: center; margin: 1.4rem auto 0; max-width: 20rem; }
.station .pin-form input { flex: 1; min-width: 0; text-align: center; font-size: 1.4rem; letter-spacing: .3em; padding: .6rem .8rem; }
.station .pin-error { margin: 1rem 0 0; color: #F19B9B; font-size: .95rem; }
.station .cam-actions { display: flex; gap: .6rem; justify-content: center; margin-top: 1rem; flex-wrap: wrap; }
.station .cam-actions[hidden], .station .cam-actions > [hidden] { display: none; }

/* The bell, lit and ringing. #by claude */
.nt-menu > summary.nt-bell.has-unread { background: var(--accent-soft); border-color: var(--brand-300); color: var(--accent); }
.nt-menu > summary.nt-bell.nt-ring { box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand-500) 30%, transparent); }
.nt-menu > summary.nt-bell.nt-ring svg { animation: nt-ring 1s ease-in-out 2; transform-origin: 50% 10%; }
.nt-count.nt-pop { animation: nt-pop .5s ease-out; }
@keyframes nt-ring { 0%, 100% { transform: rotate(0); } 15% { transform: rotate(16deg); } 30% { transform: rotate(-13deg); }
  45% { transform: rotate(10deg); } 60% { transform: rotate(-7deg); } 75% { transform: rotate(3deg); } }
@keyframes nt-pop { 0% { transform: scale(.4); } 60% { transform: scale(1.35); } 100% { transform: scale(1); } }
.nt-foot { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.nt-foot .nt-sound { color: var(--text-muted); font-weight: 600; }
@media (prefers-reduced-motion: reduce) { .nt-menu > summary.nt-bell.nt-ring svg, .nt-count.nt-pop { animation: none; } }

/* Show / hide toggle on password fields (pwreveal.js). #by claude */
.pw-wrap { position: relative; display: block; }
.pw-wrap > input { display: block; padding-inline-end: 2.75rem; }
.pw-wrap > input::-ms-reveal, .pw-wrap > input::-ms-clear { display: none; }
.pw-toggle {
  position: absolute; inset-block: 1px; inset-inline-end: 1px; width: 2.5rem;
  display: grid; place-items: center; padding: 0; margin: 0;
  background: none; border: 0; border-start-end-radius: 9px; border-end-end-radius: 9px;
  color: var(--text-muted); cursor: pointer; transition: color .15s;
}
.pw-toggle:hover { color: var(--text-strong); }
.pw-toggle:focus-visible { outline-offset: -3px; }
.pw-toggle svg { display: block; }
.pw-toggle .pw-eye-off, .pw-wrap.is-shown .pw-eye { display: none; }
.pw-wrap.is-shown .pw-eye-off { display: block; }

/* Plan & billing (billing.php, billing-add.php). #by claude */
.bl-card + .bl-card { margin-top: 1.25rem; }
.bl-list { list-style: none; margin: 1rem 0 0; padding: 0; }
.bl-row { display: flex; align-items: center; gap: 1rem; padding: .8rem 0; border-top: 1px solid var(--border); }
.bl-row:first-child { border-top: 0; }
.bl-name { flex: 1 1 auto; min-width: 0; }
.bl-name b { display: block; font-weight: 600; color: var(--text-strong); }
.bl-name span { display: block; margin-top: .1rem; font-size: .8rem; color: var(--text-muted); }
.bl-price { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text-strong); font-weight: 600; }
.bl-row form { margin: 0; }
.bl-total { border-top: 2px solid var(--border-strong); }
.bl-total .bl-price { font-size: 1.05rem; }
.bl-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
.bl-card .hint { margin: .85rem 0 0; font-size: .8rem; color: var(--text-muted); }
@media (max-width: 560px) { .bl-row { flex-wrap: wrap; } .bl-name { flex-basis: 100%; } .bl-price { margin-right: auto; } }
