/* NxtBell — the one stylesheet.
   Brand tokens from CLAUDE.md as CSS variables, the "Block" button style (decided 2026-09-01),
   inputs with the orange focus ring, and the handful of components the pages need.
   Mobile first: one 560px column that is the full width on phones, 44px tap targets, safe-area
   insets, and nothing that can scroll sideways. No framework, no CDN, no icon font.

   From 900px up ("use the screen", 2026-09-02) the same pages become a desktop product: a
   1180px column, Sora for headings, a hero and card grid on Home, a drop zone, a week grid on
   Review (from 1000px) with the forms in a sticky right column, two-column Download and Admin.
   Every one of those rules sits under a min-width query at the END of this file, so a phone
   renders exactly as it did before; the only shared new pieces are the login split (which the
   brief changed on the phone too: an Ink strip with the logo) and the fade/lift motion, itself
   fenced by prefers-reduced-motion. */

/* ---------- Sora, self-hosted (2026-09-02) ----------
   The heading face used from 900px up. It was a <link> to Google's font servers in every page's
   <head>, which meant every visitor's IP reached Google on every page load — on a phone as well
   as a laptop, and before anyone had logged in or agreed to anything. Serving the files
   ourselves ends that, so the privacy notice has nothing to disclose about it.

   Sora is a VARIABLE font: 700 and 800 come out of the SAME file per subset, which is why there
   are two files and four declarations. Both are Google's own subsets, byte for byte, so nothing
   about the rendering changes. Licence and copyright: assets/fonts/OFL.txt (SIL OFL 1.1), which
   the licence requires to travel with the files.

   Nothing here downloads a font on a phone: --font-display is only applied inside the >= 900px
   block at the end of this file, and a browser fetches a face only when something actually
   renders in it. font-display: swap means a heading paints in the system stack first either way.

   cli/stamp-assets.php rewrites src=/href= in HTML, not urls inside CSS, so these two paths are
   NOT content-hashed and the host's CDN will cache them for seven days. That is right for a font
   that never changes — but REPLACING one means giving it a new file name, not overwriting it. */

