/*
 * 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;
}

[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; }
}

.wrap { max-width: 74rem; 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; } }

.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; }

/* 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); }
.code { font-family: var(--font-mono); font-size: .8rem; color: var(--text-muted); }

/* --- Live board ---------------------------------------------------------- */

.board { display: grid; grid-template-columns: 1fr 23rem; gap: 1.5rem; align-items: start; }
@media (max-width: 950px) { .board { grid-template-columns: 1fr; } }

.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; }

.feed { max-height: 70vh; overflow: auto; }
.ev { display: flex; gap: .7rem; align-items: center; padding: .65rem 1.25rem; border-bottom: 1px solid var(--border); animation: fade-up .3s ease-out; }
.ev:last-child { border-bottom: 0; }
.dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.dot-in { background: var(--brand-500); } .dot-out { background: var(--text-muted); }
.ev .nm { font-size: .88rem; font-weight: 500; color: var(--text-strong); }
.ev .mt { font-size: .75rem; color: var(--text-muted); }
.ev time { margin-left: auto; font-size: .73rem; color: var(--text-muted); white-space: nowrap; }

.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; }
.live { display: inline-flex; align-items: center; gap: .4rem; font-size: .78rem; color: var(--text-muted); }
.live i { width: 7px; height: 7px; border-radius: 50%; background: var(--brand-500); animation: pulse 2s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

/* --- 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) --------------------------------------------------------- */

.badge-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 1rem; }
.badge {
  background: #fff; border: 1px solid var(--border); border-radius: 14px;
  padding: 1.1rem; text-align: center; box-shadow: var(--shadow-card); break-inside: avoid;
}
/* 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 { width: 100%; max-width: 168px; margin: 0 auto .75rem; display: block; }
.badge .qr svg { width: 100%; height: auto; display: block; }
.badge .nm { font-weight: 600; color: var(--ink-950); font-size: .97rem; line-height: 1.3; }
.badge .mt { color: var(--ink-500); font-size: .76rem; margin-top: .15rem; }
.badge .id { font-family: var(--font-mono); font-size: 1.05rem; font-weight: 700; color: var(--brand-700); letter-spacing: .06em; margin-top: .4rem; }
.badge .org { font-size: .66rem; color: var(--ink-400); margin-top: .5rem; text-transform: uppercase; letter-spacing: .09em; }

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

/* 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 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); }

/* 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; }

@media print {
  .topbar, .banner, .head, .no-print, .btn, .modal-head, .modal-foot { display: none !important; }

  /* 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. */
  .printing-badge .wrap,
  .printing-badge .app-foot,
  .printing-badge dialog.modal { display: none !important; }
  .printing-badge .print-area { display: block; }
  .print-area .badge { width: 62mm; margin: 0 auto; }
  body { background: #fff; }
  .wrap { padding: 0; max-width: none; }
  .badge-grid { grid-template-columns: repeat(3, 1fr); gap: .5rem; }
  .badge { box-shadow: none; border: 1px dashed #B2BACB; page-break-inside: avoid; }
  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%); }

.live i { box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 22%, transparent); }

/* --- 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.
   =========================================================================== */

.board-head {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 1.25rem; margin-bottom: 1.5rem;
  padding-bottom: 1.25rem; border-bottom: 1px solid var(--border);
}
.board-head h1 { margin-top: .1rem; }

/* 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 activity rail is the only thing on screen when the building is empty,
   so it should not look like a leftover. */
.feed time { font-variant-numeric: tabular-nums; }

/* --- 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); }

/* When the building is empty the board is the only thing on screen, so it
   takes the height it is given rather than huddling at the top. */
.board { min-height: min(28rem, 55dvh); }
.board > section { display: flex; flex-direction: column; }
.empty-state { flex: 1; display: flex; flex-direction: column; justify-content: center; }

/* 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; }

/* Tried centring the whole page when the board is empty: it pushed the header
   into the middle of the screen, which is worse — a page title belongs at the
   top. The footer is what closes the page; the board just takes its height. */
.board { min-height: 0; }

/* ===========================================================================
   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; }
.addon-note { font-size: .8rem; color: var(--text-muted); }
.addon-footnote { margin-top: 1.5rem; max-width: 44rem; }

/* Building a customer's forms for them is part of what they bought, so the way
   to ask for it sits on the forms page rather than only on the marketing site. */
.build-for-you {
  margin-top: 1.5rem; display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 1rem;
  background:
    radial-gradient(120% 140% at 0% 0%, var(--accent-soft) 0%, transparent 60%),
    var(--bg-raised);
}
.build-for-you h2 { font-size: 1rem; }
.build-for-you .sub { margin-top: .35rem; max-width: 42rem; }

/* ===========================================================================
   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; }
}

/* --- 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; }
}