@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/sora-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: 'Sora';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/sora-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: 'Sora';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(fonts/sora-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: 'Sora';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(fonts/sora-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;
}

/* ---------- tokens ---------- */

:root {
  /* --- brand, unchanged (CLAUDE.md, agreed 2026-09-01) --- */
  --orange: #F26B1C;       /* Ring Orange: primary actions, links, focus */
  --deep-ring: #D95A10;    /* orange hover / pressed */
  --green: #2E8F58;        /* success */

  /* --- the four that carry the theme ---
     --ink is TEXT ONLY. The five rules that used it as a BACKGROUND (.btn-secondary,
     .seg button[aria-pressed], .daytab[aria-selected], .toast, .auth-panel) read
     --ink-surface instead, because in dark mode --ink IS the light text colour and
     Ink-on-Ink would be invisible. */
  --ink: #1B2130;          /* Bell Ink: headings and body text */
  --ink-surface: #1B2130;  /* the dark block a white label sits on */
  --chalk: #FAF7F2;        /* page background */
  --card: #FFFFFF;

  --slate: #5C6474;        /* secondary text */
  --ink-hover: #2A3245;    /* secondary button hover */
  --on-ink: #F5F2EC;       /* text on --ink-surface */
  --on-ink-dim: rgba(245, 242, 236, .86);   /* the quieter line on --ink-surface */
  /* 3.05:1 on Ring Orange — below AA, and the light theme's existing compromise; the brand
     is the brand. Dark puts Ink on the orange instead and gets 5.28:1. */
  --on-orange: #FFFFFF;

  --line: #E7E2D9;
  --line-strong: #C9C3B8;
  --line-soft: var(--line-soft);    /* the faintest rule: week-grid hour lines */
  --line-lift: #E7E2D9;    /* a border that has to hold its own against a raised panel */
  --well: #F4F0E8;         /* quiet inset panels, tab bars */
  --field: #FFFFFF;        /* what you type into, and the raised chip on a tab bar */
  --hover-bg: #FCFAF6;     /* a row under the pointer */
  --edit-bg: #FBF9F5;      /* the open lesson editor */

  --amber-bg: #FFF3DC;
  --amber-line: #F0D49A;
  --amber-ink: #7A4B06;
  --red: #C4302B;
  --red-bg: #FCEBE9;
  --red-line: #F0C3BF;
  --red-ink: #8E221D;
  --on-red: #FFFFFF;       /* the label on a red fill */
  --red-deep: #A9261F;     /* danger hover / pressed */
  --placeholder: #8A8F9C;
  --focus-ring-red: 0 0 0 3px rgba(196, 48, 43, .25);
  --green-bg: #E5F3EA;
  --green-line: #BFE0CB;
  --green-ink: #1F6B41;    /* green as TEXT; --green alone is 3.54:1 on --green-bg */
  --toast-green: #24774A;  /* the success toast's ground: --green is 4.05:1 under white */
  --on-green: #FFFFFF;

  /* Orange at low alpha, pre-mixed per theme so a wash never lightens a dark page. */
  --orange-wash: #FDF0E8;
  --orange-wash-faint: #FEFAF7;
  --orange-wash-strong: #FDF3EC;
  --orange-wash-today: #FEFBF8;
  --orange-edge: rgba(242, 107, 28, .35);

  --radius: 8px;
  --radius-card: 14px;
  --shadow: 0 1px 2px rgba(27, 33, 48, .05), 0 6px 18px rgba(27, 33, 48, .06);
  --shadow-lift: 0 2px 4px rgba(27, 33, 48, .05), 0 14px 32px rgba(27, 33, 48, .10);
  --shadow-toast: 0 8px 24px rgba(27, 33, 48, .25);
  --shadow-pop: 0 12px 32px rgba(27, 33, 48, .18);
  --shadow-chip: 0 1px 2px rgba(27, 33, 48, .1);
  --focus-ring: 0 0 0 3px rgba(242, 107, 28, .32);
  --edge-ring: none;       /* dark gives a dark button an edge so it reads as a button */
  --scrim: rgba(27, 33, 48, .5);          /* behind a sheet */
  --bar: rgba(250, 247, 242, .92);        /* the sticky header, over blurred page */
  --bar-solid: rgba(250, 247, 242, .96);  /* the save bar */
  --disabled-opacity: .45;
  --motif-opacity: .055;   /* the brand watermark behind a hero */
  /* The logo PNG is Bell Ink + Ring Orange on transparent. On a DARK ground it goes through
     invert(1) hue-rotate(180deg): Ink becomes a chalky light and Ring Orange lands back on
     itself (#ED6617). A dark-mode logo with no second file.
     --logo-on-ink-filter is for the login panel, which is dark in BOTH themes, so it never
     changes. --logo-filter is for the header wordmark and the empty-state mark, which sit on
     the PAGE: no filter in light, the same invert in dark. */
  --logo-on-ink-filter: invert(1) hue-rotate(180deg);
  --logo-filter: none;
  --dp-weekend: var(--dp-weekend);
  --wg-hover: var(--wg-hover);

  /* The ten subject hues for the week grid, and the pre-mixed tint each block is filled with.
     app.js emits var(--subj-N) by index, NOT a literal, so these switch with the theme; an
     inline custom property carrying a hex could never be overridden by a stylesheet. */
  --subj-0: #F26B1C;  --subj-0-t: #FEF0E8;
  --subj-1: #3B5BA9;  --subj-1-t: #EBEFF6;
  --subj-2: #2E8F58;  --subj-2-t: #EAF4EE;
  --subj-3: #C98A12;  --subj-3-t: #FAF3E7;
  --subj-4: #7B4B94;  --subj-4-t: #F2EDF4;
  --subj-5: #1F8A8A;  --subj-5-t: #E9F3F3;
  --subj-6: #C2456B;  --subj-6-t: #F9ECF0;
  --subj-7: #5C6474;  --subj-7-t: #EFF0F1;
  --subj-8: #6E8B2E;  --subj-8-t: #F0F3EA;
  --subj-9: #8A5A3C;  --subj-9-t: #F3EEEC;

  --col: 560px;
  --page-max: 1180px;      /* the desktop column (>= 900px); --col stays the phone column */
  --gutter: 16px;
  /* Sora is the one web font (headings, >= 900px only), served from assets/fonts by the
     @font-face block above; the system stack is the fallback and the body face everywhere. */
  --font-display: "Sora", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --sat: env(safe-area-inset-top, 0px);
  --sar: env(safe-area-inset-right, 0px);
  --sab: env(safe-area-inset-bottom, 0px);
  --sal: env(safe-area-inset-left, 0px);

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  color-scheme: light;
}

/* ---------- dark ----------
   Three states, and the guard is what makes all three work in BOTH directions:
     no data-theme        -> follow the phone (the media block below applies). NOTE the app
                             never leaves it unset by default: see below.
     data-theme="light"   -> the :not() guard switches the media block off, :root wins
     data-theme="dark"    -> the last block wins even on a light phone
   The attribute is stamped on <html> by the inline script in each page's <head>, before this
   file paints, so a dark-mode user never sees a white flash. No component rule below knows
   which theme is on: every one of them reads a token.

   The two blocks are byte-identical on purpose and MUST stay that way — plain CSS has no way
   round the duplication short of light-dark(), which wants Safari 17.5. Change one, change both. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink: #EDEAE4;
    --ink-surface: #323B4E;
    --chalk: #14181F;
    --card: #1E2430;

    --slate: #A2ABBB;
    --ink-hover: #3D485E;
    --on-ink: #F5F2EC;
    --on-ink-dim: #C9D0DC;
    --on-orange: #1B2130;

    --line: #333B49;
    --line-strong: #4A5364;
    --line-soft: #2A3140;
    --line-lift: #4A5364;
    --well: #272E3C;
    --field: #171C25;
    --hover-bg: #232A37;
    --edit-bg: #232A37;

    --amber-bg: #3A2E14;
    --amber-line: #5C4A22;
    --amber-ink: #F5CE84;
    --red: #E5544D;
    --red-bg: #3A1E1C;
    --red-line: #5E2C29;
    --red-ink: #FFA9A2;
    --on-red: #21100F;
    --red-deep: #EF6C65;
    --placeholder: #7B8493;
    --focus-ring-red: 0 0 0 3px rgba(229, 84, 77, .38);
    --green-bg: #16301F;
    --green-line: #2A5539;
    --green-ink: #7FD9A5;
    --toast-green: #7FD9A5;
    --on-green: #0E2416;

    --orange-wash: #2C2620;
    --orange-wash-faint: #1B2028;
    --orange-wash-strong: #33291F;
    --orange-wash-today: #1E222A;
    --orange-edge: rgba(242, 107, 28, .5);

    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 6px 18px rgba(0, 0, 0, .34);
    --shadow-lift: 0 2px 4px rgba(0, 0, 0, .4), 0 14px 32px rgba(0, 0, 0, .44);
    --shadow-toast: 0 8px 24px rgba(0, 0, 0, .5);
    --shadow-pop: 0 12px 32px rgba(0, 0, 0, .5);
    --shadow-chip: 0 1px 2px rgba(0, 0, 0, .35);
    --edge-ring: inset 0 0 0 1px #4A5364;
    --scrim: rgba(6, 8, 12, .66);
    --bar: rgba(20, 24, 31, .88);
    --bar-solid: rgba(20, 24, 31, .96);
    --disabled-opacity: .62;
    --motif-opacity: .07;
    --logo-filter: invert(1) hue-rotate(180deg);
    --dp-weekend: #798394;
    --wg-hover: brightness(1.22);

    --subj-0: #FF8F45;  --subj-0-t: #463734;
    --subj-1: #7C9EE8;  --subj-1-t: #2F3A51;
    --subj-2: #4FC98A;  --subj-2-t: #274240;
    --subj-3: #E5B23F;  --subj-3-t: #423E33;
    --subj-4: #C08FD8;  --subj-4-t: #3B374E;
    --subj-5: #3FC7C7;  --subj-5-t: #24414B;
    --subj-6: #F07A9F;  --subj-6-t: #443344;
    --subj-7: #A2ABBB;  --subj-7-t: #363C49;
    --subj-8: #A8C64F;  --subj-8-t: #374136;
    --subj-9: #CE9A70;  --subj-9-t: #3E393C;

    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --ink: #EDEAE4;
  --ink-surface: #323B4E;
  --chalk: #14181F;
  --card: #1E2430;

  --slate: #A2ABBB;
  --ink-hover: #3D485E;
  --on-ink: #F5F2EC;
  --on-ink-dim: #C9D0DC;
  --on-orange: #1B2130;

  --line: #333B49;
  --line-strong: #4A5364;
  --line-soft: #2A3140;
  --line-lift: #4A5364;
  --well: #272E3C;
  --field: #171C25;
  --hover-bg: #232A37;
  --edit-bg: #232A37;

  --amber-bg: #3A2E14;
  --amber-line: #5C4A22;
  --amber-ink: #F5CE84;
  --red: #E5544D;
  --red-bg: #3A1E1C;
  --red-line: #5E2C29;
  --red-ink: #FFA9A2;
  --on-red: #21100F;
  --red-deep: #EF6C65;
  --placeholder: #7B8493;
  --focus-ring-red: 0 0 0 3px rgba(229, 84, 77, .38);
  --green-bg: #16301F;
  --green-line: #2A5539;
  --green-ink: #7FD9A5;
  --toast-green: #7FD9A5;
  --on-green: #0E2416;

  --orange-wash: #2C2620;
  --orange-wash-faint: #1B2028;
  --orange-wash-strong: #33291F;
  --orange-wash-today: #1E222A;
  --orange-edge: rgba(242, 107, 28, .5);

  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 6px 18px rgba(0, 0, 0, .34);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, .4), 0 14px 32px rgba(0, 0, 0, .44);
  --shadow-toast: 0 8px 24px rgba(0, 0, 0, .5);
  --shadow-pop: 0 12px 32px rgba(0, 0, 0, .5);
  --shadow-chip: 0 1px 2px rgba(0, 0, 0, .35);
  --edge-ring: inset 0 0 0 1px #4A5364;
  --scrim: rgba(6, 8, 12, .66);
  --bar: rgba(20, 24, 31, .88);
  --bar-solid: rgba(20, 24, 31, .96);
  --disabled-opacity: .62;
  --motif-opacity: .07;
  --logo-filter: invert(1) hue-rotate(180deg);
  --dp-weekend: #798394;
  --wg-hover: brightness(1.22);

  --subj-0: #FF8F45;  --subj-0-t: #463734;
  --subj-1: #7C9EE8;  --subj-1-t: #2F3A51;
  --subj-2: #4FC98A;  --subj-2-t: #274240;
  --subj-3: #E5B23F;  --subj-3-t: #423E33;
  --subj-4: #C08FD8;  --subj-4-t: #3B374E;
  --subj-5: #3FC7C7;  --subj-5-t: #24414B;
  --subj-6: #F07A9F;  --subj-6-t: #443344;
  --subj-7: #A2ABBB;  --subj-7-t: #363C49;
  --subj-8: #A8C64F;  --subj-8-t: #374136;
  --subj-9: #CE9A70;  --subj-9-t: #3E393C;

  color-scheme: dark;
}

/* ---------- base ---------- */

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }   /* the UA rule loses to display:flex on components */

html {
  height: 100%;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  overflow-x: hidden;
}

body {
  margin: 0;
  min-height: 100%;
  background: var(--chalk);
  color: var(--ink);
  font: 16px/1.45 var(--font);
  -webkit-font-smoothing: antialiased;
  /* No overflow-x here: with html already clipping sideways, an overflow on body makes body its
     own scroll container and position: sticky (the header) stops sticking to the viewport. */
  overflow-wrap: anywhere;
}

img, svg { max-width: 100%; }
svg.icon { width: 20px; height: 20px; flex: 0 0 auto; vertical-align: -4px; }

a { color: var(--orange); text-decoration: none; }
a:hover { color: var(--deep-ring); text-decoration: underline; }
a:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 4px; }

h1, h2, h3, p, ul, dl { margin: 0; }
ul { padding: 0; list-style: none; }

.h1 { font-size: 26px; line-height: 1.2; font-weight: 800; letter-spacing: -0.01em; }
.h2 { font-size: 18px; line-height: 1.25; font-weight: 800; }
.h3 { font-size: 16px; line-height: 1.3; font-weight: 700; }
.sub, .meta, .hint { color: var(--slate); font-size: 15px; }
.hint { margin-bottom: 12px; }
.center { text-align: center; }
.muted { color: var(--slate); }
/* The quiet line at the foot of Home: who you are signed in as, and the way to the account view. */
.page-foot { margin: 22px 0 8px; text-align: center; color: var(--slate); font-size: 14px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- layout ---------- */

.page {
  max-width: var(--col);
  margin: 0 auto;
  padding: 16px var(--gutter) calc(32px + var(--sab));
  padding-left: max(var(--gutter), var(--sal));
  padding-right: max(var(--gutter), var(--sar));
  min-width: 0;
}
body.has-savebar .page { padding-bottom: calc(96px + var(--sab)); }

.stack { display: flex; flex-direction: column; gap: 12px; }
.row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.row-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.row-actions .btn { flex: 1 1 auto; }
/* A quiet action sits at its own width at EVERY size, so it reads as a word beside the
   button rather than a second bar of equal weight. Unconditional on purpose: a per-breakpoint
   rule left the 600-899px band matching only the line above it. */
.row-actions .btn-quiet { flex: 0 1 auto; }

.view-head { margin: 8px 0 16px; }
.view-head .sub { margin-top: 4px; }
.backlink {
  display: inline-flex; align-items: center; gap: 2px;
  min-height: 44px; padding-right: 8px; margin-left: -6px;
  color: var(--slate); font-weight: 700; font-size: 15px;
}
.backlink:hover { color: var(--ink); text-decoration: none; }

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

.topbar {
  position: sticky; top: 0; z-index: 20;
  background: var(--bar);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
  padding-top: var(--sat);
}
.topbar-in {
  max-width: var(--col);
  margin: 0 auto;
  min-height: 56px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 0 max(var(--gutter), var(--sar)) 0 max(var(--gutter), var(--sal));
}
.topbar-user { display: flex; align-items: center; gap: 6px; min-width: 0; }

/* ---------- wordmark ---------- */

.wordmark {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink); text-decoration: none;
  font-weight: 800; font-size: 22px; letter-spacing: -0.02em; line-height: 1;
}
.wordmark:hover { color: var(--ink); text-decoration: none; }
.wordmark .wm-fallback { display: inline-flex; align-items: center; }
.wordmark .wm-mark { width: 28px; height: 28px; margin-right: 8px; }
.wordmark .wm-bell { color: var(--orange); }
.wordmark .wm-png { height: 32px; width: auto; display: block; filter: var(--logo-filter); }
.wordmark.big { font-size: 34px; }
.wordmark.big .wm-mark { width: 44px; height: 44px; }
.wordmark.big .wm-png { height: 48px; }

/* ---------- cards ---------- */

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow);
  padding: 20px;
  margin-bottom: 14px;
  min-width: 0;
  scroll-margin-top: calc(64px + var(--sat));   /* scrollIntoView lands below the sticky header */
}
.card > .h2 { margin-bottom: 10px; }

/* ---------- the help dot ----------
   A card heading with a "?" beside it (2026-09-02). The button is a SIBLING of the h2, not
   inside it: a <button> inside a heading is folded into that heading's accessible name, so a
   screen reader would announce "School year More about School year". The wrapper costs the
   three margin rules below, because .card > .h2 and .card-head .h2 stop matching once the
   heading is nested.
   The circle is 22px so it sits on a heading's line without pushing it around; ::after grows
   the HIT AREA to 44px, which is this app's floor for anything you tap. */
.help-head { display: flex; align-items: center; gap: 2px; min-width: 0; }
.help-head > .h1, .help-head > .h2, .help-head > .h3 { margin: 0; }
.card > .help-head { margin-bottom: 10px; }
.card-head .help-head { flex: 1 1 auto; }
.help-dot {
  position: relative; flex: 0 0 auto;
  width: 22px; height: 22px; margin-left: 7px; padding: 0;
  border: 0; border-radius: 50%;
  background: var(--well); color: var(--slate);
  font: 700 13px/22px var(--font); text-align: center;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.help-dot::after { content: ''; position: absolute; inset: -11px; }
.help-dot:hover { background: var(--line); color: var(--ink); }
.help-dot:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 18px 20px 12px;
}
.card-head .h2 { margin: 0; }
.card-flush { padding: 0; }
.card-flush .daytabs, .card-flush .seg-wide { margin-left: 20px; margin-right: 20px; }
.card-foot { padding: 12px 20px 16px; }
.card-quiet { background: transparent; box-shadow: none; border-style: dashed; }
.card.empty { text-align: center; padding: 40px 24px; }
.card.empty .h1 { margin: 12px 0 8px; }
.card.empty .hint { margin-bottom: 22px; }
.empty-icon { color: var(--orange); }
.empty-icon .icon { width: 48px; height: 48px; }

.card.progress { text-align: center; padding: 40px 24px; }
.card.progress .h2 { margin: 16px 0 6px; }
.card.progress .hint { margin-bottom: 20px; }
.card.progress .elapsed { font-variant-numeric: tabular-nums; }

/* ---------- buttons: Option A "Block" ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 0 18px;
  border: 0; border-radius: var(--radius);
  font: 700 16px/1.2 var(--font);
  text-decoration: none; text-align: center;
  cursor: pointer; user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: background-color .12s ease, color .12s ease, transform .06s ease, opacity .12s ease;
  white-space: nowrap;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.btn:disabled, .btn[aria-disabled="true"], .btn.is-busy { opacity: var(--disabled-opacity); pointer-events: none; }
.btn .icon { width: 20px; height: 20px; }

.btn-primary { background: var(--orange); color: var(--on-orange); }
.btn-secondary { background: var(--ink-surface); color: var(--on-ink); box-shadow: var(--edge-ring); }
.btn-ghost { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--line-strong); }
.btn-danger { background: var(--red); color: var(--on-red); }
.btn-danger-text { color: var(--red); }
/* The quiet tier (2026-09-02). Ghost was the app's only "other way to do this", and a
   full-width outlined bar beside a full-width filled one still reads as two equal choices —
   which is exactly what looked "all the same" on a phone. .btn-quiet has no fill and no
   border: it is a word you can press, so the eye lands on the one filled button and nothing
   else. It keeps the 44px target. Never use it for the action a card exists to perform. */
.btn-quiet { background: transparent; color: var(--slate); padding: 0 12px; }
.btn-quiet .icon { color: var(--slate); }

/* Hover colours only where a pointer can hover, so a tapped button on a phone does not stay
   stuck in its hover colour. */
@media (hover: hover) {
  .btn-primary:hover { background: var(--deep-ring); color: var(--on-orange); }
  .btn-secondary:hover { background: var(--ink-hover); color: var(--on-ink); }
  .btn-ghost:hover { background: var(--well); color: var(--ink); }
  .btn-danger:hover { background: var(--red-deep); }
  .btn-danger-text:hover { color: var(--red); background: var(--red-bg); }
}
.btn-primary:active { background: var(--deep-ring); }
.btn-secondary:active { background: var(--ink-hover); }

.btn-block { display: flex; width: 100%; }
.btn-sm { min-height: 44px; padding: 0 12px; font-size: 15px; }   /* smaller type, never a smaller tap target */
.btn-lg { min-height: 52px; font-size: 17px; }
.btn-wrap { white-space: normal; padding-top: 10px; padding-bottom: 10px; line-height: 1.3; }   /* for the odd long label */
.btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: 0 0 44px;
  border: 0; border-radius: var(--radius); background: transparent; color: var(--slate);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.btn-icon:hover { background: var(--well); color: var(--ink); }
.btn-icon:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.btn-icon:active { transform: translateY(1px); }

/* ---------- inputs ---------- */

.field { margin-bottom: 14px; min-width: 0; }
.field > label, .label {
  display: block; margin-bottom: 6px;
  font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--slate);
}
.input {
  display: block; width: 100%;
  min-height: 44px; padding: 10px 12px;
  border: 1.5px solid var(--line-strong); border-radius: var(--radius);
  background: var(--field); color: var(--ink);
  font: 16px/1.3 var(--font);      /* 16px so iOS does not zoom the page on focus */
  -webkit-appearance: none; appearance: none;
  transition: border-color .12s ease, box-shadow .12s ease;
}
.input::placeholder { color: var(--placeholder); }
.input:focus { outline: none; border-color: var(--orange); box-shadow: var(--focus-ring); }
.input.is-invalid { border-color: var(--red); }
.input.is-invalid:focus { box-shadow: var(--focus-ring-red); }
.input[data-kind="time"], .input[data-kind="date"] { font-variant-numeric: tabular-nums; }
select.input {
  padding-right: 36px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%235C6474' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat; background-position: right 8px center; background-size: 20px;
}
.field-error { color: var(--red); font-size: 14px; font-weight: 600; margin: 6px 0 0; }
.field-error + .row-actions { margin-top: 12px; }
.lesson-edit .field-error, .subpanel .field-error { margin-bottom: 4px; }
.field-error[hidden] { display: none; }
.form-error { margin: 0 0 14px; }

/* ---------- tabs and segmented controls ---------- */

.tabs {
  display: flex; gap: 4px; padding: 3px;
  background: var(--well); border-radius: 10px; margin-bottom: 18px;
}
.tab {
  flex: 1; min-height: 44px; padding: 0 10px;
  border: 0; border-radius: 7px; background: transparent;
  font: 700 15px var(--font); color: var(--slate); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tab[aria-selected="true"] { background: var(--field); color: var(--ink); box-shadow: var(--shadow-chip); }
.tab:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.seg {
  display: inline-flex; overflow: hidden;
  border: 1.5px solid var(--line-strong); border-radius: var(--radius); background: var(--field);
}
.seg-wide { display: flex; margin-bottom: 12px; }
.seg button {
  flex: 1 1 auto; min-height: 44px; padding: 0 16px;
  border: 0; border-left: 1.5px solid var(--line-strong); background: transparent;
  font: 700 15px var(--font); color: var(--slate); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.seg button:first-child { border-left: 0; }
.seg button[aria-pressed="true"] { background: var(--ink-surface); color: var(--on-ink); }
.seg button:focus-visible { outline: none; box-shadow: inset var(--focus-ring); }

.daytabs { display: flex; gap: 6px; margin-bottom: 4px; }
.daytab {
  flex: 1 1 0; min-width: 0; min-height: 48px; padding: 4px 2px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  border: 0; border-radius: var(--radius); background: var(--well);
  font: 700 14px/1.1 var(--font); color: var(--slate); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.daytab[aria-selected="true"] { background: var(--ink-surface); color: var(--on-ink); }
.daytab:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.daytab-n { font-size: 11px; font-weight: 600; opacity: .75; }

/* ---------- lesson rows ---------- */

.lessons { border-top: 1px solid var(--line); }
.lesson {
  display: grid; grid-template-columns: 64px 1fr 24px; gap: 2px 12px; align-items: center;
  width: 100%; min-height: 60px; padding: 10px 20px;
  border: 0; border-bottom: 1px solid var(--line); background: transparent;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.lesson:hover { background: var(--hover-bg); }
.lesson:focus-visible { outline: none; box-shadow: inset var(--focus-ring); }
.lesson-time { display: flex; flex-direction: column; font-variant-numeric: tabular-nums; font-weight: 700; font-size: 15px; line-height: 1.25; }
.lesson-time .end { color: var(--slate); font-weight: 600; }
.lesson-main { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.lesson-subject { font-weight: 700; }
.lesson-meta { color: var(--slate); font-size: 14px; }
.lesson-chev { color: var(--line-strong); }
.empty-row { padding: 22px 20px; color: var(--slate); border-bottom: 1px solid var(--line); }

.lesson-edit { padding: 16px 20px; background: var(--edit-bg); border-bottom: 1px solid var(--line); }
.lesson-edit .row-actions { margin-top: 2px; }

/* ---------- pills, panels, lists ---------- */

.pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px; border-radius: 999px;
  font-size: 12.5px; font-weight: 700; line-height: 1.3; white-space: nowrap;
}
.pill .icon { width: 14px; height: 14px; }
.pill-warn { background: var(--amber-bg); color: var(--amber-ink); }
.pill-ok { background: var(--green-bg); color: var(--green-ink); }
.pill-muted { background: var(--well); color: var(--slate); }

.panel {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px; border-radius: var(--radius); margin-bottom: 14px;
  font-size: 15px; line-height: 1.4;
}
.panel .icon { margin-top: 1px; }
.panel-amber { background: var(--amber-bg); border: 1px solid var(--amber-line); color: var(--amber-ink); }
.panel-red { background: var(--red-bg); border: 1px solid var(--red-line); color: var(--red-ink); }
.panel-green { background: var(--green-bg); border: 1px solid var(--green-line); color: var(--green-ink); }
.panel ul { margin-top: 4px; padding-left: 18px; list-style: disc; }
.panel li + li { margin-top: 2px; }
.panel strong { display: block; }

.list { border-top: 1px solid var(--line); margin-top: 8px; }
.list-row {
  display: flex; align-items: center; gap: 10px;
  min-height: 52px; padding: 8px 0; border-bottom: 1px solid var(--line);
}
.list-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.list-title { font-weight: 700; }
.list-meta { color: var(--slate); font-size: 14px; }
.list-empty { padding: 14px 0; color: var(--slate); }

.facts { margin: 0 0 18px; }
.facts > div { display: flex; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.facts > div:first-child { border-top: 1px solid var(--line); }
.facts dt { flex: 0 0 104px; color: var(--slate); font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding-top: 2px; }
.facts dd { margin: 0; flex: 1; min-width: 0; font-weight: 600; }

/* A disclosure. The long iPhone / Google / Android walk-throughs stay on the page and keep
   their numbered steps — they are the recovery path when the Subscribe button does nothing —
   but they start folded so they are not the first thing on a phone screen. */
.fold > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  min-height: 44px; cursor: pointer; list-style: none; color: var(--ink);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary:focus-visible { outline: none; border-radius: 6px; box-shadow: var(--focus-ring); }
.fold > summary .icon { color: var(--slate); transition: transform .15s ease; }
.fold[open] > summary .icon { transform: rotate(180deg); }

.howto { margin-top: 4px; }
.howto h3 { display: flex; align-items: center; gap: 8px; margin: 16px 0 6px; }
.howto h3:first-child { margin-top: 4px; }
.howto ol { margin: 0; padding-left: 22px; color: var(--ink); }
.howto li { margin: 4px 0; }
.howto p { color: var(--slate); font-size: 15px; margin-top: 4px; }

/* ---------- the privacy page ----------
   privacy.html is the one page here you can read without logging in, so it carries no app
   furniture: a card, a measure, and headings you can skim. */
.legal { max-width: 68ch; }
.legal h2 { font-size: 18px; font-weight: 800; margin: 26px 0 8px; }
.legal h2:first-child { margin-top: 0; }
.legal h3 { font-size: 15.5px; font-weight: 700; margin: 16px 0 4px; }
.legal p { margin: 0 0 10px; line-height: 1.55; }
.legal ul { margin: 0 0 12px; padding-left: 20px; list-style: disc; }
.legal li { margin: 5px 0; line-height: 1.5; }
.legal .lead { font-size: 17px; color: var(--slate); }
.legal .updated { color: var(--slate); font-size: 14px; margin-bottom: 22px; }
.legal .todo { background: var(--amber-bg); border: 1px solid var(--amber-line); color: var(--amber-ink); border-radius: var(--radius); padding: 12px 14px; margin: 0 0 14px; font-size: 15px; }

/* ---------- settings ---------- */
.set-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 52px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.set-row:last-child { border-bottom: 0; }
.set-row .icon { color: var(--slate); flex: 0 0 auto; }
.set-link {
  display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; min-height: 44px;
  padding: 0; border: 0; background: none; text-align: left;
  font: 700 16px var(--font); color: var(--ink); text-decoration: none;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.set-link:focus-visible { outline: none; border-radius: 6px; box-shadow: var(--focus-ring); }
.set-link:hover { color: var(--orange); text-decoration: none; }
.set-link:hover .icon { color: var(--orange); }
.set-link .chev { margin-left: auto; color: var(--line-strong); }

/* The gear in the header. It replaces the username link, so an admin's header is the same as
   everyone else's and nothing has to be hidden on a phone to make it fit. */
.topbar-gear { color: var(--slate); text-decoration: none; }
.topbar-gear:hover { color: var(--ink); background: var(--well); text-decoration: none; }
.topbar-gear .lbl { display: none; }

/* ---------- timetable cards on Home ---------- */

.tt-card .tt-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.tt-card .tt-head .h2 { margin: 0; }
.tt-card .meta { margin-bottom: 4px; }
.tt-card .row-actions { margin-top: 14px; }

/* ---------- file picker ---------- */

.picker { margin-bottom: 16px; }
.picker-buttons { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }
.picker-buttons label { cursor: pointer; }
.picked {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 10px 10px 12px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--well);
}
.picked-thumb {
  width: 48px; height: 48px; flex: 0 0 48px; border-radius: 6px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--field); color: var(--slate); border: 1px solid var(--line);
}
.picked-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.picked-name { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picked-size { color: var(--slate); font-size: 14px; white-space: nowrap; }

.subpanel { padding-top: 4px; }
.subpanel .h3 { margin-bottom: 4px; }
.closure-row { padding: 12px 0; border-top: 1px solid var(--line); }
.closure-row:first-child { border-top: 0; padding-top: 0; }
.closure-row .row { align-items: flex-end; }
.closure-row .field { margin-bottom: 0; flex: 1; }
.closure-row .grid-2 { margin-top: 10px; }
.cal-summary { display: flex; align-items: center; gap: 10px; margin: 4px 0 6px; }
.cal-summary .icon { color: var(--orange); }
.cal-summary .meta { display: block; }

/* ---------- spinner ---------- */

.spinner {
  width: 32px; height: 32px; margin: 0 auto;
  border: 3px solid var(--line); border-top-color: var(--orange); border-radius: 50%;
  animation: nxt-spin .8s linear infinite;
}
.spinner-sm { width: 18px; height: 18px; border-width: 2px; margin: 0; border-color: rgba(128, 128, 128, .35); border-top-color: currentColor; }
@keyframes nxt-spin { to { transform: rotate(360deg); } }

/* ---------- toast ---------- */

.toast {
  position: fixed; left: 50%; bottom: calc(20px + var(--sab)); z-index: 60;
  display: flex; align-items: center; gap: 10px;
  max-width: calc(100% - 32px); padding: 12px 16px;
  border-radius: 10px; background: var(--ink-surface); color: var(--on-ink);
  font-weight: 600; font-size: 15px; box-shadow: var(--shadow-toast);
  transform: translate(-50%, 16px); opacity: 0; pointer-events: none;
  transition: transform .2s ease, opacity .2s ease;
}
.toast.is-in { transform: translate(-50%, 0); opacity: 1; }
.toast-ok { background: var(--toast-green); color: var(--on-green); }
.toast-error { background: var(--red); color: var(--on-red); }

/* ---------- confirm sheet (replaces confirm()) ---------- */

.sheet-backdrop {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: flex-end; justify-content: center;
  background: var(--scrim);
  opacity: 0; transition: opacity .15s ease;
}
.sheet-backdrop.is-in { opacity: 1; }
.sheet {
  width: 100%; max-width: var(--col);
  background: var(--card); box-shadow: var(--edge-ring); border-radius: 16px 16px 0 0;
  padding: 22px 20px calc(20px + var(--sab));
  transform: translateY(100%); transition: transform .2s ease;
}
.sheet-backdrop.is-in .sheet { transform: none; }
.sheet-title { font-size: 19px; font-weight: 800; margin-bottom: 6px; }
/* A help sheet can be longer than a phone screen. .sheet itself has no max-height and a fixed
   backdrop cannot scroll, so without this the TOP of a long topic — title included — is
   simply unreachable. Only the body scrolls; the title and the button stay put. */
.sheet-help { display: flex; flex-direction: column; max-height: min(82vh, 640px); }
.sheet-help .sheet-title { flex: 0 0 auto; }
.sheet-help .sheet-scroll { overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; margin-bottom: 18px; }
.sheet-help .sheet-actions { flex: 0 0 auto; }
.sheet-help .sheet-body { margin-bottom: 12px; }
.sheet-help .sheet-body:last-child { margin-bottom: 0; }
.sheet-h3 { font-size: 15px; font-weight: 800; color: var(--ink); margin: 16px 0 4px; }
.sheet-scroll > .sheet-h3:first-child { margin-top: 0; }
.sheet-ol { margin: 0 0 12px; padding-left: 20px; list-style: decimal; color: var(--ink); font-size: 15px; }
.sheet-ol li { margin: 3px 0; }
.sheet-body { color: var(--slate); margin-bottom: 18px; }
.sheet-actions { display: flex; flex-direction: column; gap: 10px; }
@media (min-width: 600px) {
  .sheet-backdrop { align-items: center; padding: 20px; }
  .sheet { max-width: 420px; border-radius: 16px; padding-bottom: 20px; }
}

/* ---------- sticky save bar ---------- */

.savebar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  background: var(--bar-solid);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  backdrop-filter: saturate(180%) blur(12px);
  border-top: 1px solid var(--line);
  transform: translateY(110%); transition: transform .2s ease;
}
.savebar { visibility: hidden; }
.savebar.is-in { transform: none; visibility: visible; }
.savebar-in {
  max-width: var(--col); margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px max(var(--gutter), var(--sar)) calc(10px + var(--sab)) max(var(--gutter), var(--sal));
}
.savebar-text { font-weight: 700; color: var(--slate); font-size: 15px; }

/* ---------- login page ---------- */

/* A phone gets a compact Bell Ink strip with the small logo, then the card. From 900px up the
   strip grows into the brand panel (below, under "desktop"). The logo PNG is ink + orange on
   transparent, so on Ink it is shown through invert(1) hue-rotate(180deg): that turns Bell Ink
   into a chalky light and lands Ring Orange back on itself (#ED6617), a dark-mode logo with no
   second file. The SVG fallback wordmark is recoloured the plain way. */
.auth-body { display: flex; flex-direction: column; min-height: 100dvh; }
.auth-split { display: flex; flex-direction: column; flex: 1; }
.auth-panel {
  position: relative; overflow: hidden;
  background: var(--ink-surface); color: var(--on-ink);
  display: flex; align-items: center; justify-content: center;
  min-height: 64px;
  padding: calc(14px + var(--sat)) max(var(--gutter), var(--sar)) 14px max(var(--gutter), var(--sal));
}
.auth-panel .motif, .auth-panel-in { display: none; }
.auth-strip-logo .wm-png { height: 30px; filter: var(--logo-on-ink-filter); }
.auth-strip-logo .wm-mark { filter: var(--logo-on-ink-filter); }
.auth-strip-logo .wm-nxt { color: var(--on-ink); }
.auth-wrap {
  width: 100%; max-width: 440px; margin: 0 auto;
  padding: 28px var(--gutter) calc(32px + var(--sab));
  padding-left: max(var(--gutter), var(--sal)); padding-right: max(var(--gutter), var(--sar));
  display: flex; flex-direction: column; align-items: stretch; gap: 18px; flex: 1;
}
.auth-card { padding: 22px; }
.auth-card .btn-block { margin-top: 4px; }
.auth-note { text-align: center; color: var(--slate); font-size: 15px; margin-top: 14px; }
/* "Forgot your password?" sits directly under the Log in button, and with no margin it read as
   part of it — a thumb going for Log in could easily land on the link instead. It gets real
   space above, and a 44px target of its own: it is the one link a person who cannot get in
   actually needs, so it must not be the hardest thing on the card to hit. */
.auth-forgot { margin-top: 6px; }
.auth-forgot a {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 12px;
}
@media (min-width: 600px) {
  .auth-wrap { justify-content: center; padding-top: 24px; }
}

/* ---------- admin page (admin.html) ---------- */

/* A card heading with a status pill beside it: "Scanner" / "Scanner ready". */
.head-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.head-row .h2 { margin: 0; }
/* An input with one button on its right (the key box and its Show / Hide). */
.input-row { display: flex; align-items: stretch; gap: 8px; }
.input-row .input { flex: 1 1 auto; min-width: 0; }
.input-row .btn { flex: 0 0 auto; }
/* The Gemini key box is a TEXT input masked here, not type=password: a password field on the
   same origin as the login form is what a password manager autofills (the admin's own login
   password, which a Save would then store as the key) and offers to save (the key, into a
   synced vault). Password managers leave text fields alone. Show / Hide toggles this class. */
.input-secret { -webkit-text-security: disc; }
/* The mailbox password box is masked by that same class, for the same reason: it sits on the
   same origin as the login form, and a type=password there is what a password manager autofills
   with the admin's own login password and then offers to save. */
/* The port, sharing a row with the mail server name. Wide enough for five digits and no wider,
   so the server name gets the rest of the line. */
.input-row .input-port { flex: 0 0 5.5em; text-align: center; }
.field-note { color: var(--slate); font-size: 14px; margin: 6px 0 0; }
/* The one line that says a photo leaves the building. Flex so a wrapped second line clears
   the icon instead of running back underneath it. */
.ai-note { display: flex; align-items: flex-start; gap: 8px; color: var(--slate); font-size: 14px; margin-top: 10px; }
.ai-note .icon { width: 16px; height: 16px; margin-top: 2px; color: var(--slate); flex: 0 0 auto; }
.ai-note a { font-weight: 700; }
.list-title .pill { margin-left: 4px; vertical-align: 2px; }
.topbar-user .btn-sm { padding: 0 10px; }
.topbar .wordmark { flex: 0 0 auto; white-space: nowrap; }   /* the logo never wraps to make room for buttons */

/* ---------- small screens: the column is the whole width ---------- */

@media (max-width: 599px) {
  :root { --gutter: 12px; }
  /* An admin's header carries Admin + Log out; a phone has no room for the name as well, and the
     admin knows who they are (the Admin page marks their row "You"). */
  .card { padding: 16px; border-radius: 12px; }
  .card-head { padding: 14px 16px 10px; }
  .card-foot { padding: 10px 16px 14px; }
  .card-flush .daytabs, .card-flush .seg-wide { margin-left: 16px; margin-right: 16px; }
  .lesson { padding: 10px 16px; }
  .lesson-edit { padding: 14px 16px; }
  .empty-row { padding: 20px 16px; }
  .h1 { font-size: 24px; }
  .facts dt { flex-basis: 92px; }
  .row-actions .btn { flex: 1 1 100%; }
  .tt-card .row-actions .btn { flex: 1 1 40%; }
}

@media (prefers-reduced-motion: reduce) {
  .btn, .toast, .sheet, .sheet-backdrop, .savebar, .input { transition: none; }
}

/* =====================================================================================
   Motion. Everything that moves for flavour (not the spinner, which is information) lives
   here, so a person who asked their OS for less motion gets a still page. */

@media (prefers-reduced-motion: no-preference) {
  /* Views fade in: app.js replaces #view's children on every route, so each new child runs it. */
  #view > * { animation: nxt-fade .15s ease-out both; }
  @keyframes nxt-fade { from { opacity: 0; } to { opacity: 1; } }

  /* The mark on the login panel rings once when the page opens, hung from its top like a bell. */
  .auth-mark { transform-origin: 50% 10%; animation: nxt-ring .9s cubic-bezier(.36, .07, .19, .97) .25s 1 both; }
  @keyframes nxt-ring {
    0% { transform: rotate(0); }
    18% { transform: rotate(6deg); }
    38% { transform: rotate(-6deg); }
    56% { transform: rotate(4deg); }
    72% { transform: rotate(-3deg); }
    86% { transform: rotate(1.5deg); }
    100% { transform: rotate(0); }
  }

  .dropzone { transition: background-color .12s ease, border-color .12s ease; }
  .wg-block { transition: box-shadow .12s ease, filter .12s ease; }
  .wk-bar { transition: height .3s ease; }

  /* Cards lift 2px under a pointer on a laptop. Not on touch (nothing to hover) and never on a
     phone (the phone layout is frozen). */
  @media (min-width: 900px) and (hover: hover) {
    .card { transition: transform .15s ease, box-shadow .15s ease; }
    .card:not(.card-quiet):not(.progress):hover {
      transform: translateY(-2px);
      box-shadow: var(--shadow-lift);
    }
  }
}

/* =====================================================================================
   Desktop: "use the screen" (>= 900px). Nothing below this line reaches a phone. */

/* ---------- shell ---------- */

@media (min-width: 900px) {
  :root { --gutter: 32px; }

  .h1, .h2, .h3, .sheet-title, .display { font-family: var(--font-display); }
  .h1 { font-size: 44px; line-height: 1.08; font-weight: 800; letter-spacing: -0.02em; }
  .h2 { font-size: 24px; line-height: 1.2; font-weight: 700; letter-spacing: -0.01em; }
  .h3 { font-size: 17px; }
  .sub { font-size: 17px; }

  .page { max-width: var(--page-max); padding-top: 28px; position: relative; }
  .topbar-in { max-width: var(--page-max); min-height: 64px; }
  .wordmark .wm-png { height: 36px; }
  .wordmark .wm-mark { width: 32px; height: 32px; }
  .savebar-in { max-width: var(--page-max); }
  .topbar-gear { width: auto; flex: 0 0 auto; padding: 0 12px; gap: 8px; font: 700 15px var(--font); white-space: nowrap; }
  .topbar-gear .lbl { display: inline; }
  .help-dot { width: 24px; height: 24px; font-size: 14px; line-height: 24px; margin-left: 9px; }
  .card > .help-head { margin-bottom: 12px; }

  .view-head { margin: 4px 0 24px; }
  .view-head .sub { margin-top: 8px; }
  .backlink { margin-bottom: 4px; }

  .card { padding: 24px; margin-bottom: 20px; }
  .card > .h2 { margin-bottom: 12px; }
  .card-head { padding: 22px 24px 12px; }
  .card-flush { padding: 0; }
  .card-flush .daytabs, .card-flush .seg-wide { margin-left: 24px; margin-right: 24px; }
  .card-foot { padding: 12px 24px 18px; }
  .lesson { padding: 10px 24px; }
  .lesson-edit { padding: 18px 24px; }
  .empty-row { padding: 22px 24px; }
  .row-actions .btn { flex: 0 1 auto; }      /* buttons sit at their own width on a wide row */

  /* The brand motif: the mark's bars as a faint watermark, top-right, behind a hero. The hero
     is position:relative WITHOUT a z-index, so the motif's z-index -1 puts it into the page's
     own stacking context: under every in-flow block (the hero text, the cards that follow) and
     above only the Chalk. Width kept inside the column so it can never add sideways scroll. */
  .has-motif { position: relative; }
  .has-motif > .motif {
    position: absolute; z-index: -1; top: -110px; right: 0;
    width: 600px; height: 600px; opacity: var(--motif-opacity); color: var(--ink); pointer-events: none;
  }

  /* ---------- login: the split screen ---------- */

  .auth-split { flex-direction: row; min-height: 100dvh; }
  .auth-panel { flex: 0 0 44%; padding: 56px 48px; }
  .auth-strip-logo { display: none; }
  .auth-panel .motif {
    display: block; position: absolute; top: -120px; right: -140px;
    width: 620px; height: 620px; opacity: var(--motif-opacity); color: var(--on-ink); pointer-events: none;
  }
  .auth-panel-in { display: block; position: relative; width: 100%; max-width: 420px; }
  .auth-mark { display: block; width: 180px; height: 180px; margin: 0 0 28px; filter: var(--logo-on-ink-filter); }
  .auth-headline { font: 800 46px/1.06 var(--font-display); letter-spacing: -0.02em; color: var(--on-ink); margin: 0 0 28px; }
  .auth-points li { display: flex; align-items: flex-start; gap: 12px; margin-top: 12px; font-size: 17px; line-height: 1.4; color: var(--on-ink-dim); }
  .auth-points .icon { width: 22px; height: 22px; color: var(--orange); flex: 0 0 auto; margin-top: 2px; }
  .auth-wrap { flex: 1 1 auto; max-width: none; justify-content: center; padding: 48px; }
  .auth-wrap > * { width: 100%; max-width: 440px; margin-left: auto; margin-right: auto; }
  .auth-card { padding: 28px; }

  /* ---------- Home: hero + card grid ---------- */

  .hero { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin: 8px 0 28px; }
  .hero .sub { margin-top: 8px; max-width: 560px; }
  .hero .btn { flex: 0 0 auto; }
  .tt-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 20px; }
  .tt-grid .card { margin-bottom: 0; display: flex; flex-direction: column; }
  .tt-card .h2 { font-size: 24px; font-weight: 800; }
  .tt-card .row-actions { margin-top: auto; padding-top: 16px; }
  .tt-card .row-actions .btn { flex: 1 1 40%; }

  /* The week strip: one bar per shown day, height = classes that day (scaled to the busiest
     day on that card), Ring Orange at 70% on a Chalk track, day letters under it. --h is set
     inline by app.js from the counts alone. */
  .wk-strip { display: flex; gap: 6px; margin: 16px 0 2px; }
  .wk-day { flex: 1 1 0; display: flex; flex-direction: column; align-items: center; gap: 5px; min-width: 0; }
  /* The track is the well tone with a hairline, not bare Chalk: on a white card Chalk alone is
     invisible, so a quiet day (or an empty timetable) read as a gap rather than an empty bar. */
  .wk-track { width: 100%; height: 44px; border-radius: 5px; background: var(--well); border: 1px solid var(--line); display: flex; align-items: flex-end; overflow: hidden; }
  .wk-bar { width: 100%; height: var(--h, 0%); background: var(--orange); opacity: .7; border-radius: 5px 5px 0 0; }
  .wk-l { font-size: 11px; font-weight: 700; color: var(--slate); letter-spacing: .04em; }

  .card.empty { padding: 56px 32px; }
  .card.empty .h1 { margin: 10px 0 10px; }
  .card.empty .hint { max-width: 460px; margin: 0 auto 26px; font-size: 17px; }
  .card.empty .btn-block { max-width: 320px; margin: 0 auto; }
  .empty-mark { display: block; width: 128px; height: 128px; margin: 0 auto; filter: var(--logo-filter); }

  /* ---------- New timetable: drop zone + tips ---------- */

  .new-layout { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 24px; align-items: start; }
  .new-layout .card { margin-bottom: 20px; }
  .dropzone {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    min-height: 260px; padding: 24px; margin-bottom: 14px;
    border: 2px dashed var(--orange); border-radius: 12px; background: var(--field);
    text-align: center; color: var(--ink);
  }
  /* The zone is a div (it is the drop target and holds the Remove button); the picker opens
     from a <label> that fills it, so a click anywhere still chooses a file. A label may not
     contain a button, which is why the picked-file row sits beside the label, not inside it. */
  .dz-main { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; width: 100%; flex: 1 1 auto; cursor: pointer; }
  .dropzone.is-busy .dz-main { cursor: default; }
  .dropzone:hover { background: var(--orange-wash-faint); }
  .dropzone.is-over { background: var(--orange-wash-strong); border-style: solid; }
  .dropzone:focus-within { box-shadow: var(--focus-ring); }
  .dz-icon { color: var(--orange); }
  .dz-icon .icon { width: 40px; height: 40px; }
  .dz-title { font-size: 19px; font-weight: 700; }
  .dz-sub { color: var(--slate); }
  .dz-sub b { color: var(--orange); font-weight: 700; }
  /* app.js attachThumb() drops the photo's <img> into .dz-thumb; a PDF shows the file icon. */
  .dz-thumb { display: block; width: 100%; max-width: 420px; height: 220px; border-radius: 8px; overflow: hidden; border: 1px solid var(--line); background: var(--well); }
  .dz-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
  .dropzone.is-busy { cursor: default; border-color: var(--line-strong); }
  .dz-file { display: flex; align-items: center; justify-content: center; gap: 10px; max-width: 100%; margin-top: 4px; }
  .dz-file .icon { color: var(--slate); }
  .dz-name { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .dz-size { color: var(--slate); white-space: nowrap; }
  .dz-change { color: var(--orange); font-weight: 700; white-space: nowrap; cursor: pointer; }   /* a second label for the same input */
  .dz-change:hover { color: var(--deep-ring); text-decoration: underline; }
  .dz-remove { margin-left: 4px; width: 36px; height: 36px; flex-basis: 36px; }
  .tips { margin: 4px 0 18px; }
  .tips li { display: flex; align-items: flex-start; gap: 12px; padding: 8px 0; border-top: 1px solid var(--line); }
  .tips li:first-child { border-top: 0; }
  .tips .icon { color: var(--orange); flex: 0 0 auto; width: 22px; height: 22px; margin-top: 1px; }
  .tips b { display: block; }
  .tips span { color: var(--slate); font-size: 16px; }   /* body copy: never under 16px on a laptop */
  .card.progress.side { padding: 48px 24px; }

  /* ---------- Step 3, Add to calendar: subscribe left, the phone square and computer routes right ---------- */

  /* Subscribing is the recommendation, so it takes the wider half. The fold with the walk-
     throughs sits under both columns at full width. */
  .dl-layout { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 24px; align-items: start; }

  /* One card with one line and one button (step 3 while something is missing, and Settings)
     does not stretch across the whole 1180px column. */
  .card-narrow { max-width: 560px; }
  .card-narrow > .btn-block { display: inline-flex; width: auto; min-width: 280px; }

  /* ---------- Admin: two columns ---------- */

  .admin-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; align-items: start; }
  .admin-grid .list-row { min-height: 60px; padding: 10px 0; }
  .admin-grid .list-meta { margin-top: 2px; }
}

/* ---------- Review at >= 1000px: the week grid and the sticky right column ---------- */

@media (min-width: 1000px) {
  /* The grid takes what is left after a fixed-ish right column: the forms there are single
     column and need about 380px, while every day column of the grid wants every pixel it can
     get for "Teacher · Room". A 7/5 split left each day about 94px at 1440. */
  .review-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 380px); gap: 24px; align-items: start; }
  .review-warn { grid-column: 1 / -1; }
  /* Sticks under the 64px header and scrolls inside itself when it is taller than the window.
     The 4px padding/negative margin keeps the cards' shadows from being clipped at its edge. */
  .review-side { position: sticky; top: 80px; max-height: calc(100vh - 96px); overflow-y: auto; padding: 4px; margin: -4px; }
  /* While the draft is dirty the fixed save bar (10 + 44 + 10 + 1px border) covers the bottom
     of the window; the column is shortened by that much so its own scrolling can reach the
     last card's buttons. .page gets the same allowance at line ~115. */
  body.has-savebar .review-side { max-height: calc(100vh - 96px - 66px - var(--sab)); }
  .review-side .card:last-child { margin-bottom: 4px; }

  /* The timetable. One CSS grid: column 1 is the time axis, then one column per shown day; row
     1 is the day header, then one row per 15-minute slot (--slot tall). app.js sets the column
     and row counts inline; lessons are placed with grid-row / grid-column and two that overlap
     share the column through --lane / --lanes. Every value in those inline styles is a number
     computed here or a colour from the fixed list, never text from the sheet. */
  /* Sideways scrolling lives inside the card only: with six or seven days app.js gives each
     column a floor (minmax(92px, 1fr)) so a Mon-Sun timetable scrolls here instead of
     squeezing to slivers; five days always fit. */
  .wg-wrap { overflow-x: auto; overflow-y: hidden; padding-bottom: 12px; }
  /* 22px a quarter hour: three slots (a 45-58 minute lesson snapped to the quarter) is then
     64px, enough for subject, two lines of "teacher · room" and the time (see .is-tall). */
  .wg { display: grid; --slot: 22px; position: relative; }
  .wg-corner { grid-column: 1; grid-row: 1; border-bottom: 1px solid var(--line); }
  .wg-dayhead {
    grid-row: 1; display: flex; align-items: center; justify-content: space-between; gap: 4px;
    padding: 8px 3px 8px 6px; border-bottom: 1px solid var(--line); border-left: 1px solid var(--line);
    font-weight: 700; font-size: 14px; min-width: 0;
  }
  /* "Mon 6" stays on one line beside the + button; a column too narrow for it clips the count. */
  .wg-dayhead > span { white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .wg-dayhead .n { color: var(--slate); font-weight: 600; font-size: 12px; margin-left: 4px; }
  .wg-dayhead.is-today .d { color: var(--orange); }
  /* 28px so "Wed 3" and the + share a 92px column (the floor app.js sets for six or seven days). */
  .wg-add {
    display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; flex: 0 0 28px; border: 0; border-radius: 7px;
    background: transparent; color: var(--slate); cursor: pointer;
  }
  .wg-add:hover { background: var(--well); color: var(--ink); }
  .wg-add:focus-visible { outline: none; box-shadow: var(--focus-ring); }
  .wg-add .icon { width: 18px; height: 18px; }
  .wg-axis {
    grid-column: 1; align-self: start; transform: translateY(-8px);
    padding-right: 8px; text-align: right;
    font-size: 12px; font-weight: 600; color: var(--slate); font-variant-numeric: tabular-nums;
  }
  .wg-axis.is-end { align-self: end; transform: translateY(8px); }   /* the closing hour, under the last line */
  .wg-col { grid-row: 2 / -1; border-left: 1px solid var(--line); z-index: 0; }
  .wg-col.is-today { background: var(--orange-wash-today); }
  .wg-line { grid-column: 2 / -1; border-top: 1px solid var(--line); z-index: 1; pointer-events: none; }
  .wg-line.half { border-top: 1px dashed var(--line-soft); }
  .wg-block {
    position: relative; z-index: calc(2 + var(--lane, 0)); justify-self: start; align-self: stretch;
    /* Lanes: a lesson that overlaps another takes 1/--lanes of the column, but never less than
       48px (room for "Maths" on one line); past that the later lanes slide left over the earlier
       ones (each one step higher in z, still inside the column), so three lessons at once are
       three blocks with a readable word each, not slivers. */
    width: max(calc(100% / var(--lanes, 1) - 6px), 48px);
    margin-left: min(calc(100% * var(--lane, 0) / var(--lanes, 1) + 3px), calc(100% - 51px));
    margin-top: 1px; margin-bottom: 1px; min-width: 0; min-height: 0;
    display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
    padding: 3px 4px 3px 7px; overflow: hidden;   /* 4px right: "Business Studies" fits a 132px column by a pixel */
    border: 0; border-left: 3px solid var(--c); border-radius: 6px;
    background: var(--t); color: var(--ink);
    font: inherit; text-align: left; line-height: 1.2; cursor: pointer;
  }
  .wg-block.is-short { padding-top: 0; padding-bottom: 0; justify-content: center; gap: 0; }
  .wg-block.is-short .wg-s { font-size: 11.5px; line-height: 1; }
  .wg-block.is-short .wg-m, .wg-block.is-short .wg-t, .wg-block.is-short .wg-both { display: none; }
  .wg-block:hover { filter: var(--wg-hover); }
  .wg-block:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--c); z-index: 10; }
  .wg-block.is-editing { box-shadow: 0 0 0 2px var(--c); z-index: 10; }
  .wg-block > span { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* A block of three slots or more (64px; four when it also says "Both weeks") has room for
     "Teacher · Room" on two lines above the time; shorter blocks keep the one clipped line. */
  .wg-block.is-tall > span.wg-m { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-height: 1.15; }
  /* A shared column (is-lane, app.js): the subject alone, wrapped and hyphenated, so a narrow
     lane reads as a word and not its first letter; the time is where the block sits, the rest
     is in the tooltip. After .is-tall on purpose: same specificity, so the meta line stays
     hidden in a tall shared block too. */
  .wg-block.is-lane { padding-left: 5px; padding-right: 4px; }
  .wg-block.is-lane > span.wg-m, .wg-block.is-lane > span.wg-t, .wg-block.is-lane > span.wg-both { display: none; }
  .wg-block.is-lane > span.wg-s { white-space: normal; font-size: 11.5px; line-height: 1.15; hyphens: auto; overflow-wrap: anywhere; }
  .wg-s { font-weight: 700; font-size: 13px; }
  .wg-m { font-size: 11.5px; color: var(--slate); }
  .wg-t { font-size: 11px; color: var(--slate); font-variant-numeric: tabular-nums; margin-top: auto; }
  .wg-both { font-size: 10.5px; color: var(--amber-ink); font-weight: 700; }
  .wg-empty { grid-column: 2 / -1; align-self: center; text-align: center; color: var(--slate); z-index: 2; padding: 12px; }
  .lesson-panel .lesson-edit { padding: 0; background: transparent; border-bottom: 0; }
  .lesson-panel .panel-hint { color: var(--slate); font-size: 16px; margin-bottom: 14px; }
}

/* ---------- the date picker (assets/datepicker.js) ----------
   Portalled to <body> and positioned fixed, so a card with its own scrolling can never clip it. */

.dp {
  display: none;
  position: fixed;
  z-index: 60;
  width: 288px;
  padding: 12px;
  /* place() caps the height when the panel will not fit beside its box; without somewhere for
     the overflow to go, the bottom of the calendar would simply be unreachable — a fixed
     element past the viewport edge cannot be scrolled to. */
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow-pop);
}
.dp-head { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.dp-title { flex: 1; text-align: center; font-weight: 700; font-size: 16px; color: var(--ink); }
.dp-nav {
  width: 36px; height: 36px; flex: 0 0 auto;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--card); color: var(--ink);
  font-size: 20px; line-height: 1; cursor: pointer;
}
.dp-nav:hover { background: var(--well); }
.dp-days, .dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dp-days span {
  text-align: center; font-size: 12px; font-weight: 700;
  color: var(--slate); text-transform: uppercase; letter-spacing: .04em; padding: 4px 0;
}
.dp-days span.is-we { color: var(--dp-weekend); }
.dp-pad { display: block; }
.dp-day {
  height: 36px; border: 0; border-radius: 8px;
  background: transparent; color: var(--ink);
  font-size: 15px; font-weight: 600; cursor: pointer;
}
.dp-day:hover { background: var(--well); }
.dp-day.is-we { color: var(--slate); }
.dp-day.is-today { box-shadow: inset 0 0 0 1px var(--line-strong); }
.dp-day.is-cursor { outline: 2px solid var(--orange); outline-offset: -2px; }
.dp-day.is-picked { background: var(--orange); color: var(--on-orange); }
.dp-day.is-picked:hover { background: var(--deep-ring); }
.dp-foot { display: flex; justify-content: space-between; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); }
.dp-link {
  min-height: 36px; padding: 0 8px;
  border: 0; background: none; color: var(--orange);
  font-size: 15px; font-weight: 700; cursor: pointer;
}
.dp-link:hover { color: var(--deep-ring); text-decoration: underline; }
/* A date box opens a calendar, so it says so. On touch it is read-only: the calendar is the input. */
.input.has-dp { cursor: pointer; }

/* ---------- Step 3, Add to calendar: the subscribe card ---------- */

.sub-card { border-color: var(--orange-edge); }
.sub-head { margin-bottom: 8px; }
.sub-lead { margin: 0 0 14px; color: var(--slate); font-size: 16px; line-height: 1.45; }
.sub-lead b { color: var(--ink); }
.sub-card .btn + .btn { margin-top: 10px; }
.sub-card .btn + .field-note { margin-top: 10px; }
.sub-card .input[readonly] { background: var(--well); font-size: 15px; }
/* The status line once a calendar has fetched the link: Term Green as text (--green-ink,
   because --green alone is under AA on a white card), the tick from icons.js beside it. */
.feed-ok { display: inline-flex; align-items: center; gap: 4px; color: var(--green-ink); font-weight: 700; }
.feed-ok .icon { width: 16px; height: 16px; }

/* Under the stepper on step 3: what is in the calendar, in one line (no help dot: a dot sits
   beside a heading or a label, never inside a sentence). Any hint under it - no closed days
   set; things worth checking - follows at the same size. */
.facts-line { margin: -4px 0 14px; color: var(--slate); font-size: 15px; }
.facts-hint { margin: 0 0 14px; }
/* The foot of step 3: the way home, centred, at the back link's 44px. */
.page-foot .backlink { margin: 0; padding: 0 8px; }

/* The square that opens this page on a phone (laptop only, step 3). Drawn in the page by the
   encoder at the top of app.js - an inline <svg> in module units, so the width below is the
   only thing that decides how big it is. `crispEdges` (set on the element) keeps the modules
   square at any size; a light ground and a quiet zone are part of the SVG itself, because a QR
   against a coloured card will not read. 200px is well above what a camera needs for the
   29-module code a 40-character address makes. */
.qr-box { margin: 4px auto 0; text-align: center; width: 200px; max-width: 100%; }
.qr-box .qr { display: block; width: 100%; height: auto; margin: 0 auto; border-radius: 4px; }
.qr-card .sub-lead { margin-bottom: 8px; }

/* ---------- the three steps (2026-09-04) ----------
   Under the title of every timetable view: 1 Classes, 2 Term dates, 3 Add to calendar. Each is
   a live link (a go-step action, so step 3 saves first). A tick means the data is there —
   classes present, a first and last day plus closed days, a calendar following the link — and
   the orange one is where you are. Current wins over done: an orange circle with a tick in it.
   The whole <a> is the tap target (44px), circle and label together. */
.steps { display: flex; margin: 14px 0 0; padding: 0; list-style: none; }
.steps li { flex: 1 1 0; min-width: 0; position: relative; }
.step {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  min-height: 44px; padding: 2px 2px 4px; border-radius: 8px;
  color: var(--slate); font-size: 13px; font-weight: 700; text-align: center; text-decoration: none;
}
.step:hover { color: var(--ink); text-decoration: none; }
.step:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.step-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%; position: relative; z-index: 1;
  border: 2px solid var(--line-strong); background: var(--card); color: var(--slate);
  font-size: 13px; line-height: 1; font-variant-numeric: tabular-nums;
}
.step-n .icon { width: 16px; height: 16px; }
/* "Add to calendar" is 100px at 13px bold and a 320px phone gives each step 96px, so a label
   may take two lines; it never clips the word the step is named for. */
.step-l { display: block; max-width: 100%; line-height: 1.15; overflow-wrap: normal; }
/* The hairline joining the circles, drawn from every step but the first, behind the circles:
   2px top padding + 14px radius puts the centre at 16px, and a 2px line centred there starts at
   15px. Green up to a step that is done. */
.steps li + li::before { content: ''; position: absolute; top: 15px; left: calc(-50% + 16px); right: calc(50% + 16px); border-top: 2px solid var(--line-strong); }
.steps li.is-done + li::before { border-color: var(--green); }
.step.is-done { color: var(--ink); }
.step.is-done .step-n { border-color: var(--green); background: var(--green-bg); color: var(--green-ink); }
.step.is-current { color: var(--orange); }
.step.is-current .step-n { border-color: var(--orange); background: var(--orange); color: var(--on-orange); }

/* The card at the foot of steps 1 and 2: what comes next, or what stops it. */
.step-foot > .h2 { margin-bottom: 6px; }
.step-foot .step-next { margin: 0 0 14px; color: var(--slate); font-size: 15px; line-height: 1.45; }

/* From 900px the stepper sits on the title row, at its own width, circle beside label, no
   joining line: three words do not need a progress bar across an 1180px column. */
@media (min-width: 900px) {
  .view-head.has-steps { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
  .view-head.has-steps .view-title { min-width: 0; }
  .view-head.has-steps .steps { margin: 0 0 6px; flex: 0 0 auto; gap: 26px; }
  .view-head.has-steps .steps li { flex: 0 0 auto; }
  .view-head.has-steps .steps li + li::before { display: none; }
  .view-head.has-steps .step { flex-direction: row; gap: 9px; padding: 4px 2px; font-size: 15px; }
  .view-head.has-steps .step-l { white-space: nowrap; }
}

/* Step 2 from 1000px (the same breakpoint as the week grid, so stepping from 1 to 2 does not
   flip between one and two columns): the first and last day with the Next card under it on the
   left, the closed days - a long list, or a scan panel - on the right. */
@media (min-width: 1000px) {
  .dates-layout { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 24px; align-items: start; }
}
