/* ===== CONTENTS (generated: tools/check_css_index.py --fix) =====
   The order they appear in. Names only, deliberately: line
   numbers in a checked-in file are wrong by the next commit.

   - Theme variables
   - Ten colours, each in light and dark
   - Base
   - Header
   - Compact weather location / travel widget
   - Weather location search
   - Weather tab
   - Weather page
   - Day-off countdown badge
   - All-done celebration
   - Section heading polish
   - Global search results header
   - Assignment edit pencil (rename Blackbaud assignment locally)
   - Assignment note (per-item personal note)
   - Personal to-do list card
   - The to-do editor sheet
   - "You might already have this"
   - Dedicated "To-dos" page (larger, grouped layout)
   - Pull-to-refresh indicator
   - Info chips row on Today
   - The next day you have school
   - Empty-state CTA card (#6)
   - Install (Add to Home Screen) nudge (#2)
   - Ghost link button (used in Tomorrow card)
   - Filter chips
   - Sections
   - The next two weeks
   - Week mini-calendar
   - Tab bar (Today / Week / Days off / Tests)
   - Visible filter bar (one big filter, not in 3-dot menu)
   - #3 — Weather 5-day forecast strip (reuses .week-cal/.week-day)
   - Day-detail bottom sheet
   - Summary details popup (Tests / Days off)
   - Tutorial
   - Game-style guided tour (spotlight coachmarks)
   - Lists that are prose, not rows
   - Release notes ("what's new") popup — reuses .tut-overlay/.tut-card
   - A day's detail, wherever it is shown
   - Assistant image attachment
   - Feedback screenshot attachment
   - "Daily Overview has updated" bar
   - Test alert banner
   - Progress bar
   - Stats card
   - The week board (simple mode)
   - The month grid on the board
   - The month, as a page rather than a grid
   - Files on a task
   - The picture, shown
   - A picture on one step
   - The boards list, in Settings
   - Light, dark, or whatever the device is doing
   - Subtasks
   - Repeat
   - Notes, with formatting
   - The ... menu
   - Search
   - Sharing
   - One share row per board
   - The page a shared link opens
   - The task card
   - The month grid
   - Card sections
   - Checkable assignments
   - Anything a finger has to find
   - Buttons
   - Dialog
   - Schedules (in settings dialog)
   - Theme picker
   - Logo + title
   - Dropdown menu
   - Full-screen overlays (password, loading)
   - Login key reveal (key screen + settings)
   - Onboarding location step
   - Onboarding: choose Computer vs Phone mode
   - Settings: layout toggle
   - Home: dismissible multi-sign-in-method notice
   - "Do today" suggestions
   - A day opened in place, under the grid it was tapped in
   - Day header
   - Greeting
   - Onboarding name step
   - Inline line-icons (replace the old emoji chrome)
   - Desktop view switcher (hidden until computer mode)
   - Computer mode (per-device desktop-friendly layout)
   - Computer-mode To-dos
   - Full-page Settings (route: /settings)
   - Two-level Settings (category nav + panels)
   - Email preferences accordion
   - Notifications, split by channel
   - New feature styles
   - Grouped list view — breathing room + wide-screen columns (#1)
   - Declutter feature styles (Batch 3)
   - One vertical rhythm for the page
   - The app, on the landing page
   - A phone is holding this
   - "Did you know" bar
   ===== end contents ===== */
/* Daily Overview — the whole stylesheet.
 *
 * This used to be three <style> blocks inside index.html: 3,476 lines in the
 * head and two more further down the body, 3,690 lines of CSS in a 14,160-line
 * HTML file. Nothing could be found in it, and tools/check_css.py had to parse
 * the whole document to do a job that is only about CSS.
 *
 * The three blocks are concatenated here in the order they appeared, so the
 * cascade is exactly what it was — a rule that used to win because it came
 * later still comes later.
 */

  /* ===== Theme variables ===== */
  :root {
    /* The notch and the home indicator, as numbers JavaScript can read.
       env() is only usable inside CSS, and the tutorial has to position a card
       in pixels without putting it under the status bar of an installed app. */
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);

    /* Green-in-dark, repeated here as the floor.

       Every colour below is a full set on body[data-theme], so this is only
       what shows in the half-second before the theme is applied and on the
       one screen that has no theme at all. It is deliberately the SAME values
       as body[data-theme="green"] - two copies of a default is a thing that
       drifts, so if one is edited the other has to be, and
       tests/test_theme_contrast.py checks they match. */
    --bg: #14161b;
    --bg-card: #1e2128;
    --bg-input: #191c22;
    --border: #2d313b;
    --border-soft: #23262e;
    --fg: #f5f5f5;
    --fg-muted: #8b8f98;
    --fg-faint: #6b7079;
    --fg-very-faint: #464b55;
    --accent: #4ade80;
    --warn: #f59e0b;
    --danger: #ef4444;
    --info: #60a5fa;
    --test-bg: #7c2d12;
    --test-fg: #fed7aa;
    --toggle-off: #2a2a2a;
    --toggle-knob: #ffffff;
  }
  /* ===== Ten colours, each in light and dark =====

     It used to be ten THEMES, two of which were the light ones - so "I want
     Ocean, but light" was not a thing anybody could ask for, and "Daylight"
     was really just Green-in-light wearing a different name.

     So the two questions are separated. `data-theme` is the colour;
     `data-mode` is light or dark. Ten times two, and the mode can also be
     "auto", which means the device decides - resolved in applyTheme() and
     written out as a concrete light or dark, so nothing in this file ever has
     to reason about three states.

     Every block is written by hand rather than derived. An accent that works
     on a near-black page is too bright on a near-white one - #4ade80 on white
     is 1.6:1, which is not a colour, it is a rumour - so each colour has two
     accents, and the light backgrounds are the hue at about 3% rather than
     plain white, which is what stops ten light themes being one light theme.

     tests/test_theme_contrast.py holds all twenty to real numbers. */

  /* --- Rose: the red end. Plum-black rather than grey-black, so the accent
         reads as chosen rather than as an alert. --- */
  body[data-theme="rose"] {
    --bg: #171115; --bg-card: #221a20; --bg-input: #1b1419; --border: #3a2c36; --border-soft: #2a2028;
    --fg: #f8eef4; --fg-muted: #b198a8; --fg-faint: #856c7c; --fg-very-faint: #58454f;
    --accent: #fb7185; --warn: #fbbf24; --danger: #f87171; --info: #93c5fd;
    --test-bg: #7f1d3d; --test-fg: #fecdd3; --toggle-off: #3a2c36; --toggle-knob: #ffffff;
  }
  body[data-theme="rose"][data-mode="light"] {
    --bg: #fdf5f7; --bg-card: #ffffff; --bg-input: #f8ecf0; --border: #ecd8e0; --border-soft: #f5e6ec;
    --fg: #26141b; --fg-muted: #7a5c67; --fg-faint: #a98d98; --fg-very-faint: #d8c2cb;
    --accent: #be123c; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #ffe4e6; --test-fg: #881337; --toggle-off: #d8c2cb; --toggle-knob: #ffffff;
  }

  /* --- Ember: warm charcoal, amber accent. --- */
  body[data-theme="ember"] {
    --bg: #17140f; --bg-card: #221d16; --bg-input: #1c1811; --border: #3a3227; --border-soft: #2b251c;
    --fg: #f6f1e8; --fg-muted: #a99a86; --fg-faint: #7d7062; --fg-very-faint: #554b40;
    --accent: #f0a34a; --warn: #fbbf24; --danger: #f87171; --info: #7dd3fc;
    --test-bg: #7c2d12; --test-fg: #fed7aa; --toggle-off: #3a3227; --toggle-knob: #ffffff;
  }
  body[data-theme="ember"][data-mode="light"] {
    --bg: #faf6ee; --bg-card: #fffdf8; --bg-input: #f3ede1; --border: #e3d9c6; --border-soft: #ece4d6;
    --fg: #2a2318; --fg-muted: #6f6553; --fg-faint: #9a8f7b; --fg-very-faint: #cdc3b0;
    --accent: #b45309; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #fde7c8; --test-fg: #7c2d12; --toggle-off: #cdc3b0; --toggle-knob: #ffffff;
  }

  /* --- Citrus: yellow, the hardest accent to make readable. At full
         saturation it glows and at low saturation it is brown; this is the
         middle, over an olive-black. On light it has to go darker still. --- */
  body[data-theme="citrus"] {
    --bg: #16150d; --bg-card: #201e13; --bg-input: #1a180f; --border: #383423; --border-soft: #29271a;
    --fg: #f7f4e6; --fg-muted: #a9a184; --fg-faint: #7c7660; --fg-very-faint: #534f41;
    --accent: #eab308; --warn: #f97316; --danger: #f87171; --info: #7dd3fc;
    --test-bg: #713f12; --test-fg: #fde68a; --toggle-off: #383423; --toggle-knob: #ffffff;
  }
  body[data-theme="citrus"][data-mode="light"] {
    --bg: #fbfaf0; --bg-card: #ffffff; --bg-input: #f5f3e4; --border: #e6e2c8; --border-soft: #f0edd9;
    --fg: #23210f; --fg-muted: #6b6647; --fg-faint: #9b9573; --fg-very-faint: #cecaac;
    --accent: #854d0e; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #fef3c7; --test-fg: #713f12; --toggle-off: #cecaac; --toggle-knob: #ffffff;
  }

  /* --- Lime: the biggest gap on the wheel was between yellow and green. --- */
  body[data-theme="lime"] {
    --bg: #121710; --bg-card: #1b2218; --bg-input: #151b13; --border: #2b3927; --border-soft: #202a1d;
    --fg: #eef6ea; --fg-muted: #9bb094; --fg-faint: #70876a; --fg-very-faint: #4c5c47;
    --accent: #a3e635; --warn: #fbbf24; --danger: #f87171; --info: #7dd3fc;
    --test-bg: #365314; --test-fg: #d9f99d; --toggle-off: #2b3927; --toggle-knob: #ffffff;
  }
  body[data-theme="lime"][data-mode="light"] {
    --bg: #f7fbf2; --bg-card: #ffffff; --bg-input: #eff5e8; --border: #dbe7cd; --border-soft: #e9f0df;
    --fg: #17210f; --fg-muted: #5c6b4d; --fg-faint: #8b9a7c; --fg-very-faint: #c6d3b6;
    --accent: #4d7c0f; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #ecfccb; --test-fg: #365314; --toggle-off: #c6d3b6; --toggle-knob: #ffffff;
  }

  /* --- Green: the default. Its light side is what used to be called
         "Daylight", which was really this colour with the lights on. --- */
  body[data-theme="green"] {
    --bg: #14161b; --bg-card: #1e2128; --bg-input: #191c22; --border: #2d313b; --border-soft: #23262e;
    --fg: #f5f5f5; --fg-muted: #8b8f98; --fg-faint: #6b7079; --fg-very-faint: #464b55;
    --accent: #4ade80; --warn: #f59e0b; --danger: #ef4444; --info: #60a5fa;
    --test-bg: #7c2d12; --test-fg: #fed7aa; --toggle-off: #2a2a2a; --toggle-knob: #ffffff;
  }
  body[data-theme="green"][data-mode="light"] {
    --bg: #f7f8fa; --bg-card: #ffffff; --bg-input: #f1f3f7; --border: #e4e7ec; --border-soft: #eef0f4;
    --fg: #1a1d24; --fg-muted: #667085; --fg-faint: #98a2b3; --fg-very-faint: #d0d5dd;
    --accent: #16a34a; --warn: #d97706; --danger: #dc2626; --info: #2563eb;
    --test-bg: #fde7c8; --test-fg: #92400e; --toggle-off: #d0d5dd; --toggle-knob: #ffffff;
  }

  /* --- Mint: teal, between the green and the sky. Cool without being cold. --- */
  body[data-theme="mint"] {
    --bg: #0d1717; --bg-card: #142222; --bg-input: #101b1b; --border: #21393a; --border-soft: #182a2a;
    --fg: #e8f6f4; --fg-muted: #8fb0ad; --fg-faint: #648784; --fg-very-faint: #435b59;
    --accent: #2dd4bf; --warn: #fbbf24; --danger: #fb7185; --info: #60a5fa;
    --test-bg: #134e4a; --test-fg: #99f6e4; --toggle-off: #21393a; --toggle-knob: #ffffff;
  }
  body[data-theme="mint"][data-mode="light"] {
    --bg: #f2fbfa; --bg-card: #ffffff; --bg-input: #e9f5f4; --border: #cde5e2; --border-soft: #ddefed;
    --fg: #0f2120; --fg-muted: #4e6d6a; --fg-faint: #7d9b98; --fg-very-faint: #b9d2cf;
    --accent: #0f766e; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #ccfbf1; --test-fg: #134e4a; --toggle-off: #b9d2cf; --toggle-knob: #ffffff;
  }

  /* --- Ocean: crisp deep navy, sky-blue accent. --- */
  body[data-theme="ocean"] {
    --bg: #0d1620; --bg-card: #15212e; --bg-input: #111b27; --border: #243749; --border-soft: #1a2937;
    --fg: #e6eef5; --fg-muted: #92a7bc; --fg-faint: #647d92; --fg-very-faint: #44586b;
    --accent: #38bdf8; --warn: #fbbf24; --danger: #fb7185; --info: #60a5fa;
    --test-bg: #0c4a6e; --test-fg: #bae6fd; --toggle-off: #243749; --toggle-knob: #ffffff;
  }
  body[data-theme="ocean"][data-mode="light"] {
    --bg: #f2f8fd; --bg-card: #ffffff; --bg-input: #e9f2fa; --border: #cfe0ee; --border-soft: #deebf6;
    --fg: #10202d; --fg-muted: #4c6b7f; --fg-faint: #7d97ac; --fg-very-faint: #bbcfdf;
    --accent: #0369a1; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #e0f2fe; --test-fg: #0c4a6e; --toggle-off: #bbcfdf; --toggle-knob: #ffffff;
  }

  /* --- Indigo: deep indigo with a soft violet accent. --- */
  body[data-theme="indigo"] {
    --bg: #14141f; --bg-card: #1d1d2e; --bg-input: #191926; --border: #2e2e46; --border-soft: #242437;
    --fg: #eceefb; --fg-muted: #9b9cc4; --fg-faint: #6c6d96; --fg-very-faint: #494a6b;
    --accent: #8b8cf9; --warn: #fbbf24; --danger: #fb7185; --info: #818cf8;
    --test-bg: #3730a3; --test-fg: #c7d2fe; --toggle-off: #2e2e46; --toggle-knob: #ffffff;
  }
  body[data-theme="indigo"][data-mode="light"] {
    --bg: #f6f6fd; --bg-card: #ffffff; --bg-input: #eeeefa; --border: #d9d9f0; --border-soft: #e8e8f7;
    --fg: #16162a; --fg-muted: #5b5b84; --fg-faint: #8b8bb0; --fg-very-faint: #c6c6e0;
    --accent: #4338ca; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #e0e7ff; --test-fg: #312e81; --toggle-off: #c6c6e0; --toggle-knob: #ffffff;
  }

  /* --- Orchid: magenta, past violet and before the red comes round again. --- */
  body[data-theme="orchid"] {
    --bg: #16101a; --bg-card: #201827; --bg-input: #1a131f; --border: #372a41; --border-soft: #281e30;
    --fg: #f4eefa; --fg-muted: #a996b5; --fg-faint: #7d6c88; --fg-very-faint: #53465c;
    --accent: #e879f9; --warn: #fbbf24; --danger: #fb7185; --info: #93c5fd;
    --test-bg: #701a75; --test-fg: #f5d0fe; --toggle-off: #372a41; --toggle-knob: #ffffff;
  }
  body[data-theme="orchid"][data-mode="light"] {
    --bg: #fcf5fd; --bg-card: #ffffff; --bg-input: #f6ecf9; --border: #e8d5ef; --border-soft: #f2e5f6;
    --fg: #22132a; --fg-muted: #6d5779; --fg-faint: #9c86a8; --fg-very-faint: #d3c0dc;
    --accent: #a21caf; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #fae8ff; --test-fg: #701a75; --toggle-off: #d3c0dc; --toggle-knob: #ffffff;
  }

  /* --- Slate: no colour at all, for people who want none. Every set of
         themes needs the one that is just grey. --- */
  body[data-theme="slate"] {
    --bg: #131518; --bg-card: #1c1f24; --bg-input: #17191e; --border: #2c3138; --border-soft: #21252b;
    --fg: #eef1f4; --fg-muted: #949aa3; --fg-faint: #6d747e; --fg-very-faint: #4a5058;
    --accent: #a1a1aa; --warn: #fbbf24; --danger: #f87171; --info: #7dd3fc;
    --test-bg: #3f3f46; --test-fg: #e4e4e7; --toggle-off: #2c3138; --toggle-knob: #ffffff;
  }
  body[data-theme="slate"][data-mode="light"] {
    --bg: #f6f7f9; --bg-card: #ffffff; --bg-input: #eef0f3; --border: #dde1e7; --border-soft: #e9ecf0;
    --fg: #14171b; --fg-muted: #58606b; --fg-faint: #89919c; --fg-very-faint: #c4cad2;
    --accent: #52525b; --warn: #b45309; --danger: #b91c1c; --info: #1d4ed8;
    --test-bg: #e4e4e7; --test-fg: #27272a; --toggle-off: #c4cad2; --toggle-knob: #ffffff;
  }

  /* ===== Base ===== */
  * { box-sizing: border-box; }
  html, body { margin: 0; background: var(--bg); transition: background 0.2s; }
  body {
    font-family: -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
    color: var(--fg);
    padding: 0 1.25rem env(safe-area-inset-bottom);
    -webkit-tap-highlight-color: transparent;
    min-height: 100vh;
  }

  /* ===== Header =====
     Notch/safe-area is baked into the header's own top padding so the
     background extends behind the status bar but content sits clear of it,
     whether the header is at rest or stuck during scroll. */
  header {
    position: sticky;
    top: 0;
    z-index: 30;
    background: var(--bg);
    margin: 0 -1.25rem;
    padding:
      calc(env(safe-area-inset-top, 0px) + 0.5rem)
      1.25rem
      0.55rem;
    border-bottom: 1px solid var(--border-soft);
  }
  #hourly { display: none; }
  .now-next-banner { display: none !important; }
  .updated-mini { display: none; }

  /* Slimmer top bar on phone — same look, just a little shorter than the
     44px action row so the header reads thin like before. */
  body[data-layout="phone"] header {
    padding-top: calc(env(safe-area-inset-top, 0px) + 0.4rem);
    padding-bottom: 0.4rem;
  }
  body[data-layout="phone"] .icon-btn { width: 38px; height: 38px; border-radius: 10px; }
  body[data-layout="phone"] .icon-btn svg { width: 18px; height: 18px; }
  body[data-layout="phone"] .user-avatar { height: 38px; padding: 3px 13px 3px 3px; font-size: 0.9rem; }
  body[data-layout="phone"] .ua-pic { width: 30px; height: 30px; }

  .row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
  /* .title-row and .logo-sm are declared under "Logo + title" below. They were
     also declared here, at half the size, and every property was overridden. */
  .greeting { display: none; }
  .meta-line {
    font-size: 0.78rem;
    color: var(--fg-muted);
    margin-top: 0.15rem;
    display: flex; align-items: center; gap: 0.4rem;
    flex-wrap: wrap;
  }
  .meta-line .dot {
    width: 3px; height: 3px;
    background: var(--fg-muted);
    border-radius: 50%;
    flex: 0 0 auto;
    opacity: 0.6;
  }
  .meta-line .w-emoji { font-size: 0.9rem; }
  .meta-line .w-high {
    color: var(--accent);
    font-weight: 600;
  }
  .weather { display: none; }
  .date { display: none; }

  .cal-sub-help {
    font-size: 0.85rem;
    color: var(--fg-muted);
    margin: 0 0 0.75rem;
  }
  .cal-copy-btn {
    width: 100%;
    background: none;
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 10px;
    padding: 0.7rem;
    font-size: 0.9rem;
    cursor: pointer;
    font-family: inherit;
    margin-top: 0.5rem;
    -webkit-tap-highlight-color: transparent;
  }
  .cal-sub-note {
    font-size: 0.8rem;
    color: var(--accent);
    margin: 0.6rem 0 0;
    word-break: break-all;
  }
  /* Saved-calendar state in the Calendar settings section (B6) */
  .cal-state { font-size: 0.9rem; font-weight: 600; display: inline-flex; align-items: center; }
  .cal-state-ok { color: var(--accent); }
  .cal-state-none { color: var(--fg-muted); font-weight: 500; }
  .cal-state-url {
    font-size: 0.78rem; color: var(--fg-muted);
    word-break: break-all; margin: 0.3rem 0 0.6rem;
  }
  /* School/Home calendar URL inputs must be full-width block fields. They reuse the
     .filter-search pill class, which only sizes correctly inside a flex row — but
     these sit in a block settings card, so force a clean full-width box (fixes the
     cramped/narrow input on phone). */
  #account-url-input {
    flex: none; width: 100%; box-sizing: border-box;
    border-radius: 10px; font-size: 0.9rem; margin-top: 0.2rem;
  }
  /* Save / Remove row wraps cleanly on very narrow screens. */
  .cal-btn-row { display: flex; gap: 0.5rem; margin-top: 0.6rem; flex-wrap: wrap; }
  .cal-btn-row .btn-primary { flex: 1 1 60%; }
  /* Big "subscribe" buttons: side by side on wide screens, stacked on narrow */
  .cal-sub-btns { display: flex; gap: 0.6rem; margin-top: 0.25rem; align-items: flex-start; }
  .cal-sub-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }
  .cal-sub-btn {
    width: 100%;
    display: flex; align-items: center; justify-content: center; gap: 0.4rem;
    background: var(--accent); color: var(--bg);
    border: 1px solid var(--accent); border-radius: 12px;
    padding: 1rem 1.1rem;
    font-size: 1rem; font-weight: 700; line-height: 1.2;
    cursor: pointer; font-family: inherit; text-align: center;
    -webkit-tap-highlight-color: transparent;
  }
  .cal-sub-btn:active { transform: scale(0.98); }
  .cal-sub-mini { font-size: 0.75rem; color: var(--fg-muted); margin: 0.45rem 0 0; line-height: 1.35; }
  @media (max-width: 560px) {
    .cal-sub-btns { flex-direction: column; }
  }
  /* #8 — On phone mode keep these subscribe buttons compact & easy to scan
     (computer mode keeps the larger style above). */
  body[data-layout="phone"] .cal-sub-btns { flex-direction: column; gap: 0.45rem; }
  body[data-layout="phone"] .cal-sub-btn {
    padding: 0.6rem 0.85rem; font-size: 0.88rem; border-radius: 10px;
    justify-content: flex-start;
  }
  body[data-layout="phone"] .cal-sub-mini { font-size: 0.72rem; margin-top: 0.3rem; }
  body[data-layout="phone"] .cal-copy-btn { padding: 0.55rem 0.85rem; font-size: 0.85rem; }

  /* ===== Compact weather location / travel widget ===== */
  .wloc-card { position: relative; }
  .wloc-dismiss {
    position: absolute; top: 0.6rem; right: 0.6rem;
    background: none; border: none; color: var(--fg-muted);
    font-size: 0.95rem; line-height: 1; cursor: pointer; padding: 0.25rem;
    border-radius: 6px; -webkit-tap-highlight-color: transparent;
  }
  .wloc-dismiss:hover { color: var(--fg); }
  .wloc-badge {
    font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
    background: var(--accent); color: #fff; border-radius: 999px; padding: 0.1rem 0.45rem;
  }
  .wloc-sub { font-size: 0.82rem; color: var(--fg-muted); margin-top: 0.15rem; }
  /* Travel-day management (Settings) */
  .travel-dates { display: flex; gap: 0.6rem; }
  .travel-date-lbl {
    flex: 1; display: flex; flex-direction: column; gap: 0.25rem;
    font-size: 0.8rem; color: var(--fg-muted); font-weight: 600;
  }
  .travel-date-lbl input[type="date"] {
    width: 100%; padding: 0.5rem 0.6rem; border-radius: 10px;
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    font-family: inherit; font-size: 0.9rem;
    /* Fixes the empty/broken-looking date box on iOS dark mode: gives the native
       control a consistent height and a visible placeholder (mm/dd/yyyy). */
    min-height: 2.6rem; color-scheme: dark;
  }
  /* Light themes: let the native date control match so text stays readable. */
  /* Light mode, whichever colour it is wearing. */
  body[data-mode="light"] .travel-date-lbl input[type="date"] { color-scheme: light; }
  .travel-list { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.8rem; }
  .travel-row {
    display: flex; align-items: center; gap: 0.6rem;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 10px; padding: 0.55rem 0.7rem; font-size: 0.9rem;
  }
  .travel-row-main { flex: 1; min-width: 0; }
  .travel-row-place { font-weight: 700; color: var(--fg); }
  .travel-row-dates { font-size: 0.8rem; color: var(--fg-muted); }
  .travel-row-remove {
    flex: 0 0 auto; background: none; border: 1px solid var(--border);
    color: var(--danger); border-radius: 8px; padding: 0.3rem 0.6rem;
    font-size: 0.8rem; font-weight: 600; cursor: pointer;
  }
  .travel-add { margin-top: 0.4rem; }
  /* Summer break countdown banner (#5) — warm amber, readable in every theme. */
  .summer-banner {
    background: rgba(251, 191, 36, 0.14);
    border: 1px solid rgba(251, 191, 36, 0.5);
    color: var(--fg);
    font-weight: 800; text-align: center; font-size: 1rem;
    padding: 0.85rem 1rem;
  }
  /* "Add to a class / calendar" (#6) — bigger, clearer tap targets */
  .todo-assign-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
  .todo-assign {
    background: none; border: 1px solid var(--border); color: var(--fg-muted);
    border-radius: 999px; padding: 0.4rem 0.85rem; font-size: 0.85rem; font-weight: 600;
    cursor: pointer; -webkit-tap-highlight-color: transparent; font-family: inherit;
    display: inline-flex; align-items: center; gap: 0.3rem; min-height: 34px;
  }
  .todo-assign:hover { color: var(--fg); border-color: var(--fg-muted); }
  .todo-assign.on { color: var(--accent); border-color: var(--accent); }
  /* Un-filed "Add to a calendar" reads as the primary action */
  .todo-assign.todo-assign-cta { color: var(--accent); border-color: var(--accent); border-style: dashed; }
  .todo-assign.todo-assign-cta:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .todo-unassign { margin-left: 0.25rem; opacity: 0.7; font-weight: 700; }
  .todo-file-hint { margin: 0 0 0.6rem; font-size: 0.82rem; color: var(--fg-muted); line-height: 1.45; }
  .todo-file-hint b { color: var(--fg); font-weight: 600; }
  .todo-unassign:hover { opacity: 1; color: var(--danger); }
  .assign-options { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.5rem; }
  .assign-opt {
    display: flex; align-items: center; gap: 0.5rem; width: 100%; text-align: left;
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    border-radius: 10px; padding: 0.7rem 0.85rem; font-size: 0.95rem; font-weight: 600;
    cursor: pointer; font-family: inherit;
  }
  .assign-opt:hover { border-color: var(--accent); }
  .assign-remove {
    display: block; width: 100%; margin-top: 0.7rem; background: none; border: none;
    color: var(--danger); font-size: 0.85rem; font-weight: 600; cursor: pointer; text-decoration: underline;
  }
  .promoted-tag {
    font-size: 0.68rem; font-weight: 700; color: var(--fg-muted);
    border: 1px solid var(--border); border-radius: 999px; padding: 0.05rem 0.4rem; margin-left: 0.3rem;
    white-space: nowrap;
  }
  .promoted-tag.late { color: var(--danger); border-color: var(--danger); }
  /* A to-do sitting inside its class's list. It IS an assignment row — the only
     difference is the tag saying it's yours, so the row reads as part of the
     class rather than as a visitor. */
  li.class-todo-li .promoted-tag { flex: 0 0 auto; }
  li.class-todo-li .item-summary { cursor: text; }
  li.class-todo-li .todo-delete { flex: 0 0 auto; }
  .day-todo-late {
    font-size: 0.7rem; font-weight: 700; color: var(--danger);
    margin-left: 0.35rem;
  }
  /* Home-calendar items: muted "Home" pill + time prefix on a bottom "Home" group */
  .home-pill {
    font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--fg-muted); border: 1px solid var(--border); border-radius: 999px;
    padding: 0.05rem 0.4rem; margin-left: 0.35rem; vertical-align: middle; white-space: nowrap;
  }
  .home-time { font-size: 0.8rem; color: var(--fg-muted); font-weight: 600; margin-right: 0.15rem; }
  .home-li { border-left: 3px solid var(--accent); }
  .cal-setup-link { display: inline-block; margin-top: 0.6rem; font-size: 0.85rem; font-weight: 600; color: var(--accent); text-decoration: none; }
  .cal-setup-link:hover { text-decoration: underline; }
  .cal-text-input {
    width: 100%; box-sizing: border-box; padding: 0.55rem 0.7rem; border-radius: 10px;
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    font-family: inherit; font-size: 0.9rem;
  }
  .cal-text-input:focus { border-color: var(--accent); outline: none; }
  /* Inline-editable calendar name in "Your calendars" */
  .cal-row-name { display: flex; align-items: center; gap: 0.35rem; min-width: 0; }
  .cal-row-icon { flex: 0 0 auto; }
  .cal-row-label {
    flex: 1 1 auto; min-width: 0; box-sizing: border-box;
    background: transparent; border: 1px solid transparent; border-radius: 8px;
    color: var(--fg); font-weight: 700; font-size: 0.9rem; font-family: inherit;
    padding: 0.2rem 0.4rem;
  }
  .cal-row-label:hover { border-color: var(--border); }
  .cal-row-label:focus { border-color: var(--accent); outline: none; background: var(--bg-input); }
  /* ===== Weather location search ===== */  .wloc-current {
    font-size: 0.95rem;
    font-weight: 600;
    margin-bottom: 0.6rem;
    display: flex; align-items: center; gap: 0.5rem;
    flex-wrap: wrap;
  }
  .wloc-until {
    font-size: 0.7rem;
    color: var(--fg-muted);
    font-weight: 400;
  }
  .wloc-reset {
    background: none; border: 1px solid var(--border);
    color: var(--fg-muted);
    border-radius: 999px;
    padding: 0.2rem 0.6rem;
    font-size: 0.7rem;
    cursor: pointer;
    font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .wloc-search-row {
    display: flex; gap: 0.4rem;
  }
  .wloc-gps-btn {
    display: block; width: 100%; margin-top: 0.4rem;
    background: transparent; color: var(--accent);
    border: 1px dashed var(--accent); border-radius: 8px;
    padding: 0.5rem 0.75rem; font-size: 0.9rem; font-weight: 600;
    cursor: pointer; font-family: inherit; -webkit-tap-highlight-color: transparent;
  }
  .wloc-gps-btn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .wloc-search-row input {
    flex: 1; min-width: 0;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 8px;
    padding: 0.5rem 0.75rem;
    font-size: 0.9rem;
    font-family: inherit;
  }
  .wloc-search-row button {
    background: var(--accent);
    border: none; color: var(--bg);
    border-radius: 8px;
    padding: 0.5rem 1rem;
    font-weight: 700;
    cursor: pointer;
    font-family: inherit;
  }
  .wloc-list {
    display: flex; flex-direction: column;
    margin-top: 0.5rem;
  }
  .wloc-item {
    text-align: left;
    background: none;
    border: none;
    border-bottom: 1px solid var(--border-soft);
    color: var(--fg);
    padding: 0.65rem 0.25rem;
    font-size: 0.92rem;
    cursor: pointer;
    font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .wloc-item:active { opacity: 0.6; }
  .wloc-chosen { margin: 0.6rem 0 0.5rem; font-size: 0.95rem; }
  .wloc-actions { display: flex; gap: 0.5rem; }
  .wloc-travel-row {
    display: flex; align-items: center; gap: 0.5rem;
    margin-top: 0.5rem; flex-wrap: wrap;
  }
  .wloc-travel-label {
    flex: 1; min-width: 0;
    font-size: 0.85rem; color: var(--fg-muted);
    display: flex; align-items: center; gap: 0.4rem;
  }
  .wloc-travel-label input {
    width: 3.2rem; flex: 0 0 auto;
    background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg); border-radius: 8px;
    padding: 0.4rem 0.5rem; font-size: 0.9rem;
    font-family: inherit; text-align: center;
  }
  .wloc-travel-row .wloc-btn { flex: 0 0 auto; width: auto; }
  .wloc-btn {
    flex: 1;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 8px;
    padding: 0.55rem;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .wloc-btn.primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--bg);
  }

  /* ===== Weather tab ===== */
  /* ===== Weather page =================================================== */
  /* PHONE base — original linear stack, with a slightly more compact rhythm. */
  .weather-hero {
    text-align: center;
    padding: 0.7rem 0.8rem !important;
  }
  .wh-emoji-big { font-size: 2.1rem; line-height: 1; margin-bottom: 0.2rem; }
  .wh-temp-big {
    font-size: 2rem;
    font-weight: 700;
    line-height: 1;
    color: var(--accent);
  }
  .wh-summary {
    margin-top: 0.3rem;
    color: var(--fg-muted);
    font-size: 0.88rem;
  }
  .weather-stats {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem;
    margin: 0.55rem 0;
  }
  .weather-stats .w-stat {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 0.55rem 0.35rem;
    text-align: center;
    min-width: 0;
  }
  .w-stat-num { font-size: 1.2rem; font-weight: 700; }
  .w-stat-label {
    font-size: 0.66rem; color: var(--fg-muted);
    text-transform: uppercase; letter-spacing: 0.05em;
    margin-top: 0.15rem;
  }

  /* COMPUTER redesign — wrapper that owns the vertical rhythm; .weather-main
     is a 2-up grid (hero | stats). The new classes are only rendered when
     body[data-layout="computer"]; phone keeps the linear stack above. */
  .weather-page { display: flex; flex-direction: column; gap: 0.8rem; }
  body[data-layout="computer"] .weather-main {
    display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: 1rem;
  }
  /* Six rem of temperature and five and a half of emoji, on a card that also
     stretched the full width of a desktop window: the number was ninety-six
     pixels tall and said one thing. It is still the biggest thing on the page
     at half the size, and the rest of the forecast now fits beside it. */
  body[data-layout="computer"] .weather-hero {
    padding: 0.9rem 1.1rem !important;
    display: flex; flex-direction: column; align-items: center; gap: 0.3rem;
  }
  body[data-layout="computer"] .wh-emoji-big { font-size: 2.6rem; margin: 0.1rem 0; }
  body[data-layout="computer"] .wh-temp-big {
    font-size: 3.1rem; font-weight: 800; font-variant-numeric: tabular-nums;
  }
  .wh-eyebrow {
    font-size: 0.7rem; font-weight: 700; letter-spacing: 0.12em;
    color: var(--fg-muted); text-transform: uppercase;
  }
  .wh-condition { font-size: 1.05rem; font-weight: 600; color: var(--fg); margin-top: 0.15rem; }
  .wh-feels { font-size: 0.9rem; color: var(--fg-muted); }
  .wh-sun {
    margin-top: 0.5rem; font-size: 0.85rem; color: var(--fg-muted);
    font-variant-numeric: tabular-nums;
  }
  .weather-stats-card { padding: 1rem !important; }
  .stats-grid {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.55rem;
  }
  .stat-tile {
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 12px; padding: 0.6rem 0.5rem; text-align: center;
    min-height: 3.4rem;
    display: flex; flex-direction: column; justify-content: center;
  }
  /* These two used to be defined twice - here, and again under a "Stats card"
     block for Home's three big square tiles, which won by being later. Home's
     tiles are gone (its three numbers are .dh-chip now, the same shape the
     computer's header line always used), so this is the only definition left
     and it is the one the Weather tiles actually wanted. */
  .stat-num {
    font-size: 1.4rem; font-weight: 700; color: var(--fg);
    font-variant-numeric: tabular-nums; line-height: 1.1;
  }
  .stat-num.warn { color: var(--warn); }
  .stat-num.danger { color: var(--danger); }
  .stat-label {
    font-size: 0.65rem; color: var(--fg-muted);
    text-transform: uppercase; letter-spacing: 0.05em;
    margin-top: 0.2rem;
  }
  .weather-hourly-card { padding: 0.9rem 1.1rem !important; }
  .weather-hourly-title { margin: 0 0 0.7rem; font-size: 0.95rem; }
  .hourly-strip {
    display: flex; gap: 0.5rem; overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: 0.4rem;
    -webkit-overflow-scrolling: touch;
  }
  .hourly-cell {
    flex: 0 0 72px; scroll-snap-align: start;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 12px; padding: 0.65rem 0.3rem;
    display: flex; flex-direction: column; align-items: center; gap: 0.25rem;
  }
  .hc-time { font-size: 0.78rem; color: var(--fg-muted); font-variant-numeric: tabular-nums; }
  .hc-emoji { font-size: 1.3rem; line-height: 1; }
  .hc-temp { font-size: 1.05rem; font-weight: 700; font-variant-numeric: tabular-nums; }
  .hc-rain { font-size: 0.72rem; color: var(--fg-muted); }
  .hc-rain-empty { visibility: hidden; }
  /* Phone Hourly — the original vertical list. */
  .hourly-list { display: flex; flex-direction: column; gap: 0; }
  .hl-row {
    display: grid;
    grid-template-columns: 60px 30px 1fr auto;
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--border-soft);
  }
  .hl-row:last-child { border-bottom: none; }
  .hl-time {
    font-size: 0.85rem;
    color: var(--fg-muted);
    font-variant-numeric: tabular-nums;
  }
  .hl-emoji { font-size: 1.1rem; text-align: center; }
  .hl-temp {
    font-size: 1rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }
  .hl-rain-cell { text-align: right; }
  .hl-rain {
    font-size: 0.75rem;
    color: var(--accent);
  }

  /* ===== Day-off countdown badge ===== */
  .countdown-badge {
    display: inline-block;
    background: var(--accent);
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    padding: 0.125rem 0.5rem;
    border-radius: 999px;
    margin-left: 0.5rem;
    text-transform: uppercase;
    vertical-align: 2px;
  }

  /* ===== All-done celebration ===== */
  .all-done {
    text-align: center;
    padding: 0.85rem 1rem;
    font-size: 0.98rem;
    font-weight: 600;
    color: var(--fg);
    animation: bounce-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .all-done .ad-emoji {
    display: block;
    font-size: 1.5rem;
    margin-bottom: 0.2rem;
    animation: wiggle 1.2s ease-in-out infinite;
  }
  .all-done .ad-sub {
    font-size: 0.8rem;
    color: var(--fg-muted);
    font-weight: 400;
    margin-top: 0.3rem;
  }
  @keyframes bounce-in {
    0% { transform: scale(0.7); opacity: 0; }
    100% { transform: scale(1); opacity: 1; }
  }
  @keyframes wiggle {
    0%, 100% { transform: rotate(-5deg); }
    50% { transform: rotate(5deg); }
  }

  /* ===== Section heading polish ===== */
  h2 { margin-top: 0; }
  .card + .card { margin-top: 1rem; }
  /* Room under the last card for the assistant launcher.
     It is fixed in the bottom-right corner, so whatever the page ends with
     sits under it once you reach the bottom - which on Home is the fortnight
     chart, whose columns you are meant to be able to tap. */
  main { padding-bottom: 6.5rem; }

  /* ===== Global search results header ===== */
  /* The two other things you can do with what you just typed. */
  .search-actions .sa-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
  .search-actions .sa-btn { flex: 1 1 14rem; }

  .search-head {
    font-size: 0.8rem;
    color: var(--fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: 0.5rem 0 0.75rem;
    font-weight: 600;
  }

  /* ===== Assignment edit pencil (rename Blackbaud assignment locally) ===== */
  .item-edit {
    background: none; border: none;
    color: var(--fg-muted);
    cursor: pointer;
    font-size: 0.9rem;
    opacity: 0.5;
    padding: 0 0.3rem;
    margin-left: 0.3rem;
    -webkit-tap-highlight-color: transparent;
  }
  .item-edit:active { opacity: 1; }
  /* #1 — push-to-next-day arrow + carried-over card */
  .item-defer {
    background: none; border: none; color: var(--fg-muted); cursor: pointer;
    font-size: 1rem; font-weight: 700; opacity: 0.55; padding: 0 0.3rem; margin-left: 0.1rem;
    -webkit-tap-highlight-color: transparent;
  }
  .item-defer:hover, .item-defer:active { opacity: 1; color: var(--accent); }
  .carried-card h2::before { content: ''; }
  .carried-card { border-color: color-mix(in srgb, var(--warn) 45%, var(--border)); }
  .edited-mark {
    color: var(--accent);
    font-size: 0.75rem;
    margin-left: 0.25rem;
    opacity: 0.7;
  }

  /* ===== Assignment note (per-item personal note) ===== */
  .item-note {
    font-size: 0.78rem;
    color: var(--fg-muted);
    margin-top: 0.2rem;
    line-height: 1.3;
    display: flex; align-items: flex-start; gap: 0.4rem;
    -webkit-tap-highlight-color: transparent;
  }
  .item-note-text { font-style: italic; cursor: pointer; flex: 1; min-width: 0; }
  .item-note-text:active { opacity: 0.6; }
  .item-note-del {
    flex: 0 0 auto;
    background: none; border: none;
    color: var(--fg-faint);
    font-size: 0.9rem; line-height: 1;
    padding: 0 0.2rem; cursor: pointer;
    font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .item-note-del:hover { color: var(--danger); }
  .item-note-del:active { opacity: 0.6; }
  .item-summary {
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .note-editor {
    display: flex; gap: 0.4rem;
    margin-top: 0.3rem;
  }
  .note-editor input {
    flex: 1;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 6px;
    padding: 0.35rem 0.6rem;
    font-size: 0.85rem;
    font-family: inherit;
  }
  .note-editor button {
    background: var(--accent);
    border: none; color: var(--bg);
    border-radius: 6px;
    padding: 0.35rem 0.7rem;
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
  }

  /* ===== Personal to-do list card ===== */
  .todo-input-row {
    display: flex; gap: 0.4rem;
    margin: 0.5rem 0 0.75rem;
    flex-wrap: wrap;
  }
  .todo-input-row input[type="text"] {
    flex: 1 1 100%;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 8px;
    padding: 0.5rem 0.75rem;
    font-size: 0.9rem;
    font-family: inherit;
  }
  .todo-time-input {
    flex: 1 1 auto;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 8px;
    padding: 0.45rem 0.6rem;
    font-size: 0.85rem;
    font-family: inherit;
    min-width: 0;
  }
  .todo-input-row button {
    flex: 0 0 auto;
    background: var(--accent);
    border: none; color: var(--bg);
    border-radius: 8px;
    padding: 0.5rem 1rem;
    font-weight: 700;
    cursor: pointer;
    font-family: inherit;
  }
  .todo-list { list-style: none; padding: 0; margin: 0; }
  /* A to-do row is two lines, not a queue of flex items.

     As flex, the title, the two grouping chips and the four buttons all
     competed for one line: in the two-column To-dos layout the buttons took
     540 of a 760px column and squeezed the task's own name into a strip
     narrow enough to break "Overdue" one word per line. A grid gives each
     part a place, and the second line only appears when there is something
     to put on it. */
  .todo-list li {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "check body    body"
      ".     tags    tags"
      ".     actions actions";
    align-items: center;
    column-gap: 0.7rem; row-gap: 0.5rem;
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--border-soft);
  }
  .todo-list .check { grid-area: check; }
  .todo-list .todo-body { grid-area: body; }
  .todo-list .todo-assign-row { grid-area: tags; }
  .todo-list .todo-actions { grid-area: actions; }
  .todo-list li:last-child { border-bottom: none; }
  .todo-list li.done .todo-text {
    text-decoration: line-through;
    color: var(--fg-muted);
  }
  .todo-body { min-width: 0; }
  /* Right-aligned on a line of their own. Sharing a line with the chips
     meant one of the two was always being squeezed - at 400px the chips wrap
     to "Group under a / class" and the row grows taller than either layout
     would have been on its own. */
  .todo-actions {
    display: flex; align-items: center; gap: 0.35rem;
    justify-self: end;
  }

  .todo-list .todo-text {
    font-size: 1.05rem;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    line-height: 1.3;
    font-weight: 500;
  }
  .todo-time-label {
    font-size: 0.8rem;
    color: var(--accent);
    margin-top: 0.25rem;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
  }
  .todo-time-label.overdue { color: var(--danger); font-weight: 600; }
  /* How long it should take, after when it is due. Quieter than the date:
     the date is a fact and this is an estimate. */
  .todo-dur-tag {
    margin-left: 0.5rem; color: var(--fg-muted); font-weight: 500;
  }
  .todo-time-label.overdue .todo-dur-tag { color: var(--fg-muted); }
  /* "Overdue · Sun, Aug 30 · All day" is one fact. It may wrap, but not
     between the parts of a date. */
  .todo-time-label { text-wrap: pretty; }
  /* Export carries .todo-edit-btn as well, so its box, size and border come
     from there like Find a time, Edit and Delete. It used to set its own -
     a 1.1rem glyph in an 8px-radius bordered pill next to three 0.9rem
     6px-radius ones - which is why it was the odd one in the row. */
  .todo-cal:active { opacity: 0.6; }
  .todo-list .check {
    width: 26px; height: 26px;
    border-radius: 50%;
    border: 2.5px solid var(--border);
    background: transparent;
    cursor: pointer;
    flex: 0 0 auto;
    -webkit-tap-highlight-color: transparent;
  }
  .todo-list .check.done {
    background: var(--accent);
    border-color: var(--accent);
    position: relative;
  }
  .todo-list .check.done::after {
    content: '✓';
    position: absolute;
    inset: 0;
    color: var(--bg);
    font-size: 0.95rem;
    font-weight: 800;
    display: flex; align-items: center; justify-content: center;
  }
  /* ===== The to-do editor sheet =====

     The editor this replaced was built into the row, in #content, with 0.85rem
     inputs. Two consequences, both of them phone-only and both fatal to it:
     any render replaced #content and threw away whatever had been typed, and
     Safari zooms the page in on an input under 16px and does not zoom back
     out — so tapping the date field magnified the page and pushed the row you
     were editing off the side of the screen.

     Hence: it lives outside #content (see index.html), and NOTHING in here is
     under 1rem. That is the whole fix, and it is the reason for the size rules
     that otherwise look arbitrary. Everything is full-width and stacked, so
     there is one column to work down rather than a grid to aim at. */
  .todo-edit-sheet { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 1.5rem); }
  /* A computer has room for a dialog, and a form that slides up from the
     bottom of a large screen reads as a phone app running in the wrong place. */
  body[data-layout="computer"] .todo-edit-sheet {
    left: 50%; right: auto; bottom: auto; top: 50%;
    transform: translate(-50%, -46%) scale(0.98);
    width: min(30rem, 92vw);
    max-height: 86vh;
    border-radius: 18px;
    border: 1px solid var(--border);
    opacity: 0;
  }
  body[data-layout="computer"] .todo-edit-sheet.open {
    transform: translate(-50%, -50%);
    opacity: 1;
  }
  body[data-layout="computer"] .todo-edit-sheet .sheet-grip { display: none; }

  .ted-label {
    display: block; font-size: 0.75rem; font-weight: 700; color: var(--fg-muted);
    text-transform: uppercase; letter-spacing: 0.05em;
    margin-bottom: 0.4rem;
  }
  .ted-field { margin-top: 1.1rem; }
  .ted-field[hidden] { display: none; }
  /* 1rem is the floor, not a preference: below 16px iOS zooms the page on
     focus and never zooms back. */
  .ted-input {
    width: 100%; box-sizing: border-box;
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    border-radius: 10px; padding: 0.7rem 0.8rem;
    font-family: inherit; font-size: 1rem; line-height: 1.3;
  }
  .ted-input:focus { outline: none; border-color: var(--accent); }
  .ted-input:disabled { opacity: 0.45; }
  .ted-title { font-size: 1.05rem; font-weight: 600; }
  .ted-select { appearance: none; }
  .ted-row { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.5rem; }
  .ted-row-tight { margin-top: 0.4rem; }
  .ted-date { flex: 1 1 60%; }
  .ted-time { flex: 1 1 40%; }
  .ted-dur { flex: 0 0 6.5rem; }
  .ted-suffix { font-size: 0.9rem; color: var(--fg-muted); }
  /* 44px is the smallest thing a finger hits reliably; every chip and button
     in this sheet clears it. */
  .ted-chips { display: flex; flex-wrap: wrap; gap: 0.45rem; }
  .ted-chip {
    min-height: 44px;
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    border-radius: 999px; padding: 0.55rem 0.95rem;
    font-family: inherit; font-size: 0.95rem; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    display: inline-flex; align-items: center; gap: 0.35rem;
  }
  .ted-chip.on { border-color: var(--accent); color: var(--accent); font-weight: 600; }
  .ted-chip-clear { color: var(--fg-muted); }
  .ted-check {
    display: flex; align-items: center; gap: 0.55rem;
    margin-top: 0.6rem; font-size: 0.95rem; color: var(--fg);
    min-height: 44px; cursor: pointer;
  }
  .ted-check input { width: 20px; height: 20px; accent-color: var(--accent); }
  .ted-hint { margin: 0.5rem 0 0; font-size: 0.82rem; color: var(--fg-muted); }
  .ted-ai { border-top: 1px solid var(--border-soft); padding-top: 1rem; }
  .ted-ai-go {
    flex: 0 0 auto; min-height: 44px;
    background: none; border: 1px solid var(--accent); color: var(--accent);
    border-radius: 10px; padding: 0.55rem 0.95rem;
    font-family: inherit; font-size: 0.95rem; font-weight: 700; cursor: pointer;
  }
  .ted-ai-go:disabled { opacity: 0.5; cursor: default; }
  .ted-ai-msg { margin-top: 0.5rem; font-size: 0.88rem; color: var(--fg-muted); }
  .ted-ai-msg.bad { color: var(--warn); }
  .ted-ai-msg[hidden] { display: none; }
  /* "Break it into steps" and what comes back. */
  .ted-plan-go { width: 100%; }
  .ted-plan-out { margin-top: 0.6rem; }
  .plan-list { display: flex; flex-direction: column; gap: 0.4rem; margin: 0 0 0.6rem; }
  .plan-list li {
    display: flex; align-items: center; gap: 0.6rem;
    border-bottom: none; padding: 0.45rem 0.6rem;
    background: var(--bg-input); border-radius: 10px; font-size: 0.95rem;
  }
  .plan-list .plan-text { flex: 1 1 auto; min-width: 0; }
  .plan-list .plan-add { flex: 0 0 auto; padding: 0.3rem 0.75rem; font-size: 0.85rem; }
  .plan-add-all { width: 100%; }

  .ted-actions {
    display: flex; gap: 0.5rem; align-items: center;
    margin-top: 1.4rem;
  }
  .ted-btn {
    min-height: 48px; border-radius: 10px;
    padding: 0.7rem 1.1rem;
    font-family: inherit; font-size: 1rem; cursor: pointer;
  }
  .ted-save {
    flex: 1 1 auto;
    background: var(--accent); border: none; color: #06240f; font-weight: 700;
  }
  .ted-cancel { background: none; border: 1px solid var(--border); color: var(--fg-muted); }
  /* Deleting is permanent and has no undo, so it is deliberately the plainest
     thing in here — off to one side, in words, and reachable only from this
     sheet and the To-dos list. */
  .ted-delete {
    margin-left: auto;
    background: none; border: 1px solid var(--border); color: var(--danger);
  }
  .todo-edit-btn {
    flex: 0 0 auto; background: none; border: none; color: var(--fg-faint);
    font-size: 1.05rem; line-height: 1; cursor: pointer; font-family: inherit;
    padding: 0.3rem 0.35rem; -webkit-tap-highlight-color: transparent;
  }
  .todo-edit-btn:hover { color: var(--accent); }

  /* Tomorrow's work sits directly under today's and should read as its equal,
     not as a footnote — same card, one accent edge so the eye finds the pair. */
  .tomorrow-card { border-left: 3px solid var(--info); }

  /* The assistant's offer to do something. Reads as a draft action, not as a
     thing that has already happened. */
  .ast-do { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0.15rem 0 0.4rem; }
  .ast-do-go {
    background: var(--accent); border: none; color: #06240f;
    border-radius: 999px; padding: 0.35rem 0.75rem;
    font-size: 0.8rem; font-weight: 600; font-family: inherit; cursor: pointer;
  }
  .ast-do-no {
    background: none; border: 1px solid var(--border); color: var(--fg-muted);
    border-radius: 999px; padding: 0.35rem 0.75rem;
    font-size: 0.8rem; font-family: inherit; cursor: pointer;
  }
  .ast-do-done { font-size: 0.8rem; color: var(--fg-muted); }

  /* ===== "You might already have this" =====
     A question, not a warning: nothing has been blocked and nothing is being
     undone without an answer, so it is styled like the rest of the app rather
     than like an error. */
  .dupe-list {
    list-style: none; margin: 0.9rem 0 0; padding: 0;
    display: flex; flex-direction: column; gap: 0.5rem;
  }
  .dupe-item {
    background: var(--bg-input); border: 1px solid var(--border);
    border-left: 3px solid var(--warn);
    border-radius: 10px; padding: 0.6rem 0.75rem;
  }
  .dupe-item-text { display: block; font-size: 0.95rem; font-weight: 600; }
  .dupe-item-meta {
    display: block; margin-top: 0.2rem;
    font-size: 0.78rem; color: var(--fg-muted);
  }
  /* Two answers to one question. Stacked on a phone, where a full-width
     target is the right shape; side by side on a computer, where two
     full-width blocks read as one enormous button and a footnote. The safer
     answer sits first in both, because it is the one somebody who misread
     the dialog should land on. */
  .dupe-actions {
    display: flex; flex-direction: column-reverse; gap: 0.5rem;
    margin-top: 1.1rem;
  }
  .dupe-actions .tut-btn { width: 100%; min-height: 48px; }
  /* row-reverse, so the markup order is the same one the phone reads and the
     visual order is the one a computer expects: the safe answer on the left,
     the green one it is asking about on the right. flex-start is the RIGHT
     edge in a reversed row. */
  body[data-layout="computer"] .dupe-actions {
    flex-direction: row-reverse; justify-content: flex-start;
  }
  body[data-layout="computer"] .dupe-actions .tut-btn { width: auto; min-width: 10rem; }
  .dupe-no { background: none; border: 1px solid var(--border); color: var(--fg-muted); }

  /* ----- Photograph the board -----
     Nothing is added until you tap, so the found list has to look like a
     draft you're checking rather than a list you already have. */
  .todo-board-row {
    display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
    margin-top: 0.6rem;
  }
  .todo-board-btn {
    background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg); border-radius: 8px;
    padding: 0.45rem 0.7rem; font-size: 0.85rem; font-family: inherit;
    cursor: pointer;
  }
  .todo-board-btn:hover { border-color: var(--accent); color: var(--accent); }
  .todo-board-hint { font-size: 0.78rem; color: var(--fg-muted); }
  /* What the photo doesn't say. Takes the whole second line of the row so
     a sentence fits, and stays at 1rem: anything smaller makes Safari zoom
     the page in on focus and never zoom back, which is the bug that made
     the to-do editor unusable on a phone. */
  .todo-board-note {
    flex: 1 1 100%; min-width: 0;
    background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg); border-radius: 8px;
    padding: 0.5rem 0.7rem; font-size: 1rem; font-family: inherit;
  }
  .todo-board-note:focus { outline: none; border-color: var(--accent); }
  .todo-board-note::placeholder { color: var(--fg-faint); }
  .board-reading { font-size: 0.85rem; color: var(--fg-muted); margin-top: 0.6rem; }
  .board-result {
    margin-top: 0.6rem; padding: 0.7rem;
    border: 1px dashed var(--border); border-radius: 10px;
  }
  .board-result-head {
    margin: 0 0 0.5rem; font-size: 0.85rem; color: var(--fg-muted);
  }
  .board-row {
    display: flex; align-items: center; gap: 0.45rem;
    margin-bottom: 0.4rem;
  }
  .board-text {
    flex: 1 1 auto; min-width: 0;
    background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg); border-radius: 6px;
    padding: 0.35rem 0.5rem; font-family: inherit; font-size: 0.9rem;
  }
  .board-when, .board-group {
    flex: 0 0 auto; font-size: 0.75rem; color: var(--fg-muted);
    white-space: nowrap;
  }
  .board-group { color: var(--accent); }
  .board-drop {
    flex: 0 0 auto; background: none; border: none;
    color: var(--fg-faint); cursor: pointer; font-size: 1rem;
    padding: 0.1rem 0.35rem; font-family: inherit;
  }
  .board-drop:hover { color: var(--danger); }
  /* Flagged, never dropped: the model may have read a line that merely looks
     like one they have, and only the student can tell. */
  .board-dup-head {
    margin: 0 0 0.5rem; font-size: 0.8rem; color: var(--warn);
  }
  .board-row-dup { flex-wrap: wrap; }
  .board-row-dup .board-text { border-color: var(--warn); }
  .board-dup-note {
    flex: 1 1 100%; font-size: 0.75rem; color: var(--fg-muted);
    padding-left: 0.1rem;
  }
  .board-actions { display: flex; gap: 0.5rem; margin-top: 0.6rem; }
  .board-add {
    background: var(--accent); border: none; color: #06240f;
    border-radius: 8px; padding: 0.45rem 0.8rem;
    font-size: 0.85rem; font-weight: 600; font-family: inherit; cursor: pointer;
  }
  .board-cancel {
    background: none; border: 1px solid var(--border); color: var(--fg-muted);
    border-radius: 8px; padding: 0.45rem 0.8rem;
    font-size: 0.85rem; font-family: inherit; cursor: pointer;
  }

  /* The day's workload line: how much work it holds against how much day is
     left. Two levels only — busy and tight — because a third would be a colour
     nobody learns the meaning of. */
  .day-load {
    padding: 0.5rem 0.7rem;
    border-radius: 8px;
    font-size: 0.85rem;
    border-left: 3px solid var(--warn);
    background: color-mix(in srgb, var(--warn) 12%, transparent);
  }
  .day-load-over {
    border-left-color: var(--danger);
    background: color-mix(in srgb, var(--danger) 12%, transparent);
  }

  /* The word next to a to-do row's glyph.
     On a phone the glyph is the whole control and there is no room for more.
     On a computer there is, and four unlabelled symbols in a row is a puzzle
     rather than a toolbar — ✎ ✕ are guessable, ↷ and ◷ are not. */
  .btn-word { display: none; }
  body[data-layout="computer"] .btn-word {
    display: inline;
    margin-left: 0.3rem;
    font-size: 0.75rem;
    letter-spacing: 0.01em;
    vertical-align: middle;
  }
  /* With a word in them these stop being icon buttons and should look like
     buttons — otherwise the text floats next to the row with nothing round it. */
  body[data-layout="computer"] .todo-edit-btn,
  body[data-layout="computer"] .todo-list .todo-cal,
  body[data-layout="computer"] .todo-list .todo-delete {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.2rem 0.5rem;
    font-size: 0.9rem;
  }
  body[data-layout="computer"] .todo-edit-btn:hover,
  body[data-layout="computer"] .todo-list .todo-cal:hover,
  body[data-layout="computer"] .todo-list .todo-delete:hover {
    border-color: var(--accent);
    color: var(--accent);
  }
  .todo-list .todo-delete {
    background: none; border: none;
    color: var(--fg-muted);
    font-size: 1.4rem;
    cursor: pointer;
    padding: 0.1rem 0.5rem;
    line-height: 1;
    -webkit-tap-highlight-color: transparent;
  }

  /* Color stripe on todos (set with .todo-color-X class) */
  .todo-list li.has-color {
    padding-left: 0.6rem;
    margin-left: -0.6rem;
    border-left: 4px solid var(--todo-color, var(--accent));
    border-radius: 4px;
  }
  /* Set inline from the class's or the calendar's own colour. */
  .todo-grouped { --todo-color: var(--accent); }
  /* The class or calendar this task belongs to, worn the way it is worn in that
     class's own list. */
  .todo-group-tag {
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em;
    text-transform: uppercase; margin-left: 0.45rem; white-space: nowrap;
    vertical-align: 1px;
  }
  .todo-list li.done .todo-group-tag { opacity: 0.55; }
  .todo-color-1 { --todo-color: #3b82f6; } /* blue */
  .todo-color-2 { --todo-color: #f59e0b; } /* amber */
  .todo-color-3 { --todo-color: #10b981; } /* green */
  .todo-color-4 { --todo-color: #ef4444; } /* red */
  .todo-color-5 { --todo-color: #a855f7; } /* purple */

  /* Color picker dots in the input row */
  .color-row {
    display: flex; gap: 0.5rem;
    align-items: center;
    margin-top: 0.4rem;
  }
  .color-row-label {
    font-size: 0.7rem;
    color: var(--fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-right: 0.3rem;
  }
  .color-dot {
    width: 22px; height: 22px;
    border-radius: 50%;
    border: 2px solid transparent;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.1s ease;
  }
  .color-dot:active { transform: scale(0.9); }
  .color-dot.on { border-color: var(--fg); }
  .color-dot.cd-0 { background: #9ca3af; }
  .color-dot.cd-1 { background: #3b82f6; }
  .color-dot.cd-2 { background: #f59e0b; }
  .color-dot.cd-3 { background: #10b981; }
  .color-dot.cd-4 { background: #ef4444; }
  .color-dot.cd-5 { background: #a855f7; }

  .todo-repeat-select {
    margin-left: auto;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 8px;
    padding: 0.3rem 0.5rem;
    font-size: 0.78rem;
    font-family: inherit;
  }
  .repeat-badge {
    font-size: 0.72rem;
    color: var(--accent);
    font-weight: 600;
    margin-left: 0.35rem;
  }
  .todo-reorder {
    display: flex; flex-direction: column;
    gap: 1px;
    flex: 0 0 auto;
  }
  .todo-move {
    background: none; border: none;
    color: var(--fg-faint);
    font-size: 0.6rem;
    line-height: 1;
    padding: 0.15rem 0.3rem;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .todo-move:disabled { opacity: 0.25; cursor: default; }
  .todo-move:active:not(:disabled) { color: var(--accent); }

  /* Preset chips above the input */
  .preset-chips {    display: flex; gap: 0.4rem;
    flex-wrap: wrap;
    margin-bottom: 0.5rem;
  }
  .preset-chip {
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 999px;
    padding: 0.3rem 0.7rem;
    font-size: 0.78rem;
    cursor: pointer;
    font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .preset-chip:active { opacity: 0.6; }

  /* Bulk action button */
  .bulk-action-btn {
    background: none;
    border: 1px solid var(--border);
    color: var(--fg-muted);
    border-radius: 999px;
    padding: 0.3rem 0.75rem;
    font-size: 0.75rem;
    cursor: pointer;
    font-family: inherit;
    margin-left: 0.5rem;
    -webkit-tap-highlight-color: transparent;
  }
  .bulk-action-btn:active { opacity: 0.6; }
  .todo-header-row {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: 0.3rem;
  }

  /* ===== Dedicated "To-dos" page (larger, grouped layout) ===== */
  .todos-page { padding-bottom: 0.5rem; }
  .todos-head {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 1rem; margin: 0.25rem 0 0.9rem;
  }
  .todos-title { font-size: 1.7rem; font-weight: 800; margin: 0; line-height: 1.1; }
  .todos-sub { font-size: 0.9rem; color: var(--fg-muted); margin-top: 0.2rem; }
  .todos-empty { font-size: 1rem; text-align: center; margin: 2rem 0; }
  .todo-add-panel { margin: 0 0 1.1rem; border-radius: 14px; }
  .todo-add-title {
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.07em;
    text-transform: uppercase; color: var(--fg-muted);
    margin: 0 0 0.6rem;
  }
  .todos-page .preset-chips { margin-bottom: 0.7rem; }
  .todos-page .todo-input-row { margin: 0 0 0.7rem; }
  /* Roomy, readable add-task field — wide enough to see the whole task text. */
  .todos-page .todo-input-row input[type="text"] {
    font-size: 1.1rem; padding: 0.85rem 1rem; border-radius: 11px;
    line-height: 1.35;
  }
  .todos-page .todo-input-row input[type="text"]:focus {
    outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(125, 125, 125, 0.18);
  }
  .todos-page .todo-input-row button {
    padding: 0.85rem 1.6rem; font-size: 1.05rem; border-radius: 11px;
  }
  .todos-page .todo-time-input {
    font-size: 0.92rem; padding: 0.7rem 0.8rem; border-radius: 10px;
  }
  .todo-meta-row {
    display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
    margin-top: 0.6rem; font-size: 0.95rem;
  }
  .todo-allday-label { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; }
  .todo-allday-label input { width: 18px; height: 18px; }
  .todo-nl-hint { font-size: 0.82rem; color: var(--fg-muted); margin: 0.5rem 0 0; line-height: 1.5; }
  .todo-nl-hint b { color: var(--fg); }
  /* The way in to the pickers. Quiet, because typing the task is the main path
     and this is the exception. */
  .todo-manual-toggle {
    margin: 0.7rem 0 0; background: none; border: none; padding: 0;
    color: var(--fg-muted); font-family: inherit; font-size: 0.82rem;
    font-weight: 600; cursor: pointer; text-decoration: underline;
    text-underline-offset: 3px;
  }
  .todo-manual-toggle:hover { color: var(--accent); }
  .todo-manual {
    margin-top: 0.85rem; padding-top: 0.85rem;
    border-top: 1px solid var(--border-soft);
  }
  .todo-manual[hidden] { display: none; }
  .todo-manual-row { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.7rem; }
  .todo-manual-label {
    font-size: 0.72rem; font-weight: 700; color: var(--fg-muted);
    text-transform: uppercase; letter-spacing: 0.05em;
  }
  /* The toast is styled once, under "Small toast for email actions" below.
     There were two full declarations of it — a green one here and a card one
     there — and the second overrode every property of the first, so the green
     one had never been on screen. The only thing worth rescuing was this one's
     safe-area-aware bottom, which is now on the rule that actually applies. */
  /* "All day" hides the length box, and did not: the attribute was set and
     the rule below outranked it, so an all-day task still asked for minutes. */
  .todo-dur-wrap[hidden] { display: none; }
  .todo-dur-wrap { display: flex; align-items: center; gap: 0.4rem; color: var(--fg-muted); }
  .todo-dur-input {
    width: 4.5rem; background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg); border-radius: 8px; padding: 0.4rem 0.5rem;
    font-family: inherit; font-size: 0.9rem;
  }
  .todo-section { margin: 0.75rem 0; }
  .todo-section-head {
    display: flex; align-items: center; gap: 0.5rem; margin: 0 0 0.4rem;
  }
  .todo-section-title {
    font-size: 0.78rem; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--fg-muted);
  }
  .todo-section-count {
    font-size: 0.72rem; font-weight: 700; color: var(--fg-muted);
    background: var(--bg-input); border-radius: 999px;
    padding: 0.05rem 0.5rem; min-width: 1.5em; text-align: center;
  }
  .todo-section-done { opacity: 0.7; }
  .todos-page .todo-list li { padding: 1rem 0; gap: 0.95rem; }
  .todos-page .todo-list .check { width: 30px; height: 30px; border-width: 3px; }
  .todos-page .todo-list .check.done::after { font-size: 1.1rem; }
  .todos-page .todo-list .todo-text { font-size: 1.15rem; line-height: 1.35; }
  .todos-page .todo-time-label { font-size: 0.9rem; margin-top: 0.3rem; }
  .todos-page .todo-list .todo-delete { font-size: 1.5rem; }

  /* ===== Pull-to-refresh indicator ===== */
  .ptr {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 0.5rem);
    left: 50%;
    transform: translate(-50%, -200%);
    background: var(--bg-card);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 999px;
    padding: 0.4rem 1rem;
    font-size: 0.8rem;
    z-index: 50;
    transition: transform 0.25s ease, opacity 0.2s ease;
    opacity: 0;
    pointer-events: none;
    box-shadow: 0 4px 12px rgba(0,0,0,0.2);
  }
  .ptr.show { transform: translate(-50%, 0); opacity: 1; }
  .ptr.spinning .ptr-icon { animation: spin 1s linear infinite; display: inline-block; }
  @keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
  }

  /* ===== Info chips row on Today ===== */
  .info-chips {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0.25rem 0 0.85rem;
  }
  .info-chip {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.4rem 0.85rem;
    font-size: 0.8rem;
    color: var(--fg);
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
  }
  .info-chip strong { font-weight: 700; }
  /* Self-contained on purpose: it sits in the phone's .info-chips row and in
     the desktop header's .dh-meta line, and must not inherit either one's
     sizing or colour, or it stops looking like the same thing. */
  .streak-chip {
    display: inline-flex; align-items: center; gap: 0.35rem;
    background: linear-gradient(135deg, #f59e0b, #ef4444);
    border: 1px solid transparent;
    border-radius: 999px;
    padding: 0.3rem 0.8rem;
    font-size: 0.8rem; font-weight: 600; line-height: 1.25;
    color: #fff; font-family: inherit; white-space: nowrap;
    vertical-align: middle;
  }
  .streak-chip b { font-weight: 800; }
  .streak-chip .ico { width: 0.95em; height: 0.95em; }
  .weekend-chip { border-color: var(--accent); }

  /* A title the assistant named, made openable. Reads as text with a hint of
     underline, not as a button: it is part of a sentence. */
  .ast-cite {
    font: inherit; color: inherit; cursor: pointer;
    background: none; border: none; padding: 0;
    text-decoration: underline dotted;
    text-underline-offset: 2px;
  }
  .ast-cite:hover { color: var(--accent); text-decoration-style: solid; }

  /* ===== The next day you have school ===== */
  .next-school-day .day-h { margin-top: 0.9rem; }
  /* A focus ring you can actually see. The browser default disappears against
     a dark card, and several controls here had it removed outright. */
  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
  }
  /* Someone who has asked their system for less movement means it. Every
     transition and animation here is decoration; none of them carry meaning. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }

  /* ===== Empty-state CTA card (#6) ===== */
  .cta-card {
    background: var(--bg-card); border: 1px dashed var(--accent);
    border-radius: 16px; padding: 1.25rem 1.3rem; text-align: center; margin: 0 0 1rem;
  }
  .cta-card .cta-emoji { font-size: 1.8rem; }
  /* The getting-started list. Left-aligned, because it is a list of things to
     do rather than a banner. */
  .gs-card { text-align: left; }
  .gs-count { font-size: 0.8rem; color: var(--fg-muted); margin: 0.1rem 0 0.6rem; }
  .gs-list { display: flex; flex-direction: column; gap: 0.7rem; margin: 0; }
  .gs-step {
    display: flex; align-items: flex-start; gap: 0.65rem;
    border-bottom: none; padding: 0; font-size: 0.95rem;
  }
  .gs-tick {
    flex: 0 0 auto; width: 20px; height: 20px; margin-top: 0.1rem;
    border-radius: 50%; border: 2px solid var(--border);
    display: flex; align-items: center; justify-content: center;
    font-size: 0.75rem; font-weight: 800; color: var(--bg);
  }
  .gs-tick.done { background: var(--accent); border-color: var(--accent); }
  .gs-body { flex: 1 1 auto; min-width: 0; }
  .gs-title { font-weight: 600; color: var(--fg); }
  .gs-step.is-done .gs-title { color: var(--fg-muted); text-decoration: line-through; }
  .gs-why { font-size: 0.86rem; color: var(--fg-muted); line-height: 1.4; margin-top: 0.15rem; }
  .gs-act { margin-top: 0.5rem; display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
  .gs-help { font-size: 0.82rem; color: var(--accent); }
  .cta-card h3 { margin: 0.35rem 0 0.3rem; font-size: 1.05rem; }
  .cta-card p { margin: 0 0 0.9rem; color: var(--fg-muted); font-size: 0.9rem; }
  .cta-card .cta-btn {
    display: inline-block; background: var(--accent); color: #fff; border: none;
    border-radius: 10px; padding: 0.6rem 1.1rem; font-weight: 700; font-size: 0.92rem;
    cursor: pointer; font-family: inherit;
  }

  /* ===== Install (Add to Home Screen) nudge (#2) ===== */
  .install-nudge {
    display: none; align-items: center; gap: 0.7rem;
    background: var(--bg-card); border: 1px solid var(--border); border-left: 3px solid var(--accent);
    border-radius: 12px; padding: 0.7rem 0.85rem;
    /* Body already supplies 1.25rem side gutters, so only add vertical breathing
       room here — this keeps the bar aligned with the cards below it. */
    margin: 0.75rem 0 1rem;
    box-sizing: border-box;
  }
  body[data-screen="main"] .install-nudge.show { display: flex; }
  /* On a desktop it used to be a 640px block pinned to the left edge under a
     full-width app bar, which read as a stray fragment rather than a banner.
     Centre it, give it its own presence, and let the text breathe. */
  body[data-layout="computer"] .install-nudge {
    max-width: 720px; margin: 1.1rem auto 1.4rem;
    justify-content: center; text-align: left;
    padding: 0.85rem 1.1rem; gap: 1rem;
    border-left-width: 1px; border-radius: 14px;
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 9%, var(--bg-card)), var(--bg-card));
    box-shadow: 0 6px 20px rgba(0,0,0,0.28);
  }
  body[data-layout="computer"] .install-nudge .in-text { flex: 0 1 auto; font-size: 0.9rem; }
  body[data-layout="computer"] .install-nudge .in-btn { padding: 0.5rem 1.1rem; font-size: 0.86rem; }
  .install-nudge .in-emoji { font-size: 1.3rem; flex: 0 0 auto; }
  .install-nudge .in-text { flex: 1 1 auto; font-size: 0.85rem; line-height: 1.35; }
  .install-nudge .in-text b { font-weight: 700; }
  .install-nudge .in-actions { display: flex; gap: 0.4rem; flex: 0 0 auto; }
  .install-nudge .in-btn {
    background: var(--accent); color: #fff; border: none; border-radius: 8px;
    padding: 0.45rem 0.8rem; font-weight: 700; font-size: 0.82rem; cursor: pointer; font-family: inherit; white-space: nowrap;
  }
  .install-nudge .in-dismiss {
    background: transparent; border: none; color: var(--fg-muted); font-size: 1.1rem;
    cursor: pointer; line-height: 1; padding: 0.2rem 0.35rem;
  }

  /* ===== Ghost link button (used in Tomorrow card) ===== */
  .ghost-link {
    background: transparent;
    border: none;
    color: var(--accent);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    padding: 0.4rem 0;
    font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .ghost-link:active { opacity: 0.6; }

  .hourly {
    display: flex; gap: 0.5rem;
    overflow-x: auto;
    margin: 1rem -1.25rem 0;
    padding: 0.25rem 1.25rem 0.75rem;
    scrollbar-width: none;
  }
  .hourly::-webkit-scrollbar { display: none; }
  .hour {
    flex: 0 0 auto;
    min-width: 56px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 0.5rem 0.4rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
  }
  .hour-label { font-size: 0.65rem; color: var(--fg-muted); }
  .hour-emoji { font-size: 1.25rem; line-height: 1.2; margin: 0.2rem 0; }
  .hour-temp { font-size: 0.875rem; font-weight: 600; }
  .hour-rain { font-size: 0.65rem; color: var(--info); margin-top: 0.1rem; min-height: 0.85rem; }

  /* ===== Filter chips ===== */
  .filters {
    display: flex; gap: 0.4rem;
    overflow-x: auto;
    margin: 1rem -1.25rem 0;
    padding: 0.25rem 1.25rem 0.75rem;
    scrollbar-width: none;
  }
  .filters::-webkit-scrollbar { display: none; }
  .chip {
    flex: 0 0 auto;
    background: var(--bg-card);
    color: var(--fg-muted);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.4rem 0.85rem;
    font-size: 0.8rem; font-weight: 500;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: all 0.15s;
    white-space: nowrap;
  }
  .chip.on {
    background: var(--accent);
    color: var(--bg);
    border-color: var(--accent);
    font-weight: 600;
  }
  .chip:active { transform: scale(0.96); }

  /* ===== Sections ===== */
  h2 {
    font-size: 0.7rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.12em;
    color: var(--fg-muted); margin: 1.75rem 0 0.5rem;
  }
  h3.day-h {
    font-size: 0.72rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--accent); margin: 1rem 0 0.45rem;
  }
  .hdr-warn { color: var(--warn); font-weight: 500; }
  ul { list-style: none; padding: 0; margin: 0; }
  li {
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--border-soft);
    font-size: 1rem; line-height: 1.4;
    display: flex; align-items: center; gap: 0.6rem;
  }
  li:last-child { border-bottom: none; }
  .t {
    color: var(--accent); font-variant-numeric: tabular-nums;
    font-weight: 500;
  }
  .loc { color: var(--fg-faint); font-size: 0.875rem; }
  .empty { color: var(--fg-faint); margin: 0.5rem 0 0; }
  /* Said quietly and only where an answer already is — it explains a worse
     answer, it is not a problem the reader has to do anything about. */
  .ai-rules-note { font-size: 0.8rem; opacity: 0.75; }
  .error { color: var(--danger); }

  /* ===== The next two weeks =====
     A column per day: the height is how much is due, and the red cap on top is
     the part of it that is a test. Gaps come from the flex row rather than
     margins on the columns, so nothing here can drift out of rhythm.
     Every column is a real <button>, so it is tabbable and Enter opens the day
     exactly as a tap does. */
  .wl-lead {
    font-size: 0.78rem; line-height: 1.45; color: var(--fg-muted);
    margin-top: 0.4rem; margin-bottom: 0.7rem;
  }
  /* No gap between the columns, and the space that used to be the gap is
     padding INSIDE each button instead. Fourteen days across a phone is about
     22px a column, and a 3px gap either side of that is a dead strip down every
     boundary — a tap landing in one does nothing at all. It showed up worst on a
     day with nothing due, because a bar is what you aim at and an empty day
     hasn't got one, so the aim drifts to the middle of the gap. Every pixel of
     the row now belongs to a day. The bars stay visually apart because the bar
     itself is 0.72rem in a column twice that wide. */
  .wl-chart { display: flex; align-items: flex-end; gap: 0; flex-wrap: wrap; }
  /* Two rows of seven on a phone.

     Fourteen columns across a phone is 22px each, measured - half a thumb, and
     well under the 44px a touch target needs. So some days took the tap, some
     took the neighbour's, and the ones under the assistant launcher in the
     bottom-right corner took nothing at all. That is the "I can only click
     some of them, and only at the bottom" report.

     Seven across is 52px, which is a real target, and the fortnight reads as
     this week and next week - which is how anyone looking at it is thinking
     about it anyway. Nothing is hidden and nothing scrolls. */
  body[data-layout="phone"] .wl-col,
  body:not([data-layout="computer"]) .wl-col { flex: 0 0 14.2857%; }
  /* The break between the weeks is a row of its own now, not a border. */
  body[data-layout="phone"] .wl-col.is-weekbreak,
  body:not([data-layout="computer"]) .wl-col.is-weekbreak {
    border-left: 0; padding-left: 0.1rem;
  }
  @media (min-width: 760px) {
    body[data-layout="computer"] .wl-col { flex: 1 1 0; }
    body[data-layout="computer"] .wl-col.is-weekbreak {
      border-left: 1px solid var(--border); padding-left: 0.5rem;
    }
  }
  .wl-col {
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; gap: 0.15rem;
    background: none; border: 0; font: inherit; color: inherit;
    padding: 0.35rem 0.1rem; border-radius: 7px;
    cursor: pointer; -webkit-tap-highlight-color: transparent;
    /* touch-action: the chart sits inside a card the pull-to-refresh gesture
       watches, and a tap with a pixel of drift was being read as the start of a
       pull rather than as a tap. */
    touch-action: manipulation;
  }
  .wl-col:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
  /* A clear break between this week and next, so the fortnight reads as two.
     A left margin here was another dead strip, and a wider one; it's a border
     and padding on the button now, so the break is inside next Monday's tap
     target rather than between it and Sunday. */
  .wl-col.is-weekbreak { border-left: 1px solid var(--border); padding-left: 0.5rem; }
  .wl-n {
    font-size: 0.62rem; line-height: 1; height: 0.72rem;
    color: var(--fg-faint); font-variant-numeric: tabular-nums;
  }
  .wl-col.is-peak .wl-n { color: var(--warn); font-weight: 700; }
  .wl-track { position: relative; width: 100%; height: 3.5rem; }
  .wl-stack {
    position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
    width: 0.72rem; display: flex; flex-direction: column;
    border-radius: 3px; overflow: hidden;
  }
  .wl-seg { display: block; width: 100%; }
  .wl-seg-work { background: var(--accent); }
  .wl-seg-test { background: var(--danger); }
  /* A day with nothing on it still gets a baseline, so the row reads as a
     chart with a gap rather than as a chart that failed to draw. */
  .wl-none {
    position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
    width: 0.72rem; height: 2px; border-radius: 2px; background: var(--fg-very-faint);
  }
  .wl-dow { font-size: 0.62rem; line-height: 1.1; color: var(--fg-muted); }
  .wl-dom {
    font-size: 0.68rem; line-height: 1.1; color: var(--fg-faint);
    font-variant-numeric: tabular-nums;
  }
  .wl-col.is-off .wl-dow, .wl-col.is-off .wl-dom { color: var(--fg-very-faint); }
  .wl-col.is-today { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .wl-col.is-today .wl-dow,
  .wl-col.is-today .wl-dom { color: var(--accent); font-weight: 700; }
  /* The open day. It sits inside the same card as the chart, directly under
     the column that was tapped, and borrows .day-detail from the Week view so
     the two surfaces are the same thing rather than two versions of it. */
  .wl-col.is-open { background: color-mix(in srgb, var(--accent) 22%, transparent); }
  .wl-col.is-open .wl-dow,
  .wl-col.is-open .wl-dom { color: var(--fg); font-weight: 700; }

  /* Hover, as a layer over the column rather than as its background.

     It was `background: var(--bg-input)` declared BEFORE `.is-today` and
     `.is-open`, which set a background of their own at the same specificity -
     so the two columns you are most likely to point at were the two that
     never reacted. Every state here sets a background, so hover cannot be one
     and win; it is a tint on top instead, and it composes with whatever the
     column already is.

     Darker rather than lighter, which is what a pressable thing does on a
     dark page, and darker again while the mouse is down. */
  .wl-col { position: relative; overflow: hidden; }
  .wl-col::before {
    content: ''; position: absolute; inset: 0;
    border-radius: inherit; background: #000;
    opacity: 0; transition: opacity .12s ease;
    pointer-events: none;                 /* never eats the click */
  }
  .wl-col:hover::before { opacity: 0.28; }
  .wl-col:active::before { opacity: 0.4; }
  /* The bar and the labels sit above the tint. */
  .wl-col > * { position: relative; z-index: 1; }
  @media (hover: none) {
    /* A finger has no hover, and a sticky one after a tap reads as stuck. */
    .wl-col:hover::before { opacity: 0; }
  }
  .wl-open {
    margin-top: 0.8rem; padding-top: 0.8rem;
    border-top: 1px solid var(--border-soft);
  }
  .wl-open-head {
    display: flex; align-items: baseline; gap: 0.5rem;
    margin-bottom: 0.5rem;
  }
  .wl-open-head h3 { margin: 0; font-size: 1rem; }
  .wl-open-sub { font-size: 0.8rem; font-weight: 400; color: var(--fg-muted); }
  .wl-open-close {
    margin-left: auto; background: none; border: 0; cursor: pointer;
    color: var(--fg-faint); font-size: 1rem; line-height: 1;
    padding: 0.2rem 0.35rem; border-radius: 6px; font-family: inherit;
  }
  .wl-open-close:hover { color: var(--fg); background: var(--bg-input); }
  .wl-empty { font-size: 0.85rem; color: var(--fg-muted); }
  /* One line, only when a heavy day is close enough to do something about. */
  .heads-up {
    background: color-mix(in srgb, var(--warn) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
    border-radius: 12px; padding: 0.6rem 0.8rem;
    font-size: 0.92rem; line-height: 1.4; color: var(--fg);
  }
  .heads-up .ghost-link { font-weight: 600; }
  /* The sentence is the button. It names a day, so pressing it goes there. */
  .heads-up { position: relative; padding-right: 2rem; }
  /* "Show me" — the thing the sentence already did, said out loud. */
  .heads-up-go { color: var(--accent); font-weight: 700; white-space: nowrap; }

  .heads-up-main {
    display: block; width: 100%; text-align: left;
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; color: inherit; cursor: pointer;
  }
  .heads-up-main:hover { color: var(--warn); }
  .heads-up .hu-never { color: var(--fg-muted); font-weight: 500; }
  .hu-x {
    position: absolute; top: 0.35rem; right: 0.4rem;
    background: none; border: 0; cursor: pointer; font: inherit;
    color: var(--fg-faint); line-height: 1;
    /* Small glyph, thumb-sized target. */
    padding: 0.45rem 0.5rem; margin: -0.2rem;
  }
  .hu-x:hover { color: var(--fg); }

  .wl-note { font-size: 0.82rem; line-height: 1.45; color: var(--fg); margin-top: 0.8rem; }
  .wl-note b { color: var(--fg); }
  .wl-key {
    display: flex; flex-wrap: wrap; gap: 0.4rem 0.9rem;
    margin-top: 0.5rem; font-size: 0.7rem; color: var(--fg-faint);
  }
  .wl-key-i { display: inline-flex; align-items: center; gap: 0.3rem; }
  .wl-sw { width: 0.5rem; height: 0.5rem; border-radius: 2px; display: inline-block; }
  .wl-sw-work { background: var(--accent); }
  .wl-sw-test { background: var(--danger); }
  .wl-sw-free { background: var(--fg-very-faint); }

  .badge-test {
    display: inline-block;
    background: var(--test-bg); color: var(--test-fg);
    font-size: 0.65rem; font-weight: 700;
    letter-spacing: 0.08em;
    padding: 0.125rem 0.4rem;
    border-radius: 4px;
    vertical-align: 1px;
  }

  .sched-note {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-left: 3px solid var(--warn);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    font-size: 0.85rem;
    margin: 0.5rem 0;
    color: var(--fg);
  }

  /* ===== Week mini-calendar ===== */
  /* ===== Tab bar (Today / Week / Days off / Tests) ===== */
  .tab-bar {
    display: flex;
    gap: 0.4rem;
    margin: 0.85rem -1.25rem 0;
    padding: 0.35rem 1.25rem 0.85rem;
    overflow-x: auto;
    scrollbar-width: none;
    border-bottom: 1px solid var(--border-soft);
    background: var(--bg);
    position: sticky;
    top: 0;
    z-index: 25;
  }
  .tab-bar::-webkit-scrollbar { display: none; }
  .tab {
    flex: 0 0 auto;
    padding: 0.5rem 1rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 999px;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    white-space: nowrap;
    font-family: inherit;
    transition: transform 0.1s ease, background 0.15s ease, color 0.15s ease;
  }
  .tab:active { transform: scale(0.95); }
  .tab.active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--bg);
  }
  .tab .tab-count {
    display: inline-block;
    background: rgba(0,0,0,0.15);
    color: inherit;
    font-size: 0.7rem;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    margin-left: 0.3rem;
    font-weight: 700;
  }
  .tab.active .tab-count { background: rgba(0,0,0,0.25); }

  /* ===== Visible filter bar (one big filter, not in 3-dot menu) ===== */
  /* It starts hidden and is shown when there is something to filter. */
  .filter-bar[hidden] { display: none; }
  .filter-bar {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin: 0 -1.25rem 0.5rem;
    padding: 0.65rem 1.25rem;
    border-bottom: 1px solid var(--border-soft);
    background: var(--bg);
    overflow: visible;
    position: sticky;
    top: 0;
    z-index: 24;
  }
  .filter-search {
    flex: 0 1 38%;
    min-width: 0;
    background: var(--bg-card);
    border: 1px solid var(--border);
    color: var(--fg);
    padding: 0.45rem 0.75rem;
    border-radius: 999px;
    font-size: 0.8rem;
    font-family: inherit;
    outline: none;
    transition: border-color 0.15s ease;
  }
  .filter-search:focus { border-color: var(--accent); }
  .filter-select {
    flex: 1 1 auto;
    background: var(--accent);
    border: 1px solid var(--accent);
    color: var(--bg);
    border-radius: 999px;
    padding: 0.6rem 2.2rem 0.6rem 1.1rem;
    font-size: 1rem;
    font-family: inherit;
    font-weight: 700;
    cursor: pointer;
    outline: none;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path fill='black' d='M0 0l6 8 6-8z'/></svg>");
    background-repeat: no-repeat;
    background-position: right 0.9rem center;
    transition: opacity 0.15s ease;
  }
  .filter-select:active { opacity: 0.85; }

  .week-cal {
    display: flex; gap: 0.5rem;
    margin: 1.25rem 0 0.5rem;
  }
  .week-day {
    flex: 1; min-width: 0;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 0.75rem 0.3rem 0.65rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: transform 0.1s ease, background 0.15s ease;
    -webkit-tap-highlight-color: transparent;
  }
  .week-day:active { transform: scale(0.94); }
  .week-day.today {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--bg);
  }
  /* Only dim PAST days - weekends and days-off stay full opacity */
  .week-day.past { opacity: 0.4; }
  .week-day .wd-label {
    font-size: 0.7rem; color: var(--fg-muted);
    text-transform: uppercase; letter-spacing: 0.06em;
    font-weight: 600;
  }
  .week-day.today .wd-label { color: var(--bg); opacity: 0.85; }
  .week-day .wd-num { font-size: 1.35rem; font-weight: 700; margin-top: 0.2rem; line-height: 1.1; }
  .week-day .wd-dots {
    display: flex; gap: 3px; justify-content: center;
    margin-top: 0.45rem; height: 6px;
  }
  .wd-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--fg-muted);
  }
  .wd-dot.test { background: var(--danger); }
  .wd-dot.assign { background: var(--warn); }
  /* Calendar (event) dots: square so they read distinctly from round assignment dots. */
  .wd-dot.cal-dot, .mdot.cal-dot { border-radius: 1.5px; }
  .week-day.today .wd-dot.cal-dot { box-shadow: 0 0 0 1px rgba(255,255,255,0.6); }
  .week-day.today .wd-dot { background: var(--bg); }
  .week-day.today .wd-dot.test { background: white; }

  /* ===== #3 — Weather 5-day forecast strip (reuses .week-cal/.week-day) ===== */
  /* Force exactly 5 equal columns regardless of page flow — flex:1 on .week-day
     can drift when content widths differ, leaving Today visually outsized. */
  .week-cal.forecast-strip {
    display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.5rem;
  }
  .forecast-day { cursor: pointer; padding: 0.7rem 0.3rem 0.65rem; min-width: 0; }
  .forecast-day:active { transform: none; }
  /* Selected (the day the forecast below is showing). A non-today selection gets an
     accent ring; the today cell already reads as selected via its solid accent fill. */
  .forecast-day.selected { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent) inset; }
  .forecast-day:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .forecast-day .fc-emoji { font-size: 1.5rem; line-height: 1; margin: 0.35rem 0 0.3rem; }
  .forecast-day .fc-temps { font-size: 0.95rem; font-variant-numeric: tabular-nums; }
  .forecast-day .fc-hi { font-weight: 700; }
  .forecast-day .fc-lo { color: var(--fg-muted); font-weight: 600; }
  .forecast-day.today .fc-lo { color: var(--bg); opacity: 0.8; }
  .forecast-day .fc-rain {
    font-size: 0.65rem; color: var(--accent);
    margin-top: 0.25rem; min-height: 0.85rem;
  }
  .forecast-day.today .fc-rain { color: var(--bg); opacity: 0.9; }

  /* ===== Day-detail bottom sheet ===== */
  .sheet-overlay {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.5);
    z-index: 200;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  .sheet-overlay.open { opacity: 1; pointer-events: auto; }
  .sheet {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    background: var(--bg);
    border-radius: 18px 18px 0 0;
    padding:
      1.25rem 1.5rem
      calc(env(safe-area-inset-bottom, 0px) + 1.75rem);
    z-index: 201;
    max-height: 85vh;
    overflow-y: auto;
    transform: translateY(100%);
    transition: transform 0.28s cubic-bezier(0.32, 0.72, 0.32, 1);
    box-shadow: 0 -8px 24px rgba(0,0,0,0.25);
    border-top: 1px solid var(--border);
  }
  .sheet.open { transform: translateY(0); }
  /* #5 — the bottom sheet is the phone-mode surface only. Hard-hide on computer
     so a regression can't ever show the popup AND the sheet at the same time. */
  .sheet-grip {
    width: 38px; height: 4px;
    background: var(--border);
    border-radius: 2px;
    margin: 0 auto 0.85rem;
  }
  .sheet-header {
    display: flex; align-items: baseline;
    justify-content: space-between;
    margin-bottom: 1rem;
  }
  .sheet-day-label {
    font-size: 1.35rem; font-weight: 700;
    line-height: 1.1;
  }
  .sheet-day-sub {
    font-size: 0.75rem; color: var(--fg-muted);
    text-transform: uppercase; letter-spacing: 0.08em;
    margin-top: 0.15rem;
  }
  .sheet-close {
    background: var(--bg-card);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 50%;
    width: 32px; height: 32px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
  }
  .sheet ul { margin: 0.25rem 0; padding-left: 1.1rem; }
  .sheet li { padding: 0.2rem 0; }
  .sheet .empty-day {
    text-align: center;
    color: var(--fg-muted);
    font-size: 0.9rem;
    padding: 1rem 0;
  }

  /* Clickable summary cards (Tests / Days off open a details popup) */
  .stat-tap { cursor: pointer; -webkit-tap-highlight-color: transparent; position: relative; }
  .stat-tap::after {
    content: '›';
    position: absolute; top: 0.3rem; right: 0.45rem;
    font-size: 0.85rem; color: var(--fg-faint); line-height: 1;
  }
  .stat-tap:hover { border-color: var(--accent); }
  .stat-tap:active { opacity: 0.6; }

  /* ===== Summary details popup (Tests / Days off) ===== */
  /* ===== Tutorial ===== */
  .tut-overlay {
    position: fixed; inset: 0; z-index: 300;
    background: rgba(0,0,0,0.6); backdrop-filter: blur(2px);
    display: flex; align-items: center; justify-content: center; padding: 1rem;
  }
  .tut-overlay[hidden] { display: none; }
  .tut-card {
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 18px; width: 100%; max-width: 460px;
    max-height: 88vh; overflow-y: auto;
    padding: 1.5rem 1.5rem 1.25rem;
    box-shadow: 0 16px 48px rgba(0,0,0,0.45);
    display: flex; flex-direction: column;
  }
  .tut-foot { flex: 0 0 auto; position: relative; }
  .tut-foot::before {
    content: ''; position: absolute; left: 0; right: 0; top: -1.4rem; height: 1.4rem;
    background: linear-gradient(to bottom, transparent, var(--bg-card));
    pointer-events: none;
  }
  body[data-layout="computer"] .tut-card { max-width: 560px; padding: 1.9rem 2rem 1.5rem; }
  .tut-emoji { font-size: 2.2rem; line-height: 1; }
  .tut-title { font-size: 1.4rem; font-weight: 800; margin: 0.5rem 0 0.6rem; line-height: 1.15; }
  .tut-text { font-size: 0.98rem; color: var(--fg); line-height: 1.55; margin: 0; }
  .tut-text ul { list-style: none; padding: 0; margin: 0.6rem 0 0; }
  .tut-text li {
    display: block; padding: 0.4rem 0; border: none;
    font-size: 0.95rem; line-height: 1.45;
  }
  .tut-text li b { color: var(--accent); }
  .tut-modes { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.8rem; }
  .tut-mode-btn {
    display: flex; align-items: center; gap: 0.75rem; text-align: left;
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    border-radius: 12px; padding: 0.85rem 1rem; cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .tut-mode-btn.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
  .tut-mode-btn .tm-emoji { font-size: 1.6rem; flex: 0 0 auto; }
  .tut-mode-btn .tm-title { font-weight: 700; }
  .tut-mode-btn .tm-desc { font-size: 0.8rem; color: var(--fg-muted); }
  /* A row of 17 dots is a blob you can't count, so the number carries the
     progress and the dots just show the shape of it. */
  .tut-progress {
    display: flex; align-items: center; gap: 0.6rem;
    margin: 1rem 0 0.85rem;
  }
  .tut-count {
    flex: 0 0 auto; font-size: 0.72rem; font-weight: 700; color: var(--fg-muted);
    letter-spacing: 0.05em; font-variant-numeric: tabular-nums;
  }
  .tut-dots { display: flex; gap: 0.3rem; flex: 1 1 auto; flex-wrap: nowrap; overflow: hidden; }
  .tut-dot { flex: 1 1 0; height: 3px; border-radius: 2px; background: var(--border); min-width: 4px; }
  .tut-dot.on { background: var(--accent); }
  .tut-dot.seen { background: color-mix(in srgb, var(--accent) 45%, var(--border)); }
  .tut-nav { display: flex; gap: 0.6rem; }
  .tut-btn {
    flex: 1; border-radius: 10px; padding: 0.75rem 1rem; font-size: 0.95rem;
    font-weight: 700; cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .tut-back { background: var(--bg-input); border: 1px solid var(--border); color: var(--fg); }
  .tut-back:disabled { opacity: 0.4; cursor: default; }
  .tut-next { background: var(--accent); border: 1px solid var(--accent); color: var(--bg); }
  .tut-btn:active { opacity: 0.85; }
  .tut-skip {
    display: block; background: none; border: none; color: var(--fg-muted);
    font-size: 0.82rem; cursor: pointer; font-family: inherit;
    margin: 0.85rem auto 0; padding: 0.35rem 0.5rem; text-decoration: underline;
    text-underline-offset: 3px;
    -webkit-tap-highlight-color: transparent;
  }
  .tut-skip:hover { color: var(--fg); }
  .tut-skip:active { opacity: 0.6; }

  /* ===== Game-style guided tour (spotlight coachmarks) ===== */
  .coach-overlay { position: fixed; inset: 0; z-index: 1200; }
  .coach-overlay[hidden] { display: none; }
  .coach-dim { position: absolute; inset: 0; background: rgba(8,10,14,.45); }
  .coach-spot {
    position: absolute; border-radius: 12px; pointer-events: none;
    box-shadow: 0 0 0 9999px rgba(8,10,14,.66), 0 0 0 3px var(--accent),
                0 0 26px 5px color-mix(in srgb, var(--accent) 55%, transparent);
    transition: left .12s ease, top .12s ease, width .12s ease, height .12s ease;
  }
  .coach-spot[hidden] { display: none; }
  .coach-bubble {
    position: absolute; width: 320px; max-width: calc(100vw - 28px);
    /* A step that will not fit scrolls inside its own card rather than running
       off the bottom of the screen and taking the Next button with it. */
    max-height: calc(100vh - 20px - var(--safe-top) - var(--safe-bottom));
    overflow-y: auto;
    background: var(--bg-card); border: 1px solid var(--border); border-radius: 14px;
    padding: 15px 16px 13px; box-shadow: 0 16px 44px rgba(0,0,0,.5);
    transition: left .12s ease, top .12s ease;
  }
  .coach-bubble.center { left: 50% !important; top: 50% !important; transform: translate(-50%,-50%); width: 330px; text-align: center; }
  /* Where a step goes when the assistant is open over the tour: top of the
     screen, out of the panel's corner, so you can read the step and type at the
     same time. */
  .coach-bubble.center-top {
    left: 50% !important; top: 16px !important;
    transform: translateX(-50%); width: min(360px, calc(100vw - 24px));
    text-align: center;
  }
  .coach-emoji { font-size: 22px; line-height: 1; }
  .coach-title { font-weight: 700; font-size: 17px; margin: 6px 0 6px; color: var(--fg); }
  /* 13.5px, for a caption you are meant to read in a second, while looking at
     the thing it is pointing at. The copy got shorter; this got bigger. */
  .coach-text { font-size: 15.5px; color: var(--fg); line-height: 1.45; }
  .coach-text b { color: var(--fg); font-weight: 600; }
  .coach-nav { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 13px; }
  .coach-dots { display: flex; gap: 5px; flex-wrap: wrap; }
  .coach-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--border); }
  .coach-dot.on { background: var(--accent); }
  .coach-btns { display: flex; gap: 6px; flex: 0 0 auto; }
  .coach-btn { font: inherit; font-size: 13px; font-weight: 600; border-radius: 8px; padding: 7px 13px; cursor: pointer; border: 1px solid var(--border); background: var(--bg-input); color: var(--fg); }
  .coach-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
  .coach-btn:disabled { opacity: .4; cursor: default; }
  .coach-skip { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,.62); color: #fff; border: 1px solid rgba(255,255,255,.18); border-radius: 999px; padding: 10px 22px; font-size: 13px; font-weight: 600; cursor: pointer; z-index: 1203; box-shadow: 0 6px 20px rgba(0,0,0,.4); }
  .coach-skip:hover { background: rgba(0,0,0,.8); }
  .coach-modes { display: flex; flex-direction: column; gap: .55rem; margin: .7rem 0 0; text-align: left; }

  /* ===== Lists that are prose, not rows =====
     `li` near the top of this file is a flex row, because nearly every list in
     the app is one: a time, a title, a button. A list that is a SENTENCE is the
     exception and was never opted out of it — so each <b> or <em> inside one
     became a flex item of its own and the sentence came apart into columns,
     reading as a table with the bold words in a narrow column beside the text.
     It hit three places at once: What's new, the tutorial's bulleted steps, and
     the landing page's "students <em>and</em> parents". Flex also suppresses the
     marker, which is why none of those lists had bullets either.
     This puts normal list rendering back; anything genuinely row-shaped keeps
     the flex rule by not being named here. */
  .tut-card ul, .tut-card ol { list-style: disc; padding-left: 1.2rem; margin: 0.5rem 0; }
  .tut-card li, .lp-ul li {
    display: list-item; align-items: initial; gap: 0;
    border-bottom: none; line-height: 1.55;
  }
  .tut-card li { padding: 0.15rem 0; }
  /* ...and the list that is neither prose nor rows.

     The duplicate question's matches are CARDS in a <ul>, and that <ul>
     happens to sit inside a .tut-card - so both rules above landed on it: a
     bullet it never asked for, 1.2rem of indent that pushed the cards off
     centre, and 2.4px of padding in place of the 0.6rem that keeps the text
     off its own coloured edge. Named here, next to the rule it is an
     exception to, rather than given a bigger specificity somewhere far away
     where nobody would connect the two. */
  .tut-card .dupe-list { list-style: none; padding-left: 0; margin: 0.9rem 0 0; }
  .tut-card .dupe-item { padding: 0.65rem 0.8rem; }

  /* ===== Release notes ("what's new") popup — reuses .tut-overlay/.tut-card ===== */
  .rn-head { display: flex; align-items: center; justify-content: center; gap: 0.45rem; }
  .rn-sun { font-size: 1.5rem; line-height: 1; }
  .rn-brand { font-size: 1.05rem; font-weight: 800; color: var(--fg); }
  .rn-title { font-size: 1.4rem; font-weight: 800; margin: 0.6rem 0 0.9rem; text-align: center; line-height: 1.15; }
  .rn-body { display: flex; flex-direction: column; gap: 1.1rem; }
  .rn-entry + .rn-entry { border-top: 1px solid var(--border); padding-top: 1.1rem; }
  .rn-entry-meta {
    font-size: 0.72rem; color: var(--fg-muted); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.06em;
  }
  .rn-entry-title { font-size: 1rem; font-weight: 700; color: var(--fg); margin-top: 0.2rem; }
  .rn-items { list-style: disc; padding-left: 1.2rem; margin: 0.5rem 0 0; }
  .rn-items li { font-size: 0.95rem; line-height: 1.5; color: var(--fg); padding: 0.15rem 0; }
  /* "Got it" stays pinned to the bottom of the (scrollable) release-notes card so
     it's reachable no matter how many entries there are. Scoped to the release
     notes overlay so the tutorial card's nav is unaffected. A gradient fade above
     it softens the scrolling content underneath. */
  .rn-got-it { margin-top: 1.2rem; }
  #release-notes-overlay .rn-got-it {
    position: sticky;
    bottom: 0;
    z-index: 1;
    margin-top: 0.5rem;
  }
  #release-notes-overlay .rn-got-it::before {
    content: "";
    position: absolute;
    left: 0; right: 0; top: -1.5rem;
    height: 1.5rem;
    background: linear-gradient(to bottom, transparent, var(--bg-card));
    pointer-events: none;
  }

  .info-modal-overlay {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.5);
    z-index: 210;
    display: none;
    align-items: center; justify-content: center;
    padding: 1rem;
  }
  .info-modal-overlay.open { display: flex; }
  .info-modal {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 16px;
    width: 100%; max-width: 460px;
    max-height: 80vh; overflow-y: auto;
    padding: 0.4rem 1.25rem 1.25rem;
    box-shadow: 0 12px 40px rgba(0,0,0,0.4);
  }
  .info-modal-head { display: flex; justify-content: flex-end; }
  .info-modal-close {
    background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg); border-radius: 8px;
    width: 32px; height: 32px; cursor: pointer; font-size: 1rem;
    display: flex; align-items: center; justify-content: center;
    -webkit-tap-highlight-color: transparent; flex: 0 0 auto;
  }
  .info-modal-close:active { opacity: 0.6; }
  /* The list renderers return their own .card wrapper + heading; let it blend. */
  .info-modal-body > .card { background: none; border: none; padding: 0; margin: 0; }
  .info-modal-body h2 { margin-top: 0; }

  /* ===== A day's detail, wherever it is shown =====
     One class owns how a day reads — its sections, headings and rows — and the
     surfaces (.day-detail--inline in the page, .day-detail--sheet on a phone)
     add nothing but their own layout. This used to be three parallel rule sets
     keyed off three different containers, and they drifted: the in-page day
     matched neither of the other two and inherited none of this. */
  .day-detail .day-sec { margin: 0 0 1.75rem; }
  .day-detail .day-sec:last-child { margin-bottom: 0; }
  .day-detail h3 {
    margin: 0.2rem 0 0.9rem;
    font-size: 1.05rem; font-weight: 700; line-height: 1.25;
    color: var(--fg); text-transform: none; letter-spacing: normal;
  }
  /* A sub-header after a list needs a clear gap so class groups don't crowd
     onto the items above them. */
  .day-detail ul + h3 { margin-top: 1.6rem; }
  .day-detail li { padding: 0.9rem 0; line-height: 1.5; }
  .day-detail .sched-note { margin: 0 0 1rem; }
  .day-detail .empty-day { margin: 0.5rem 0; }
  /* The one thing a surface may change about how a day reads: a phone sheet is
     touched, not clicked, so its rows get a bigger target. */
  .day-detail--sheet li { padding: 1.15rem 0; }

  .ast-msg a {
    color: var(--accent); text-decoration: underline;
    text-underline-offset: 2px; word-break: break-word;
  }
  .ast-msg.bot a:hover { text-decoration-thickness: 2px; }

  /* ===== Assistant image attachment ===== */
  #assist-attach {
    background: none; border: none; color: var(--fg-muted);
    cursor: pointer; padding: 0.35rem; line-height: 0; flex: 0 0 auto;
    border-radius: 8px; font-family: inherit;
  }
  #assist-attach:hover { color: var(--accent); background: var(--bg-input); }
  #assist-attach .ico { width: 1.15rem; height: 1.15rem; }
  .ast-shot { position: relative; margin: 0 0 0.5rem; }
  .ast-shot img {
    display: block; max-height: 140px; max-width: 100%;
    border-radius: 10px; border: 1px solid var(--border);
  }
  .ast-shot button {
    position: absolute; top: 5px; left: 5px;
    width: 22px; height: 22px; border-radius: 50%;
    background: rgba(0,0,0,0.75); color: #fff; border: none;
    cursor: pointer; font-size: 0.72rem; line-height: 1; font-family: inherit;
  }
  .ast-msg-img {
    display: block; max-width: 100%; border-radius: 10px;
    margin-top: 0.4rem; border: 1px solid var(--border);
  }

  .fb-limit {
    margin: 0.55rem 0 0; text-align: center;
    font-size: 0.78rem; color: var(--fg-muted);
  }
  .fb-limit.waiting { color: var(--accent); }
  .fb-limit.spent { color: var(--danger); }

  /* ===== Feedback screenshot attachment ===== */
  .fb-shot { margin: 0.6rem 0 0.8rem; }
  .fb-shot-btn {
    background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg-muted); border-radius: 10px;
    padding: 0.55rem 0.9rem; font-size: 0.88rem; font-family: inherit;
    font-weight: 600; cursor: pointer; width: 100%;
  }
  .fb-shot-btn:hover { color: var(--fg); border-color: var(--accent); }
  .fb-shot-preview { position: relative; margin-top: 0.55rem; }
  .fb-shot-preview img {
    display: block; width: 100%; max-height: 220px;
    object-fit: contain; border-radius: 10px;
    border: 1px solid var(--border); background: var(--bg-input);
  }
  .fb-shot-remove {
    position: absolute; top: 6px; right: 6px;
    width: 26px; height: 26px; border-radius: 50%;
    background: rgba(0,0,0,0.7); color: #fff;
    border: none; cursor: pointer; font-size: 0.85rem; line-height: 1;
    font-family: inherit;
  }

  /* ===== "Daily Overview has updated" bar =====
     Sits above everything, including the sticky header, and slides down from
     the top edge. Quiet by design: it is news, not an alarm. */
  .update-bar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 400;
    display: flex; align-items: center; gap: 0.75rem;
    padding: calc(env(safe-area-inset-top, 0px) + 0.6rem) 1rem 0.6rem;
    background: var(--bg-card);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 6px 20px rgba(0,0,0,0.35);
    transform: translateY(-100%);
    transition: transform 0.25s ease;
  }
  .update-bar.in { transform: translateY(0); }
  .update-bar-text { flex: 1 1 auto; font-size: 0.9rem; font-weight: 600; color: var(--fg); }
  .update-bar-go {
    flex: 0 0 auto; background: var(--accent); border: none; color: var(--bg);
    border-radius: 999px; padding: 0.4rem 0.9rem;
    font-family: inherit; font-size: 0.85rem; font-weight: 700; cursor: pointer;
  }
  .update-bar-x {
    flex: 0 0 auto; background: none; border: none; color: var(--fg-muted);
    font-size: 0.9rem; cursor: pointer; font-family: inherit; padding: 0.3rem;
    -webkit-tap-highlight-color: transparent;
  }
  .update-bar-x:hover { color: var(--fg); }

  /* Shown only when the render itself threw — see renderData(). */
  .render-fail h2 { font-size: 1.05rem; text-transform: none; letter-spacing: normal;
    color: var(--fg); margin-bottom: 0.5rem; }
  .render-fail p { color: var(--fg-muted); font-size: 0.9rem; margin: 0 0 0.9rem; line-height: 1.5; }
  .render-fail-actions { margin-bottom: 0.9rem; }
  .render-fail-detail {
    margin: 0; padding: 0.6rem 0.7rem; background: var(--bg-input);
    border-radius: 8px; font-size: 0.75rem; color: var(--fg-muted);
    white-space: pre-wrap; word-break: break-word; overflow-x: auto;
  }

  /* ===== Test alert banner ===== */
  .test-alert {
    background: linear-gradient(135deg, var(--danger), #c2410c);
    color: #fff;
    padding: 0.5rem 0.75rem;
    border-radius: 10px;
    margin: 0.5rem 0;
    font-size: 0.8rem;
    box-shadow: 0 2px 6px rgba(239, 68, 68, 0.15);
    display: flex; align-items: center; gap: 0.6rem;
  }
  .test-alert .ta-label {
    font-size: 0.6rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
    opacity: 0.9;
    flex: 0 0 auto;
    padding: 0.15rem 0.4rem;
    background: rgba(255,255,255,0.18);
    border-radius: 4px;
  }
  .test-alert .ta-title { font-weight: 600; font-size: 0.85rem; }

  /* ===== Progress bar ===== */
  .progress-block { margin: 0.4rem 0 0.75rem; }
  .progress-text {
    font-size: 0.75rem; color: var(--fg-muted);
    margin-bottom: 0.3rem;
  }
  .progress-bar {
    height: 6px;
    background: var(--bg-card);
    border-radius: 3px;
    overflow: hidden;
    border: 1px solid var(--border);
  }
  .progress-fill {
    height: 100%;
    background: var(--accent);
    transition: width 0.3s;
  }

  /* ===== Stats card ===== */
  /* The week's three numbers, on a phone.

     They were big square tiles here and small outlined chips in the computer's
     header line - the same three numbers in two shapes, plus a third copy
     above the Week strip. One shape now, .dh-chip, and this is the row that
     holds them where there is no header line to put them in. */
  /* Reorder the view menu, by dragging the grip. Phone only - a computer
     shows the views as a row of tabs, where the order is part of the layout
     rather than a preference. */
  /* ===== The week board (simple mode) =====

     A sheet of ruled paper, six columns wide. Monday to Friday get a column
     each; Saturday and Sunday share the last one, because a weekend day holds
     a fraction of what a school day holds and giving each its own column makes
     the week read as mostly empty.

     The rules are a repeating gradient on the day's body rather than markup,
     so a day with two things on it and a day with nine are the same piece of
     paper. --sw-line is the row height and the gradient's period; every line
     of text is exactly one row tall so the writing sits ON the rules. */
  :root { --sw-line: 2.35rem; }

  .sw-top {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 1rem; margin: 0.2rem 0 1.1rem;
  }
  .sw-month {
    margin: 0; font-size: clamp(1.5rem, 4vw, 2rem); font-weight: 800;
    letter-spacing: -0.02em; color: var(--fg);
  }
  .sw-arrows { display: flex; align-items: center; gap: 0.45rem; flex: 0 0 auto; }
  /* Four buttons, one set.

     They were 34px round, which is under the 44px a fingertip needs, and
     Today was the odd one out twice over: transparent where the other three
     were filled, and a different height. Four controls in a row that do the
     same kind of job read as one thing or as a mess; the only difference
     between them now is that one of them is a word and three are not. */
  .sw-arrow, .sw-today {
    height: 2.75rem; min-width: 2.75rem;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--bg-card); border: 1px solid var(--border);
    color: var(--fg); font: inherit; line-height: 1; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .sw-arrow { width: 2.75rem; border-radius: 50%; font-size: 1.3rem; }
  .sw-today { border-radius: 999px; font-size: 0.9rem; padding: 0 1rem; }
  .sw-arrow:hover, .sw-today:hover { border-color: var(--accent); color: var(--accent); }
  .sw-arrow:active, .sw-today:active { background: var(--bg-input); }

  /* Week or month, above the arrows. Two words rather than an icon: this
     changes what the whole page is, and an icon that does that is a guess. */
  .sw-view {
    display: inline-flex; align-items: center; height: 2.75rem;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 999px; padding: 0.2rem; margin-right: 0.15rem;
  }
  .sw-view-btn {
    height: 2.35rem; padding: 0 0.85rem; border: 0; border-radius: 999px;
    background: none; color: var(--fg-muted); font: inherit; font-size: 0.85rem;
    cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .sw-view-btn:hover { color: var(--fg); }
  .sw-view-btn.on { background: var(--bg-input); color: var(--fg); font-weight: 700; }

  /* ===== The month grid on the board =====

     Titles in the cells, not a count. A number tells you how busy Thursday
     is; the reason anybody opens a month is usually to find WHICH day
     something is on, and a grid of numbers cannot answer that. */
  /* ===== The month, as a page rather than a grid =====

     No boxes. A ruled grid of bordered cells is a spreadsheet, and the week
     board next door is deliberately a sheet of paper - so the month is the
     same paper, with a hairline under each row of days and nothing down the
     sides. What carries the eye is the chips, not the furniture. */
  .swm-grid {
    display: grid; grid-template-columns: repeat(7, 1fr);
    gap: 0; margin: 0 0 0.9rem;
  }
  .swm-dow {
    font-size: 0.64rem; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--fg-faint);
    text-align: center; padding: 0 0 0.45rem;
  }
  .swm-cell {
    position: relative; overflow: hidden;
    min-height: 6.4rem; display: flex; flex-direction: column;
    align-items: stretch; gap: 0.3rem;
    background: none; border: 0; border-top: 1px solid var(--border-soft);
    border-radius: 0; padding: 0.45rem 0.3rem 0.5rem;
    font: inherit; color: inherit; text-align: left; cursor: pointer;
    -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  }
  .swm-cell.is-empty { cursor: default; }
  /* The day number, big enough to be the thing you scan for. */
  .swm-n {
    align-self: center;
    font-size: 0.95rem; font-weight: 700; color: var(--fg); line-height: 1.6;
    min-width: 1.6rem; height: 1.6rem; border-radius: 999px; text-align: center;
  }
  .swm-cell.is-off .swm-n, .swm-cell.is-free .swm-n { color: var(--fg-muted); }
  /* Today is the one circled, the way a paper calendar does it. */
  .swm-cell.is-today .swm-n {
    background: var(--fg); color: var(--bg);
  }
  .swm-cell.is-open .swm-n {
    background: var(--accent); color: var(--bg);
  }
  .swm-lines { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
  /* A chip filled with its colour, not a line ticked with it. A four-pixel
     bar beside grey text is a colour you have to go looking for. */
  .swm-chip {
    font-size: 0.68rem; line-height: 1.35; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    padding: 0.12rem 0.35rem; border-radius: 5px;
    /* A task with no colour is still a chip. --bg-input is a hair off the page
       and reads as nothing at all next to the coloured ones; a card-coloured
       fill with a hairline is the same object in the same row. */
    background: var(--bg-card); color: var(--fg);
    box-shadow: inset 0 0 0 1px var(--border);
  }
  .swm-chip b { font-size: 0.58rem; letter-spacing: 0.04em; opacity: 0.8; }
  /* With a colour on it, the chip IS the colour and the text goes to whatever
     can be read on it. Not a fixed white: half these colours are yellows and
     limes, and white on those is the chip nobody can read. */
  .swm-chip.has-c {
    background: var(--sw-c);
    color: color-mix(in srgb, var(--sw-c) 22%, #000);
    box-shadow: none;
  }
  .swm-more { font-size: 0.62rem; color: var(--fg-faint); padding-left: 0.35rem; }
  .swm-clear { font-size: 0.62rem; color: var(--fg-very-faint); padding-left: 0.35rem; }
  /* Hover as a layer over the cell. It has no background of its own any more,
     so this is what a cell looks like when you point at it. */
  .swm-cell::before {
    content: ''; position: absolute; inset: 1px 0 0; border-radius: 8px;
    background: var(--fg); opacity: 0; transition: opacity .12s ease;
    pointer-events: none;
  }
  .swm-cell:not(.is-empty):hover::before { opacity: 0.06; }
  .swm-cell:not(.is-empty):active::before { opacity: 0.11; }
  .swm-cell > * { position: relative; z-index: 1; }
  @media (hover: none) { .swm-cell:hover::before { opacity: 0; } }
  .swm-open { margin: 0 0 0.9rem; }
  .swm-open-head { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }
  .swm-open-head h3 { margin: 0 0 0.4rem; font-size: 1rem; font-weight: 700; }
  .swm-hint { margin: 0 0 0.9rem; font-size: 0.85rem; color: var(--fg-muted); text-align: center; }
  /* A phone cannot give seven columns a title each. The dates and the fullness
     still read; the titles are what has to go. */
  /* A phone cannot give seven columns a title each - the chips become the
     bars they were, which still say how full a day is and which colour it is,
     and the day itself is one tap away for the words. */
  @media (max-width: 640px) {
    .swm-cell { min-height: 4rem; padding: 0.35rem 0.15rem 0.4rem; gap: 0.2rem; }
    .swm-n { font-size: 0.85rem; line-height: 1.5; min-width: 1.5rem; height: 1.5rem; }
    .swm-chip { font-size: 0; line-height: 0; padding: 0; height: 5px;
                border-radius: 3px; background: var(--fg-faint); }
    .swm-chip b { display: none; }
    .swm-chip.has-c { background: var(--sw-c); }
    .swm-more, .swm-clear { font-size: 0.58rem; padding-left: 0.1rem; }
  }

  .sw-grid { display: grid; grid-template-columns: 1fr; gap: 0 1.4rem; }
  .sw-weekend { display: flex; flex-direction: column; gap: 0; }

  .sw-day-h {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 0.5rem; padding: 0 0 0.35rem;
    border-bottom: 2px solid var(--border);
  }
  .sw-day { min-width: 0; overflow: hidden; }
  .sw-date {
    font-size: 1rem; font-weight: 800; color: var(--fg);
    white-space: nowrap; flex: 0 0 auto;
  }
  /* The weekday is the first thing to go when a column gets narrow: the date
     is the part you navigate by, and "Mon" clipped to "M" beside the next
     column's date reads as a broken layout. */
  .sw-dow {
    font-size: 0.9rem; font-weight: 600; color: var(--fg-faint);
    white-space: nowrap; overflow: hidden; flex: 0 1 auto; min-width: 0;
  }
  @media (min-width: 900px) and (max-width: 1080px) { .sw-dow { display: none; } }
  /* Today is the only coloured thing on the page. */
  .sw-day.is-today .sw-day-h { border-bottom-color: var(--accent); }
  .sw-day.is-today .sw-date,
  .sw-day.is-today .sw-dow { color: var(--accent); }

  .sw-body {
    min-height: calc(var(--sw-line) * 4);
    cursor: text;
    background-image: repeating-linear-gradient(
      to bottom,
      transparent 0, transparent calc(var(--sw-line) - 1px),
      var(--border-soft) calc(var(--sw-line) - 1px), var(--border-soft) var(--sw-line));
  }
  .sw-day.is-small .sw-body { min-height: calc(var(--sw-line) * 2); }

  /* The line under the greeting, in the full app. The numbers are the part
     you read, so they are the part that is emphasised. */
  .greet-sub b {
    color: var(--accent); font-weight: 800;
    font-variant-numeric: tabular-nums;
  }
  .greet-late b { color: var(--danger); }
  .greet-late { color: var(--danger); }
  .greet-good { color: var(--fg-muted); }
  /* Same line, in the computer header. */
  .dh-sub b { color: var(--accent); font-weight: 800; font-variant-numeric: tabular-nums; }
  /* The weather on the day line: an icon and its numbers, together, always. */
  .dh-wx { white-space: nowrap; display: inline-flex; align-items: center; gap: 0.3rem; }
  .dh-wx .wx { width: 1.25em; height: 1.25em; vertical-align: middle; }
  .dh-sub .greet-late, .dh-sub .greet-late b { color: var(--danger); }

  /* Hello, above the week. Big enough to be the first thing read, because on
     a board with no cards and no colour it is the only warm thing on screen. */
  .sw-hello { margin: 0 0 1.1rem; }
  /* text-transform: none, deliberately. The bare h2 in this stylesheet is the
     app's small uppercase section label, so "Good morning, Noam" came out as
     GOOD MORNING, NOAM - shouted, and the opposite of the warm thing it is
     meant to be. Same trap as .day-panel-title further down. */
  .sw-hello-h {
    margin: 0; font-size: clamp(1.35rem, 3.2vw, 1.75rem); font-weight: 800;
    letter-spacing: -0.02em; color: var(--fg);
    text-transform: none;
  }
  .sw-hello-p {
    margin: 0.3rem 0 0; font-size: 1.02rem; color: var(--fg-muted);
  }
  .sw-hello-p b {
    color: var(--accent); font-weight: 800;
    font-variant-numeric: tabular-nums;
  }

  /* The tick on a line. On a mouse it appears when the row is under the
     pointer; on a touch screen there is no hover, so it is simply always
     there. The circle is 16px and the target around it is 36px. */
  /* At the right end of the line. It used to be in front of the words, which
     pushed every line of a ruled page in by the width of something that is
     invisible most of the time. */
  .sw-tick {
    order: 2; flex: 0 0 auto; width: 16px; height: 16px; border-radius: 50%;
    border: 2px solid var(--border); background: none; cursor: pointer;
    padding: 0; position: relative; opacity: 0; transition: opacity .12s ease;
  }
  .sw-tick::before { content: ''; position: absolute; inset: -10px; }
  .sw-item:hover .sw-tick,
  .sw-tick:focus-visible { opacity: 1; }
  .sw-item.done .sw-tick { opacity: 1; background: var(--accent); border-color: var(--accent); }
  .sw-item.done .sw-tick::after {
    content: '\2713'; position: absolute; inset: 0; color: #fff;
    font-size: 10px; line-height: 12px; text-align: center; font-weight: 700;
  }
  @media (hover: none) { .sw-tick { opacity: 1; } }

  /* A colour is a bar down the left of the line, not a filled row: this is
     ruled paper and a block of colour on it stops being writing. */
  /* One place says what each preset is; everything that shows a colour reads
     --sw-c. It was six near-identical rules per surface, three surfaces deep,
     and a seventh colour would have meant a seventh copy in each - which is
     the shape of thing that stops a feature being worth building. A custom
     colour writes --sw-c inline instead and every rule below already paints
     it.

     The presets stay `var(--cN)` rather than becoming six hexes here, so a
     theme can restate what "red" means and every line follows. */
  /* Ten, spanning the wheel: red, orange, amber, yellow, green, teal, sky,
     indigo, violet, pink.

     The NUMBERS never move. 1-6 are what they have always been, and 7-10 are
     appended, because a task stores the number and renumbering them would
     silently repaint every board that already exists. The rainbow ORDER is a
     display order (SW_PRESETS in app.js), which costs nothing to change. */
  .sw-c1 { --sw-c: var(--c1, #ef4444); }
  .sw-c2 { --sw-c: var(--c2, #f59e0b); }
  .sw-c3 { --sw-c: var(--c3, #22c55e); }
  .sw-c4 { --sw-c: var(--c4, #38bdf8); }
  .sw-c5 { --sw-c: var(--c5, #a855f7); }
  .sw-c6 { --sw-c: var(--c6, #ec4899); }
  .sw-c7 { --sw-c: var(--c7, #f97316); }
  .sw-c8 { --sw-c: var(--c8, #eab308); }
  .sw-c9 { --sw-c: var(--c9, #14b8a6); }
  .sw-c10 { --sw-c: var(--c10, #6366f1); }

  .sw-item { padding-left: 0; }
  .sw-item.has-c { box-shadow: inset 3px 0 0 var(--sw-c); padding-left: 0.5rem; }

  .sw-colours {
    display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.9rem;
  }
  /* Round, and big enough to hit. Ten of them plus "none" is a row of small
     targets, and 20px was under every guideline there is. */
  .sw-colour {
    width: 23px; height: 23px; border-radius: 50%; cursor: pointer;
    border: 2px solid var(--border); background: var(--bg-input); padding: 0;
  }
  .sw-colour.has-c { background: var(--sw-c); border-color: var(--sw-c); }
  .sw-colour.on { outline: 2px solid var(--fg); outline-offset: 2px; }
  /* The seventh swatch: every other colour there is. Rainbow only while it is
     UNCHOSEN - once a colour is picked it shows that colour, so the row still
     answers "what colour is this line" rather than "custom exists". */
  .sw-colour-any {
    background: conic-gradient(#ef4444, #f59e0b, #eab308, #22c55e,
                               #38bdf8, #6366f1, #a855f7, #ec4899, #ef4444);
    border-color: var(--border);
  }
  /* The whole rainbow, not folded away behind a swatch.

     The ten above are shortcuts to colours worth having; this is the answer to
     "I want THAT one". It used to be hidden until you found the rainbow
     button, which is a colour picker that most people never discovered they
     had. */
  /* The wheel, the code and the button, rounded like everything else in the
     card. They were the one square-cornered row on a page of pills. */
  .sw-pick {
    display: flex; align-items: center; gap: 0.5rem;
    margin-top: 0.55rem;
  }
  .sw-pick-wheel {
    flex: 0 0 auto; width: 2.6rem; height: 2.4rem; padding: 0; cursor: pointer;
    background: none; border: 1px solid var(--border); border-radius: 999px;
    overflow: hidden;
  }
  /* Safari and Chrome each wrap the swatch in their own box; without these the
     colour sits in a square inside the round button. */
  .sw-pick-wheel::-webkit-color-swatch-wrapper { padding: 2px; }
  .sw-pick-wheel::-webkit-color-swatch { border: 0; border-radius: 999px; }
  .sw-pick-wheel::-moz-color-swatch { border: 0; border-radius: 999px; }
  .sw-pick-row { display: flex; gap: 0.5rem; flex: 1 1 auto; min-width: 0; }
  .sw-pick-hex {
    flex: 1 1 auto; min-width: 0;
    background: var(--bg-card); border: 1px solid var(--border); color: var(--fg);
    border-radius: 999px; padding: 0.5rem 0.85rem;
    font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 0.9rem;
    text-transform: uppercase;
  }
  .sw-pick-hex:focus { outline: none; border-color: var(--accent); }
  .sw-pick-hex.bad { border-color: var(--danger); }
  .sw-pick-save { flex: 0 0 auto; border-radius: 999px; padding: 0.5rem 1rem; }

  /* A reminder's day and time, side by side, and how it reaches you. */
  .sw-bell-when { display: flex; gap: 0.4rem; }
  .sw-bell-when .sw-bell-custom { flex: 1 1 0; min-width: 0; }
  .sw-bell-how { display: flex; gap: 0.4rem; padding: 0.5rem 0.6rem 0.6rem; }
  .sw-bell-way {
    flex: 1 1 0; min-height: 2.4rem; padding: 0 0.6rem;
    background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg-muted); border-radius: 8px;
    font: inherit; font-size: 0.82rem; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .sw-bell-way:hover { border-color: var(--accent); color: var(--fg); }
  .sw-bell-way.on { border-color: var(--accent); color: var(--accent); font-weight: 700; }

  /* ===== Files on a task =====
     A list of rows, not a grid of thumbnails: a thumbnail is a second download
     of a photograph to answer a question the filename already answers, and the
     storage this runs on is a gigabyte. */
  .sw-files { margin-top: 0.9rem; }
  .sw-file-list { list-style: none; margin: 0 0 0.4rem; padding: 0; }
  .sw-file {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.35rem 0; border-bottom: 1px solid var(--border-soft);
  }
  .sw-file:last-child { border-bottom: 0; }
  .sw-file-kind {
    flex: 0 0 auto; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.05em;
    color: var(--fg-muted); border: 1px solid var(--border);
    border-radius: 4px; padding: 0.1rem 0.28rem;
  }
  .sw-file-name {
    flex: 1 1 auto; min-width: 0; text-align: left;
    background: none; border: 0; padding: 0; font: inherit; font-size: 0.88rem;
    color: var(--fg); cursor: pointer;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .sw-file-name:hover { color: var(--accent); text-decoration: underline; }
  .sw-file-size, .sw-file-note {
    flex: 0 0 auto; font-size: 0.72rem; color: var(--fg-faint); white-space: nowrap;
  }
  /* A file the fortnight sweep has taken. The row stays so the task still
     records that there WAS one - a picture that silently vanishes is worse
     than one that says where it went. */
  .sw-file.is-gone .sw-file-name { color: var(--fg-faint); cursor: default; }
  .sw-file.is-gone .sw-file-note { font-style: italic; }
  .sw-file-x {
    flex: 0 0 auto; width: 28px; height: 28px; border-radius: 6px;
    background: none; border: 0; color: var(--fg-faint);
    font: inherit; font-size: 0.85rem; cursor: pointer;
  }
  .sw-file-x:hover { color: var(--danger); background: var(--bg-input); }
  .sw-file-add {
    background: var(--bg-input); border: 1px dashed var(--border);
    color: var(--fg-muted); border-radius: 8px;
    padding: 0.45rem 0.7rem; font: inherit; font-size: 0.82rem; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .sw-file-add:hover { border-color: var(--accent); color: var(--fg); }
  .sw-file-add:disabled { opacity: 0.6; cursor: default; }

  /* ===== The picture, shown =====

     A row saying "worksheet photo.jpg" is a filename, and somebody who
     photographed the board wants the board. So an image shows itself, and its
     row stays underneath it as the place the name and the size and the delete
     live - the row is the record, the picture is the point. */
  .sw-file-shot { list-style: none; position: relative; margin: 0.5rem 0 0; }
  .sw-shot-btn {
    display: block; width: 100%; padding: 0; border: 0; cursor: zoom-in;
    background: var(--bg-input); border-radius: 10px; overflow: hidden;
    -webkit-tap-highlight-color: transparent;
  }
  .sw-shot {
    display: block; width: 100%; height: auto;
    /* A third of the screen, not half. Big enough to read a worksheet, small
       enough that the notes and the steps above it are still on the page -
       the picture is part of the task, not a replacement for it. Tap it for
       the full thing. */
    max-height: 32vh; object-fit: contain;
    background: var(--bg-input);
  }
  .sw-file-shot .sw-file-x {
    position: absolute; top: 6px; right: 6px;
    background: rgba(0,0,0,.55); color: #fff; border-radius: 8px;
  }
  .sw-file-shot .sw-file-x:hover { background: rgba(0,0,0,.75); color: #fff; }
  /* The row under a picture is a caption, not a list item of its own. */
  .sw-file.is-under { border-bottom: 0; padding-top: 0.25rem; opacity: 0.85; }

  /* ===== A picture on one step ===== */
  .sw-sub-clip {
    flex: 0 0 auto; width: 26px; height: 26px; border-radius: 6px;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--fg-faint);
    cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .sw-sub-clip:hover { color: var(--accent); background: var(--bg-input); }
  .sw-sub-files {
    list-style: none; margin: 0.3rem 0 0.1rem; padding: 0 0 0 1.85rem;
    display: flex; flex-wrap: wrap; gap: 0.4rem; width: 100%;
  }
  .sw-sub-file {
    display: flex; align-items: center; gap: 0.35rem;
    font-size: 0.78rem; color: var(--fg-muted);
  }
  .sw-sub-file .sw-shot-sm {
    width: auto; max-width: 9rem; max-height: 6rem; border-radius: 8px;
  }
  .sw-sub-file.is-gone .sw-file-name { color: var(--fg-faint); cursor: default; }
  /* A step is a row; its files sit under it, so the row has to be allowed to
     become two lines. Everything else in this list is deliberately one. */
  .sw-sub { flex-wrap: wrap; }

  /* The same list, on the page somebody opens from a share link. */
  .shared-files { list-style: none; margin: 1rem 0 0; padding: 0; }
  .shared-file {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.4rem 0; border-bottom: 1px solid var(--border-soft);
  }
  .shared-file a { color: var(--fg); font-size: 0.92rem; flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Attachments under a line of a shared class or board. Indented under the
     title rather than beside it: a row with three things on it stops being a
     list of work and starts being a table. */
  .shared-cls-files { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.25rem; }
  .shared-cls-file {
    display: inline-flex; align-items: center; gap: 0.3rem;
    font-size: 0.78rem; color: var(--fg-muted);
  }
  .shared-cls-file:hover { color: var(--accent); }

  /* ===== The boards list, in Settings ===== */
  .sw-deck-row {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.45rem 0; border-bottom: 1px solid var(--border-soft);
  }
  .sw-deck-row:last-child { border-bottom: 0; }
  .sw-deck-pick {
    flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%;
    border: 2px solid var(--border); background: none; padding: 0; cursor: pointer;
  }
  .sw-deck-row.on .sw-deck-pick {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 4px var(--bg-card), inset 0 0 0 12px var(--accent);
  }
  .sw-deck-name {
    flex: 1 1 auto; min-width: 0;
    background: none; border: 1px solid transparent; color: var(--fg);
    border-radius: 8px; padding: 0.35rem 0.45rem; font: inherit; font-size: 0.95rem;
  }
  .sw-deck-name:hover { border-color: var(--border); }
  .sw-deck-name:focus { outline: none; border-color: var(--accent); background: var(--bg-input); }
  .sw-deck-count { flex: 0 0 auto; font-size: 0.78rem; color: var(--fg-muted); }
  .sw-deck-x {
    flex: 0 0 auto; width: 28px; height: 28px; border-radius: 8px;
    background: none; border: 0; color: var(--fg-faint);
    font: inherit; font-size: 0.95rem; cursor: pointer;
  }
  .sw-deck-x:hover { color: var(--danger); background: var(--bg-input); }
  /* Which board the page in front of you is. Beside the month, quiet: it is
     an answer to a question you only have when there is more than one. */
  /* The link icon in front of a share URL. Says what the box is before the
     URL in it has to be read, which for a 60-character token is the whole
     point. */
  .sw-share-ico {
    flex: 0 0 auto; display: inline-flex; align-items: center;
    color: var(--fg-faint);
  }

  /* The link and paperclip icons. One size, wherever they land. */
  .ico-link {
    width: 1.05em; height: 1.05em; flex: 0 0 auto; vertical-align: -0.18em;
  }
  .ico-link-inline { margin-right: 0.1rem; }

  /* A condition worth knowing before you start, not after. Quiet - it is a
     footnote, not a warning about something going wrong. */
  .auth-note {
    margin: -0.2rem 0 0.9rem; padding: 0.55rem 0.7rem;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 10px;
    font-size: 0.82rem; line-height: 1.45; color: var(--fg-muted);
    text-align: left;
  }

  /* ===== Light, dark, or whatever the device is doing =====

     Three buttons rather than a toggle, because there are three answers and a
     toggle can only hold two. Auto is one of them and not a checkbox beside
     them: "follow my phone" is a choice of the same kind as "always light",
     not a modifier on it. */
  .theme-modes {
    display: flex; gap: 0.4rem; margin: 0 0 0.7rem;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 999px; padding: 0.25rem;
  }
  .theme-mode {
    flex: 1 1 0; display: inline-flex; align-items: center; justify-content: center;
    gap: 0.35rem; min-height: 2.35rem; padding: 0 0.7rem;
    background: none; border: 0; border-radius: 999px;
    color: var(--fg-muted); font: inherit; font-size: 0.85rem; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .theme-mode:hover { color: var(--fg); }
  .theme-mode.on { background: var(--bg-card); color: var(--fg); font-weight: 700; }
  .theme-mode-ico { width: 17px; height: 17px; flex: 0 0 auto; }

  .sw-deck-tag {
    font-size: 0.8rem; font-weight: 600; color: var(--fg-muted);
    border: 1px solid var(--border); border-radius: 999px;
    padding: 0.15rem 0.6rem; vertical-align: 0.35em; margin-left: 0.5rem;
    white-space: nowrap;
  }
  .sw-card-btn.ok { color: var(--accent); }
  .sw-card-btn.ok:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }

  .sw-list { margin: 0; padding: 0; list-style: none; }
  /* One line of handwriting. No tick box, no X, no hover controls: everything
     you can do to a task lives in the card that opens when you click it. */
  /* A row, not a block.

     This was `display: block` back when the line held nothing but words. Then
     the tick was added to the left of it, and a block-level .sw-text at
     width:100% cannot sit beside anything - so the words dropped to a second
     line inside a box clamped to one line high, and every item spilled over
     the one below it. That is the overlapping board.

     Flex, and the text takes the space that is left. */
  .sw-item {
    display: flex; align-items: center; gap: 0.45rem;
    padding: 0; border-bottom: 0;
    height: var(--sw-line); line-height: var(--sw-line);
    overflow: hidden;
  }
  .sw-text {
    flex: 1 1 auto; min-width: 0; text-align: left;
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; font-size: 0.92rem; line-height: var(--sw-line);
    color: var(--fg); cursor: pointer;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .sw-text:hover { color: var(--accent); }
  .sw-item.done .sw-text { text-decoration: line-through; color: var(--fg-faint); }

  .sw-add-form { height: var(--sw-line); display: flex; align-items: center; }
  .sw-add-input {
    /* box-sizing is border-box here, so the accent underline is drawn inside
       the row rather than adding a pixel and pushing every rule below it out
       of step with the ruled background. */
    box-sizing: border-box;
    width: 100%; height: var(--sw-line); background: none; color: var(--fg);
    border: 0; border-bottom: 1px solid var(--accent);
    padding: 0; font: inherit; font-size: 0.92rem; line-height: 1;
  }
  .sw-add-input:focus { outline: none; }

  .sw-notes { margin-top: 1.6rem; }
  /* The heading is an input, styled as a heading: no box until you are in it,
     so it reads as a title rather than as a form field sitting above a week. */
  .sw-notes-h {
    display: block; width: 100%; max-width: 14rem;
    background: none; border: 0; border-bottom: 1px solid transparent;
    padding: 0 0 0.1rem; margin-bottom: 0.3rem;
    font: inherit; font-size: 0.95rem; font-weight: 800; color: var(--fg-faint);
  }
  .sw-notes-h:hover { border-bottom-color: var(--border); }
  .sw-notes-h:focus {
    outline: none; color: var(--fg); border-bottom-color: var(--accent);
  }
  .sw-notes-h::placeholder { color: var(--fg-faint); }
  .sw-notes-box {
    width: 100%; background: none; border: 0; resize: vertical;
    color: var(--fg); font: inherit; font-size: 0.92rem;
    line-height: var(--sw-line); padding: 0;
    background-image: repeating-linear-gradient(
      to bottom,
      transparent 0, transparent calc(var(--sw-line) - 1px),
      var(--border-soft) calc(var(--sw-line) - 1px), var(--border-soft) var(--sw-line));
  }
  .sw-notes-box:focus { outline: none; }

  .sw-cta {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.9rem; flex-wrap: wrap; margin-top: 1.6rem;
    border-top: 1px solid var(--border); padding-top: 1rem;
    font-size: 0.88rem; color: var(--fg-muted);
  }
  .sw-cta .btn { width: auto; flex: 0 0 auto; }

  /* Six across once there is room for six. Below that the days stack, which
     is the same paper in one column. */
  @media (min-width: 900px) {
    .sw-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  }
  @media (max-width: 899px) {
    .sw-grid { gap: 1.1rem 0; }
    .sw-weekend { gap: 1.1rem; }
  }

  /* The way past the calendar step, offered rather than hidden.
     Most people who abandon setup abandon it on that screen. */
  .onboard-alt {
    margin-top: 0.9rem; padding-top: 0.9rem;
    border-top: 1px solid var(--border);
    font-size: 0.85rem; color: var(--fg-muted); line-height: 1.5;
  }
  .onboard-alt b { color: var(--fg); }
  .oa-rec { margin: 0; }   /* kept: the guide pages use it too */
  /* A real divider, because this is a fork in setup and not a footnote to the
     field above it. */
  .oa-or {
    display: flex; align-items: center; gap: 0.7rem;
    margin: 0.9rem 0; color: var(--fg-faint); font-size: 0.78rem;
  }
  .oa-or::before, .oa-or::after {
    content: ''; flex: 1 1 auto; height: 1px; background: var(--border);
  }
  .oa-simple-h { margin: 0 0 0.25rem; color: var(--fg); }
  .oa-simple-p { margin: 0; }
  .onboard-alt-btn {
    display: block; width: 100%; margin-top: 0.6rem;
    background: none; border: 1px solid var(--border); border-radius: 10px;
    color: var(--fg); font: inherit; font-size: 0.88rem;
    padding: 0.6rem 0.7rem; cursor: pointer;
  }
  .onboard-alt-btn:hover { border-color: var(--accent); color: var(--accent); }

  /* "Where do I find this link?" — the whole of the old step, folded away.

     A disclosure rather than a link to a page, because the answer is six
     different answers and the one you need depends on which portal your
     school bought. Closed it is one line; open it is what used to be printed
     at everybody unconditionally. */
  .onboard-help {
    margin: 0.9rem 0 0; text-align: left;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 12px; padding: 0 0.9rem;
  }
  .onboard-help > summary {
    cursor: pointer; list-style: none; padding: 0.75rem 0;
    font-size: 0.9rem; font-weight: 600; color: var(--fg);
    display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
  }
  .onboard-help > summary::-webkit-details-marker { display: none; }
  .onboard-help > summary::after { content: '+'; color: var(--fg-muted); font-size: 1.15rem; line-height: 1; }
  .onboard-help[open] > summary::after { content: '\2013'; }
  .onboard-help-in { padding: 0 0 0.9rem; }
  .onboard-help-home { display: block; margin-top: 0.5rem; }
  .onboard-help-home[hidden] { display: none; }   /* see .onboard-field-label */

  /* The two ways to copy the week across. Side by side on anything wide
     enough, because they are alternatives rather than a sequence. */
  .simple-copy-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.6rem; }
  .simple-copy-row .btn { flex: 1 1 14rem; }
  .settings-sub-h {
    margin: 0 0 0.2rem; font-size: 0.95rem; font-weight: 700; color: var(--fg);
  }

  /* One choice on the account step: a tick, a name, and why you would want it. */
  .onboard-opt {
    display: flex; align-items: flex-start; gap: 0.7rem; text-align: left;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 12px; padding: 0.85rem 0.9rem; margin: 0.6rem 0 0;
    cursor: pointer;
  }
  .onboard-opt:hover { border-color: var(--accent); }
  .onboard-opt input { margin-top: 0.15rem; flex: 0 0 auto; width: 1.05rem; height: 1.05rem; accent-color: var(--accent); }
  .oo-body { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
  .oo-t { font-size: 0.95rem; font-weight: 700; color: var(--fg); }
  .oo-rec {
    font-size: 0.68rem; font-weight: 700; letter-spacing: 0.04em;
    text-transform: uppercase; color: var(--accent);
    border: 1px solid var(--accent); border-radius: 999px;
    padding: 0.05rem 0.4rem; margin-left: 0.3rem; vertical-align: 0.1em;
  }
  .oo-d { font-size: 0.83rem; line-height: 1.45; color: var(--fg-muted); }

  /* The focus timer. It sits above the assistant launcher rather than beside
     it, because both are bottom-right and the launcher is the one you press
     by accident. */
  .focus-timer {
    position: fixed; right: 18px;
    bottom: calc(84px + var(--corner-lift, 0px)); z-index: 1450;
    transition: bottom .18s ease;
    display: flex; align-items: center; gap: 0.5rem;
    background: var(--bg-card); border: 1px solid var(--accent);
    border-radius: 999px; padding: 0.45rem 0.6rem 0.45rem 0.75rem;
    box-shadow: 0 10px 28px rgba(0,0,0,.35);
    max-width: min(340px, calc(100vw - 36px));
  }
  .focus-timer[hidden] { display: none; }
  .focus-dot {
    width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
    flex: 0 0 auto; animation: focus-pulse 2s ease-in-out infinite;
  }
  @keyframes focus-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
  @media (prefers-reduced-motion: reduce) { .focus-dot { animation: none; } }
  .focus-time {
    font-weight: 800; font-variant-numeric: tabular-nums; color: var(--fg);
    font-size: 0.95rem; flex: 0 0 auto;
  }
  .focus-label {
    font-size: 0.8rem; color: var(--fg-muted); min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .focus-btn {
    flex: 0 0 auto; background: none; border: 0; cursor: pointer;
    font: inherit; font-size: 0.8rem; font-weight: 600; color: var(--fg-muted);
    padding: 0.3rem 0.4rem; border-radius: 7px;
  }
  .focus-btn:hover { background: var(--bg-input); color: var(--fg); }
  .focus-x { font-size: 0.85rem; }
  /* Two fixed things in one corner: the tip bar moves up when both are out. */
  body:has(#focus-timer:not([hidden])) .undo-bar { bottom: 150px; }

  /* Start a timer, from the row it is about. */

  /* What "Auto" resolved to. A setting that says "Auto" and nothing else
     leaves you unable to tell which of two very different layouts you are
     looking at. */
  .auto-note {
    margin: 0.55rem 0 0; font-size: 0.82rem; color: var(--fg-muted);
    background: var(--bg-input); border: 1px solid var(--border-soft);
    border-radius: 8px; padding: 0.4rem 0.6rem;
  }
  .auto-note[hidden] { display: none; }

  /* Sign out is ordinary; deleting the account is not. The sizes say so. */
  .sett-signout { width: auto; min-width: 9rem; padding: 0.5rem 1rem; }
  .sett-delete {
    width: 100%; color: var(--danger); border-color: var(--danger);
    padding: 0.85rem 1rem; font-size: 1rem; font-weight: 700;
  }

  /* ===== Subtasks ===== */
  .sw-subs { margin-top: 0.9rem; border-top: 1px solid var(--border); padding-top: 0.7rem; }
  .sw-subs-h {
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--fg-faint); margin-bottom: 0.35rem;
  }
  .sw-sub-list { margin: 0 0 0.3rem; padding: 0; list-style: none; }
  .sw-sub {
    display: flex; align-items: center; gap: 0.55rem;
    padding: 0.25rem 0; border-bottom: 0; font-size: 0.92rem;
  }
  .sw-sub-tick {
    flex: 0 0 auto; width: 15px; height: 15px; border-radius: 50%;
    border: 2px solid var(--border); background: none; cursor: pointer;
    padding: 0; position: relative;
  }
  .sw-sub-tick::before { content: ''; position: absolute; inset: -9px; }
  .sw-sub-tick:hover { border-color: var(--accent); }
  .sw-sub.done .sw-sub-tick { background: var(--accent); border-color: var(--accent); }
  .sw-sub.done .sw-sub-tick::after {
    content: '\2713'; position: absolute; inset: 0; color: #fff;
    font-size: 9px; line-height: 11px; text-align: center; font-weight: 700;
  }
  .sw-sub-text { flex: 1 1 auto; min-width: 0; color: var(--fg); }
  .sw-sub.done .sw-sub-text { text-decoration: line-through; color: var(--fg-faint); }
  .sw-sub-x {
    flex: 0 0 auto; background: none; border: 0; cursor: pointer;
    color: var(--fg-faint); font: inherit; font-size: 0.8rem;
    padding: 0.3rem; opacity: 0;
  }
  .sw-sub:hover .sw-sub-x, .sw-sub-x:focus-visible { opacity: 1; }
  @media (hover: none) { .sw-sub-x { opacity: 1; } }
  .sw-sub-form { display: flex; align-items: center; gap: 0.5rem; }
  .sw-sub-plus { color: var(--fg-faint); font-size: 0.95rem; }
  .sw-sub-input {
    flex: 1 1 auto; min-width: 0; background: none; border: 0;
    color: var(--fg); font: inherit; font-size: 0.92rem; padding: 0.3rem 0;
  }
  .sw-sub-input:focus { outline: none; }

  /* ===== Repeat ===== */
  /* An icon in the top row that lights up when the task actually repeats, so
     the state is visible without opening anything. */
  .sw-card-btn.on { color: var(--accent); }
  .sw-menu-rep { min-width: 11rem; }
  .sw-menu-bell { min-width: 15rem; }
  .sw-bell-none {
    padding: 0.6rem; font-size: 0.85rem; color: var(--fg-muted); line-height: 1.4;
  }
  .sw-bell-custom {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.7rem; padding: 0.5rem 0.6rem; margin-top: 0.2rem;
    border-top: 1px solid var(--border-soft);
    font-size: 0.9rem; color: var(--fg);
  }
  .sw-bell-custom input {
    background: var(--bg-input); color: var(--fg); font: inherit;
    font-size: 0.85rem; border: 1px solid var(--border); border-radius: 7px;
    padding: 0.25rem 0.4rem;
  }

  /* ===== Notes, with formatting ===== */
  .sw-tools {
    display: flex; align-items: center; gap: 0.15rem;
    margin: 0.8rem 0 0.2rem;
  }
  .sw-tool {
    background: none; border: 0; cursor: pointer; border-radius: 7px;
    color: var(--fg-muted); font: inherit; font-size: 0.95rem;
    padding: 0.35rem 0.5rem; line-height: 1;
  }
  .sw-tool-h { font-weight: 800; }
  .sw-tool-b { font-weight: 800; }
  .sw-tool:hover { background: var(--bg-input); color: var(--fg); }
  /* The note is a contenteditable, so the placeholder is drawn rather than
     supplied by the browser. :empty alone is not enough - a contenteditable
     that has been typed in and cleared holds a stray <br>. */
  .sw-card-note[data-ph]:empty::before {
    content: attr(data-ph); color: var(--fg-faint); pointer-events: none;
  }
  .sw-card-note h3 { font-size: 1rem; margin: 0.6rem 0 0.2rem; text-transform: none; }
  .sw-card-note ul, .sw-card-note ol { margin: 0.3rem 0; padding-left: 1.3rem; }
  .sw-card-note li {
    display: list-item; border-bottom: 0; padding: 0.1rem 0;
    align-items: initial; gap: 0; font-size: 0.95rem;
  }
  .sw-card-note blockquote {
    margin: 0.4rem 0; padding-left: 0.7rem;
    border-left: 2px solid var(--border); color: var(--fg-muted);
  }
  .sw-card-note a { color: var(--accent); }
  .sw-card-note p { margin: 0.25rem 0; }

  /* ===== The ... menu ===== */
  .sw-menu-wrap { position: relative; }
  .sw-menu {
    position: absolute; right: 0; top: calc(100% + 0.35rem); z-index: 5;
    min-width: 12rem; padding: 0.35rem;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 12px; box-shadow: 0 16px 40px rgba(0,0,0,.5);
  }
  .sw-menu button {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; width: 100%; text-align: left;
    background: none; border: 0; border-radius: 8px; cursor: pointer;
    font: inherit; font-size: 0.92rem; color: var(--fg);
    padding: 0.5rem 0.6rem;
  }
  .sw-menu button span { color: var(--fg-faint); font-size: 0.9rem; }
  .sw-menu button:hover:not([disabled]) { background: var(--bg-input); }
  .sw-menu button[disabled] { color: var(--fg-faint); cursor: default; }

  /* ===== Search ===== */
  .sws-overlay {
    position: fixed; inset: 0; z-index: 1600;
    background: rgba(8,10,14,.6);
    display: flex; align-items: flex-start; justify-content: center;
    padding: 3.5rem 1rem 1rem;
  }
  .sws-panel {
    width: min(560px, 100%); max-height: calc(100vh - 5rem);
    display: flex; flex-direction: column;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 18px; padding: 1.3rem 1.4rem 0.9rem;
    box-shadow: 0 30px 70px -25px rgba(0,0,0,.7);
  }
  .sws-h { margin: 0 0 0.9rem; font-size: 1.4rem; font-weight: 800; text-transform: none; }
  .sws-input-row { display: flex; align-items: center; gap: 0.6rem; }
  .sws-input {
    flex: 1 1 auto; min-width: 0; background: none; border: 0;
    border-bottom: 1px solid var(--border);
    color: var(--fg); font: inherit; font-size: 1.05rem; padding: 0.4rem 0;
  }
  .sws-input:focus { outline: none; border-bottom-color: var(--accent); }
  .sws-x {
    flex: 0 0 auto; background: none; border: 0; cursor: pointer;
    color: var(--fg-muted); font: inherit; font-size: 1rem; padding: 0.4rem;
  }
  .sws-x:hover { color: var(--fg); }
  .sws-colours { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.9rem 0 0.3rem; }
  .sws-colour {
    width: 23px; height: 23px; border-radius: 50%; cursor: pointer;
    border: 2px solid var(--border); background: var(--bg-input); padding: 0;
  }
  .sws-colour.has-c { background: var(--sw-c); border-color: var(--sw-c); }
  .sws-colour.on { outline: 2px solid var(--fg); outline-offset: 2px; }
  .sws-list {
    margin: 0.6rem 0 0; padding: 0; list-style: none;
    overflow-y: auto; flex: 1 1 auto;
  }
  .sws-row { display: block; padding: 0; border-bottom: 1px solid var(--border-soft); }
  .sws-hit {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 1rem; width: 100%; text-align: left;
    background: none; border: 0; cursor: pointer;
    font: inherit; font-size: 0.98rem; color: var(--fg);
    padding: 0.75rem 0.1rem;
  }
  .sws-hit:hover { color: var(--accent); }
  .sws-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .sws-when { flex: 0 0 auto; color: var(--fg-muted); font-size: 0.88rem; }
  .sws-row.done .sws-text { text-decoration: line-through; color: var(--fg-faint); }
  .sws-empty { color: var(--fg-muted); font-size: 0.92rem; padding: 1.2rem 0; margin: 0; }

  /* Which portal, at the top of the calendar step. */
  .portal-pick { margin: 0.2rem 0 0.9rem; }
  .portal-pick-q {
    font-size: 0.85rem; font-weight: 700; color: var(--fg); margin-bottom: 0.5rem;
  }
  .portal-pick-row { display: flex; flex-wrap: wrap; gap: 0.4rem; }
  .portal-opt {
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 999px; color: var(--fg); font: inherit; font-size: 0.85rem;
    padding: 0.45rem 0.8rem; cursor: pointer;
  }
  .portal-opt:hover { border-color: var(--accent); }
  .portal-opt.on { border-color: var(--accent); color: var(--accent); font-weight: 700; }
  .portal-pick-help {
    margin: 0.6rem 0 0; font-size: 0.85rem; line-height: 1.5;
    color: var(--fg-muted);
    background: var(--bg-input); border: 1px solid var(--border-soft);
    border-radius: 10px; padding: 0.6rem 0.7rem;
  }
  .portal-pick-help b { color: var(--fg); }
  .signup-2fa { margin: 0.6rem 0 0.2rem; }
  .signup-2fa small {
    display: block; color: var(--fg-muted); font-size: 0.78rem;
    line-height: 1.45; margin-top: 0.15rem;
  }

  /* Collapsed until the bot check actually wants a person.

     Not display:none, and not visibility:hidden with its height kept: the
     first stops Turnstile running at all, the second leaves a gap in the form
     for something nobody can see. Height goes, layout stays. */
  .captcha-box {
    max-height: 0; opacity: 0; overflow: hidden;
    transition: max-height .2s ease, opacity .2s ease, margin .2s ease;
    margin: 0;
  }
  .captcha-box.show {
    max-height: 140px; opacity: 1; margin: 0 0 0.6rem;
  }

  /* The bot check, on the screens with no form to sit it on. */
  .captcha-sheet {
    position: fixed; inset: 0; z-index: 3000;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0,0,0,.55); padding: 1rem;
  }
  .captcha-sheet[hidden] { display: none; }
  .captcha-sheet-card {
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 16px; padding: 1.1rem; text-align: center;
    max-width: 22rem; width: 100%;
  }
  .captcha-sheet-t {
    margin: 0 0 0.8rem; font-size: 0.9rem; color: var(--fg-muted);
  }
  .captcha-sheet-x {
    margin-top: 0.8rem; background: none; border: none; font: inherit;
    font-size: 0.85rem; color: var(--fg-muted); cursor: pointer;
    text-decoration: underline;
  }

  /* "Recommended", beside a setting's name. */
  .pill-rec {
    display: inline-block; vertical-align: middle; margin-left: 0.45rem;
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    color: var(--accent); border-radius: 999px;
    padding: 0.1rem 0.5rem; font-size: 0.65rem; font-weight: 800;
    letter-spacing: 0.04em; text-transform: uppercase;
  }
  /* The two links under a code box. They had no styles at all, so they came
     out as the browser's default grey buttons - the only two on the site. */
  .auth-link {
    background: none; border: 0; font: inherit; font-size: 0.85rem;
    font-weight: 600; color: var(--fg-muted); cursor: pointer;
    padding: 0.5rem 0.6rem; margin: 0.15rem 0.2rem 0;
    border-radius: 8px; -webkit-tap-highlight-color: transparent;
  }
  .auth-link:hover { color: var(--accent); }

  /* A path that is not a page. */
  #notfound-screen { display: none; }
  body[data-screen="notfound"] #notfound-screen { display: flex; }
  body[data-screen="notfound"] header,
  body[data-screen="notfound"] main,
  body[data-screen="notfound"] #landing-screen,
  body[data-screen="notfound"] nav { display: none !important; }
  .nf-code {
    font-size: clamp(3rem, 12vw, 5rem); font-weight: 800; line-height: 1;
    color: var(--accent); letter-spacing: -0.04em;
  }
  .nf-h { margin: 0.6rem 0 0; font-size: 1.4rem; font-weight: 800; }
  .nf-sub {
    margin: 0.5rem 0 0; color: var(--fg-muted); font-size: 0.92rem;
    line-height: 1.55; max-width: 26rem;
  }
  .nf-sub code {
    background: var(--bg-input); border-radius: 6px; padding: 0.1rem 0.35rem;
    font-size: 0.88em; word-break: break-all;
  }
  .nf-cta { margin-top: 1.2rem; display: inline-block; }

  /* Work that arrived from somebody else's share. */
  .from-share {
    display: inline-flex; vertical-align: middle; margin-left: 0.4rem;
    color: var(--fg-faint);
  }
  .from-share svg { width: 14px; height: 14px; }

  /* The offer at the top of a shared link. */
  .shared-offer {
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 14px; padding: 0.9rem 1rem; margin-bottom: 1.1rem;
  }
  .shared-offer-t { margin: 0 0 0.7rem; font-size: 0.92rem; color: var(--fg); }
  .shared-offer-btns { display: flex; flex-wrap: wrap; gap: 0.5rem; }
  .shared-offer-btns .btn { flex: 1 1 auto; min-height: 44px; }

  /* A shared class: its assignments, read only. */
  .shared-cls { list-style: none; padding: 0; margin: 1rem 0 0; }
  .shared-cls-row {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.7rem 0; border-bottom: 1px solid var(--border-soft);
    font-size: 0.95rem; line-height: 1.4;
  }
  .shared-cls-row:last-child { border-bottom: none; }
  .shared-cls-row.done .shared-cls-text {
    text-decoration: line-through; color: var(--fg-muted);
  }
  .shared-cls-text { flex: 1 1 auto; min-width: 0; }
  .shared-cls-when {
    flex: 0 0 auto; color: var(--fg-muted); font-size: 0.82rem;
    font-variant-numeric: tabular-nums;
  }

  /* Sharing a whole class, from its page. */
  .class-head-btns { display: flex; align-items: center; gap: 0.4rem; }
  .cls-share {
    background: none; border: 1px solid var(--border); color: var(--fg-muted);
    border-radius: 10px; min-width: 44px; min-height: 44px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; font-family: inherit; flex: 0 0 auto;
    -webkit-tap-highlight-color: transparent;
  }
  .cls-share svg { width: 18px; height: 18px; }
  .cls-share:hover { color: var(--fg); border-color: var(--accent); }
  .cls-share.on { color: var(--accent); border-color: var(--accent); }
  .cls-share-panel {
    background: var(--bg-input); border: 1px solid var(--border-soft);
    border-radius: 12px; padding: 0.8rem 0.9rem; margin-top: 0.8rem;
  }
  .cls-share-panel .menu-check { margin-bottom: 0.5rem; }

  /* Which days a task repeats on, and when it stops. */
  .sw-rep-days {
    display: flex; gap: 0.25rem; padding: 0.5rem 0.6rem 0.15rem;
    border-top: 1px solid var(--border-soft);
  }
  .sw-rep-day {
    flex: 1 1 0; min-width: 34px; min-height: 34px;
    background: none; border: 1px solid var(--border); border-radius: 8px;
    color: var(--fg-muted); font: inherit; font-size: 0.8rem; font-weight: 700;
    cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .sw-rep-day.on {
    background: var(--accent); border-color: var(--accent); color: var(--bg);
  }
  .sw-rep-until {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.5rem; padding: 0.5rem 0.6rem;
    font-size: 0.82rem; color: var(--fg-muted);
  }
  .sw-rep-until input {
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 8px; color: var(--fg); font: inherit; font-size: 0.82rem;
    padding: 0.3rem 0.4rem; min-height: 34px;
  }
  .sw-rep-clear {
    display: block; width: calc(100% - 1.2rem); margin: 0 0.6rem 0.5rem;
    background: none; border: 0; color: var(--fg-faint); font: inherit;
    font-size: 0.78rem; text-align: left; cursor: pointer; padding: 0.2rem 0;
  }
  .sw-rep-clear:hover { color: var(--accent); }

  /* Breaking a school assignment into steps. */
  .plan-card { max-width: 30rem; }
  .plan-h { margin: 0; font-size: 1.15rem; font-weight: 800; }
  .plan-sub {
    margin: 0.3rem 0 0.9rem; font-size: 0.9rem; color: var(--fg-muted);
    line-height: 1.4;
  }
  .plan-close { margin-top: 0.9rem; }
  /* How long each step takes, as a number you can argue with. */
  .plan-mins {
    display: inline-flex; align-items: center; gap: 0.25rem;
    color: var(--fg-muted); font-size: 0.8rem; flex: 0 0 auto;
  }
  .plan-mins input {
    width: 3.4rem; background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 8px; color: var(--fg); font: inherit; font-size: 0.82rem;
    padding: 0.25rem 0.35rem; min-height: 32px;
  }

  /* The two alignments a note may carry. Classes rather than inline styles,
     so the note sanitiser can allow them by name - see NOTE_CLASSES. */
  .sw-card-note .note-center, .shared-notes .note-center { text-align: center; }
  .sw-card-note .note-right, .shared-notes .note-right { text-align: right; }

  /* A link attached to a task. */
  .sw-card-link {
    display: flex; align-items: center; gap: 0.45rem;
    margin: 0.6rem 0 0.2rem; padding: 0.5rem 0.6rem;
    background: var(--bg-input); border: 1px solid var(--border-soft);
    border-radius: 10px; color: var(--accent); text-decoration: none;
    font-size: 0.85rem; word-break: break-all; min-height: 44px;
  }
  .sw-card-link:hover { border-color: var(--accent); }

  /* Weather, and the other line icons that replaced emoji. Sized by the
     thing they sit in rather than by a font that varies per platform, which
     is the whole reason they are drawings now. */
  /* inline-block by DEFAULT, and block only where the icon is meant to sit
     alone above a temperature.

     It was the other way round, and a block-level icon in the middle of a
     sentence pushes everything after it onto the next line - which is what
     put the cloud on one line and "74 / H79 / L69 / 75% rain" on the one
     below it. The safe default for something dropped into a line of text is
     the one that stays in the line. */
  .wx {
    width: 1.6em; height: 1.6em;
    display: inline-block; vertical-align: -0.3em;
  }
  .gw-emoji .wx, .wh-emoji-big .wx { width: 2.4em; height: 2.4em; }
  /* These four are the ones stacked above a number in their own column. */
  .gw-emoji .wx, .wh-emoji-big .wx { display: block; margin: 0 auto; }
  .fc-emoji .wx, .hour-emoji .wx, .hl-emoji .wx {
    width: 1.5em; height: 1.5em; display: block; margin: 0 auto;
  }
  .w-emoji .wx { width: 1.15em; height: 1.15em; display: inline-block;
                 vertical-align: -0.2em; }
  .heads-up-ico svg { width: 1.05em; height: 1.05em; vertical-align: -0.15em; }
  .tip-ico svg { width: 1.15em; height: 1.15em; display: block; }
  .ico-inline { width: 1.05em; height: 1.05em; vertical-align: -0.18em;
                display: inline-block; }

  /* "Which class is this for?" - shown when nothing could work it out. */
  .class-ask { flex-wrap: wrap; }
  .class-ask-row {
    display: flex; flex-wrap: wrap; gap: 0.4rem; width: 100%;
    margin-top: 0.6rem;
  }
  /* A spent suggestion in the assistant: still readable, not pressable. */
  .ast-do-spent { opacity: 0.55; }
  .ast-do-spent button { cursor: default; }

  /* ===== Sharing ===== */
  /* Icons in the card's top row, drawn rather than typed: the emoji a
     bell or a link character turns into is a different picture on every
     platform, and several render in colour beside monochrome siblings. */
  .sw-ico { width: 15px; height: 15px; display: block; }
  .sw-card-btn { display: inline-flex; align-items: center; justify-content: center; }

  .sw-share {
    position: absolute; right: 0; top: calc(100% + 0.4rem); z-index: 6;
    width: min(20rem, 80vw); padding: 0.9rem;
    max-height: 70vh; overflow-y: auto;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 14px; box-shadow: 0 20px 50px rgba(0,0,0,.55);
  }
  .sw-share-h { font-weight: 800; font-size: 1.05rem; margin-bottom: 0.7rem; }
  .sw-share-web {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.8rem; padding: 0.7rem 0.8rem; border-radius: 11px;
    background: var(--bg-input); border: 1px solid var(--border-soft);
  }
  .sw-share-web.on { border-color: var(--accent); }
  .sw-share-web-t { min-width: 0; font-size: 0.85rem; }
  .sw-share-web-t b { display: block; color: var(--fg); }
  .sw-share-web-t span { color: var(--fg-muted); }
  .sw-toggle {
    flex: 0 0 auto; width: 42px; height: 24px; border-radius: 999px;
    background: var(--border); border: 0; cursor: pointer; padding: 3px;
    display: flex; justify-content: flex-start; transition: background .15s ease;
  }
  .sw-toggle span {
    display: block; width: 18px; height: 18px; border-radius: 50%;
    background: #fff; transition: transform .15s ease;
  }
  .sw-toggle.on { background: var(--accent); }
  .sw-toggle.on span { transform: translateX(18px); }
  .sw-toggle[disabled] { opacity: 0.5; cursor: default; }
  .sw-share-link { display: flex; gap: 0.4rem; margin-top: 0.6rem; }
  .sw-share-link input {
    flex: 1 1 auto; min-width: 0; background: var(--bg-input); color: var(--fg-muted);
    border: 1px solid var(--border); border-radius: 9px;
    padding: 0.45rem 0.6rem; font: inherit; font-size: 0.8rem;
  }
  .sw-copy { width: auto; flex: 0 0 auto; padding: 0.45rem 0.8rem; font-size: 0.85rem; }
  /* The same row as .sw-share-link, for the places that mark it up with the
     link icon in front. It had no rules at all - not one - so the URL sat in a
     browser-default text box about twenty characters wide with the rest of the
     row empty beside it, on the class share and on every board share. */
  .sw-share-url-row { display: flex; align-items: center; gap: 0.4rem; }
  .sw-share-url {
    flex: 1 1 auto; min-width: 0; background: var(--bg-input); color: var(--fg-muted);
    border: 1px solid var(--border); border-radius: 9px;
    padding: 0.45rem 0.6rem; font: inherit; font-size: 0.8rem;
  }
  .sw-people { margin-top: 0.7rem; }
  .sw-person {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.4rem 0; font-size: 0.85rem;
  }
  .sw-person-i {
    flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
    background: var(--bg-input); border: 1px solid var(--border);
    display: flex; align-items: center; justify-content: center;
    font-size: 0.75rem; font-weight: 700; color: var(--fg-muted);
  }
  .sw-person-e { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .sw-person-s { flex: 0 0 auto; color: var(--fg-faint); font-size: 0.75rem; }
  .sw-invite { display: flex; gap: 0.4rem; margin-top: 0.7rem; }
  .sw-invite input {
    flex: 1 1 auto; min-width: 0; background: var(--bg-input); color: var(--fg);
    border: 1px solid var(--border); border-radius: 9px;
    padding: 0.45rem 0.6rem; font: inherit; font-size: 0.85rem;
  }
  .sw-invite .btn { width: auto; flex: 0 0 auto; padding: 0.45rem 0.8rem; font-size: 0.85rem; }
  /* ===== One share row per board =====

     Each board gets its own link, and there is one more for all of them at
     once. They are boxed rather than listed because each row carries a switch,
     a URL and an email field, and three unboxed rows of that run together into
     one wall of controls where it is not obvious which link belongs to which
     board - which is exactly the mistake worth not making with a share link. */
  .bshare {
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 12px; padding: 0.6rem 0.75rem; margin-top: 0.5rem;
  }
  .bshare .menu-check { margin: 0; }
  .bshare .menu-check small {
    color: var(--fg-faint); font-weight: 400; font-size: 0.78rem;
  }
  /* The widest one, set apart from the boards above it. */
  .bshare-all { border-color: var(--border); border-style: dashed; margin-top: 0.7rem; }
  .bshare-live { margin-top: 0.55rem; }
  .bshare .sw-invite { margin-top: 0.5rem; }

  .sw-share-note {
    margin: 0.7rem 0 0; font-size: 0.78rem; color: var(--fg-faint);
  }

  /* On a phone a popover hanging off an icon has nowhere to hang: the task
     card is nearly the width of the screen, so a panel anchored to one button
     in its top-right corner runs off both edges at once.

     These were made into bottom sheets to fix that, and the sheet was the
     wrong answer - it moved the panel a long way from the button that opened
     it, and it reads as a different kind of thing from the card it came out
     of, which is a centred popup. They are popups too now, centred over the
     card, sized to the screen rather than to their content: that was the
     actual problem, and it is the whole of what needed fixing.

     min() on both axes, so nothing can be wider or taller than the phone
     regardless of what is inside it - a share panel with six people on it and
     a month grid are very different heights. Fixed, so the card cannot clip
     them, and above the card's own overlay so they sit on top of it. */
  @media (max-width: 620px) {
    .sw-share, .sw-menu, .swp {
      position: fixed;
      left: 50%; top: 50%;
      right: auto; bottom: auto;
      transform: translate(-50%, -50%);
      width: min(22rem, calc(100vw - 1.5rem));
      max-width: none;
      max-height: min(80vh, calc(100vh - 2rem));
      overflow-y: auto;
      border-radius: 16px;
      box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.7);
      z-index: 1600;                 /* over .sw-overlay, which is 1500 */
    }
    /* Thumb-sized rows once the menu is a phone-sized thing. */
    .sw-menu { padding: 0.45rem; }
    .sw-menu button { padding: 0.75rem 0.7rem; }
    .swp { padding: 1rem; }
    .sw-share { padding: 1rem; }
  }

  /* ===== The page a shared link opens ===== */
  .shared-screen { display: none; }
  body[data-screen="shared"] .shared-screen {
    display: flex; align-items: flex-start; justify-content: center;
    padding: clamp(1.5rem, 8vh, 5rem) 1rem;
    min-height: 100vh;
  }
  /* Everything else is hidden: this page is one task and nothing else. */
  body[data-screen="shared"] header,
  body[data-screen="shared"] main,
  body[data-screen="shared"] #landing-screen,
  body[data-screen="shared"] #assist-fab,
  body[data-screen="shared"] #filter-bar { display: none !important; }
  .shared-card {
    width: min(620px, 100%);
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 18px; padding: 1.8rem;
  }
  .shared-when {
    font-size: 0.8rem; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--accent); margin-bottom: 0.5rem;
  }
  .shared-h {
    margin: 0; font-size: clamp(1.4rem, 4vw, 1.9rem); font-weight: 800;
    line-height: 1.2; text-transform: none; letter-spacing: -0.01em;
  }
  .shared-h.done { text-decoration: line-through; color: var(--fg-muted); }
  .shared-sub { color: var(--fg-muted); margin: 0.6rem 0 1.2rem; }
  .shared-loading { color: var(--fg-muted); margin: 0; }
  .shared-notes {
    margin-top: 1rem; color: var(--fg); font-size: 1rem; line-height: 1.6;
  }
  .shared-notes h3 { font-size: 1.05rem; text-transform: none; margin: 0.8rem 0 0.3rem; }
  .shared-notes ul, .shared-notes ol { padding-left: 1.3rem; margin: 0.4rem 0; }
  .shared-notes li { display: list-item; border-bottom: 0; align-items: initial; gap: 0; }
  .shared-notes blockquote {
    margin: 0.5rem 0; padding-left: 0.8rem;
    border-left: 2px solid var(--border); color: var(--fg-muted);
  }
  .shared-notes a { color: var(--accent); }
  .shared-subs { margin: 1.2rem 0 0; padding: 0; list-style: none; }
  .shared-sub-row {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.4rem 0; border-bottom: 0; font-size: 0.98rem;
  }
  .shared-sub-row.done { color: var(--fg-faint); text-decoration: line-through; }
  .shared-tick {
    flex: 0 0 auto; width: 15px; height: 15px; border-radius: 50%;
    border: 2px solid var(--border);
  }
  .shared-tick.on { background: var(--accent); border-color: var(--accent); }
  .shared-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    margin-top: 1.6rem; padding-top: 1rem; border-top: 1px solid var(--border);
    font-size: 0.82rem; color: var(--fg-muted);
  }
  .shared-cta { width: auto; flex: 0 0 auto; }

  /* The inactivity warning. Deliberately plain and deliberately in the way:
     it is the only thing on screen that is about to do something to you. */
  .idle-overlay {
    position: fixed; inset: 0; z-index: 2000;
    background: rgba(8,10,14,.66);
    display: flex; align-items: center; justify-content: center; padding: 1rem;
  }
  .idle-overlay[hidden] { display: none; }
  .idle-card {
    width: min(380px, 100%); text-align: center;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 18px; padding: 1.5rem 1.4rem;
    box-shadow: 0 30px 70px -25px rgba(0,0,0,.7);
  }
  .idle-card h2 { margin: 0 0 0.5rem; font-size: 1.25rem; }
  .idle-card p { margin: 0 0 1.1rem; color: var(--fg-muted); font-size: 0.95rem; }
  .idle-card b { color: var(--fg); font-variant-numeric: tabular-nums; }
  .idle-btns { display: flex; gap: 0.5rem; }
  .idle-btns .btn { flex: 1 1 0; width: auto; }

  /* How many steps are done, on the line itself. */
  .sw-subcount {
    font-size: 0.75rem; color: var(--fg-faint);
    font-variant-numeric: tabular-nums;
  }

  /* ===== The task card ===== */
  .sw-overlay {
    position: fixed; inset: 0; z-index: 1500;
    background: rgba(8,10,14,.55);
    display: flex; align-items: center; justify-content: center; padding: 1rem;
  }
  .sw-card {
    width: min(520px, 100%);
    max-height: min(92vh, 900px); overflow-y: auto;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 18px; padding: 1rem 1.1rem 1.1rem;
    box-shadow: 0 30px 70px -25px rgba(0,0,0,.65);
  }
  /* Wider when there is a picture in it.

     520px is right for a title, a paragraph and some steps. It is not right
     for a photograph of a worksheet, which at that width is a thumbnail of a
     page of text - technically visible and not actually readable. The class
     is set by swFilesHtml() only when an image is on screen, so a card with
     no picture is exactly the card it always was. */
  .sw-card.has-shot { width: min(760px, 100%); }
  .sw-card-top {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.8rem; margin-bottom: 0.9rem;
  }
  /* The date is the label; the real <input type=date> sits invisibly on top of
     it, so the whole thing is a date picker without looking like a form. */
  .sw-card-datewrap { position: relative; }
  .sw-card-date {
    display: inline-flex; align-items: center; gap: 0.45rem; cursor: pointer;
    background: none; border: 0; padding: 0.2rem 0.35rem; margin: -0.2rem -0.35rem;
    border-radius: 8px; font: inherit;
  }
  .sw-card-date:hover { background: var(--bg-input); }

  /* ===== The month grid ===== */
  .swp {
    position: absolute; left: 0; top: calc(100% + 0.4rem); z-index: 6;
    width: 17.5rem; padding: 0.8rem;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 14px; box-shadow: 0 20px 50px rgba(0,0,0,.55);
  }
  .swp-head {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: 0.55rem;
  }
  .swp-title { font-weight: 800; font-size: 0.95rem; color: var(--fg); }
  .swp-nav {
    background: none; border: 0; cursor: pointer; color: var(--fg-muted);
    font: inherit; font-size: 1.1rem; line-height: 1; padding: 0.3rem 0.5rem;
    border-radius: 8px;
  }
  .swp-nav:hover { background: var(--bg-input); color: var(--fg); }
  .swp-dow, .swp-grid {
    display: grid; grid-template-columns: repeat(7, 1fr); gap: 0.15rem;
  }
  .swp-dow span {
    text-align: center; font-size: 0.7rem; font-weight: 700;
    color: var(--fg-faint); padding-bottom: 0.25rem;
  }
  .swp-day {
    display: flex; align-items: center; justify-content: center;
    aspect-ratio: 1 / 1; border: 0; background: none; cursor: pointer;
    border-radius: 50%; font: inherit; font-size: 0.85rem; color: var(--fg);
    font-variant-numeric: tabular-nums;
  }
  .swp-day:hover { background: var(--bg-input); }
  .swp-day.is-out { color: var(--fg-very-faint); cursor: default; }
  .swp-day.is-today { color: var(--accent); font-weight: 800; }
  .swp-day.is-on {
    background: var(--accent); color: #fff; font-weight: 800;
  }
  .swp-clear {
    display: block; width: 100%; margin-top: 0.6rem;
    background: none; border: 1px solid var(--border); border-radius: 9px;
    color: var(--fg-muted); font: inherit; font-size: 0.83rem;
    padding: 0.45rem; cursor: pointer;
  }
  .swp-clear:hover { border-color: var(--accent); color: var(--accent); }
  .sw-card-cal { width: 15px; height: 15px; color: var(--fg-muted); flex: 0 0 auto; }
  .sw-card-chev { color: var(--fg-faint); font-size: 0.7rem; }
  .sw-card-date:hover .sw-card-datetext,
  .sw-card-date:hover .sw-card-cal { color: var(--accent); }
  .sw-card-datetext { font-size: 0.95rem; font-weight: 700; color: var(--fg); }
  .sw-card-date input[type="date"] {
    position: absolute; inset: 0; opacity: 0; width: 100%; cursor: pointer;
    border: 0; padding: 0;
  }
  .sw-card-acts { display: flex; align-items: center; gap: 0.2rem; }
  .sw-card-btn {
    background: none; border: 0; cursor: pointer; font: inherit;
    color: var(--fg-muted); font-size: 1rem; line-height: 1;
    padding: 0.5rem 0.55rem; border-radius: 8px;
  }
  .sw-card-btn:hover { background: var(--bg-input); color: var(--fg); }
  .sw-card-btn.danger:hover { color: var(--danger); }

  .sw-card-title-row {
    display: flex; align-items: flex-start; gap: 0.7rem;
    border-bottom: 1px solid var(--border); padding-bottom: 0.7rem;
  }
  .sw-card-title {
    flex: 1 1 auto; background: none; border: 0; resize: none;
    color: var(--fg); font: inherit; font-size: 1.15rem; font-weight: 600;
    line-height: 1.35; padding: 0; field-sizing: content; min-height: 1.6rem;
  }
  .sw-card-title:focus { outline: none; }
  .sw-card-check {
    flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
    border: 2px solid var(--border); background: none; cursor: pointer;
    padding: 0; position: relative; margin-top: 0.15rem;
  }
  .sw-card-check::before { content: ''; position: absolute; inset: -10px; }
  .sw-card-check:hover { border-color: var(--accent); }
  .sw-card-check.on { background: var(--accent); border-color: var(--accent); }
  .sw-card-check.on::after {
    content: '\2713'; position: absolute; inset: 0; color: #fff;
    font-size: 13px; line-height: 18px; text-align: center; font-weight: 700;
  }
  .sw-card-note {
    width: 100%; margin-top: 0.2rem; background: none; border: 0;
    color: var(--fg); font: inherit; font-size: 0.95rem; line-height: 1.5;
    padding: 0.2rem 0; min-height: 5rem; max-height: 16rem; overflow-y: auto;
  }
  .sw-card-note:focus { outline: none; }
  .sw-card-note::placeholder { color: var(--fg-faint); }

  /* Simple mode hides the app's chrome: there is one view, so there is
     nothing to switch between and nothing to search across. */
  body[data-simple="on"] #filter-bar,
  body[data-simple="on"] #deskviews,
  /* Nothing to refresh: there is no calendar behind the board. Hidden in CSS
     as well as in setSimpleMode(), so it is gone on the first paint rather
     than after the config request lands. The button is only the manual
     trigger - the background refreshes are timers and are untouched, and
     switching back to full mode brings it straight back. */
  body[data-simple="on"] #refresh { display: none !important; }
  /* Settings, in simple mode.

     The category list used to be hidden here, because simple mode had one
     settings page and a list of one is furniture. It has three now, and
     hiding the list meant two of them could not be opened at all: on a
     computer the sidebar is the only way to change panel, and on a phone the
     list IS the page. So the list stays, and simple mode gets the same
     two-level Settings as the full app.

     What does go is the search box. Six panels of calendar, class, reminder
     and view settings are worth searching; three named pages are not, and the
     index behind it points at full-mode sections that are not on this side. */
  #settings-page[data-simple="on"] .settings-find { display: none; }


  /* A search result you can open.

     Results used to be lines of text: they told you the thing existed and left
     you to go and find it. The row is a button now - an assignment opens its
     day, a class opens its own page - so it looks like text and behaves like a
     link, which is what it always claimed to be. */
  .sr-hit {
    display: block; width: 100%; text-align: left;
    background: none; border: none; padding: 0; margin: 0;
    font: inherit; color: inherit; cursor: pointer;
  }
  .sr-hit:hover { color: var(--accent); }
  .sr-class {
    font: inherit; font-size: 0.72rem; font-weight: 700;
    background: none; border: 1px solid var(--border); border-radius: 999px;
    padding: 0.05rem 0.45rem; margin-left: 0.35rem;
    color: var(--fg-muted); cursor: pointer; white-space: nowrap;
  }
  .sr-class:hover { border-color: var(--accent); color: var(--accent); }
  .class-page-head h2 { margin: 0; }
  .class-page-head .cal-sub-help { margin: 0.4rem 0 0; }

  .vieworder-list { margin: 0.2rem 0 0.7rem; }
  .vieworder-row {
    display: flex; align-items: center; gap: 0.6rem;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 10px; padding: 0.6rem 0.7rem; margin-bottom: 0.4rem;
    color: var(--fg); font-size: 0.95rem;
    touch-action: none;      /* a drag here is a drag, not a page scroll */
  }
  .vieworder-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .vieworder-grip {
    cursor: grab; color: var(--fg-muted); font-size: 1.1rem;
    /* The grip is the handle, and on a phone it has to be thumb-sized even
       though the glyph is not. */
    padding: 0.3rem 0.45rem; margin: -0.3rem -0.15rem -0.3rem -0.35rem;
  }
  .vieworder-row.dragging {
    border-color: var(--accent); opacity: 0.9;
    box-shadow: 0 6px 18px rgba(0,0,0,0.25);
  }
  .vieworder-row.dragging .vieworder-grip { cursor: grabbing; }
  .vieworder-name { flex: 1 1 auto; }

  .week-chips {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 0.4rem 0.5rem; margin: 0.75rem 0;
  }

  /* ===== Card sections ===== */
  .card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 0.85rem 1rem;
    margin: 0.75rem 0;
  }
  .card h2 { margin-top: 0.25rem; }
  .card h2:first-child { margin-top: 0; }
  .card ul li:last-child { border-bottom: none; }
  .card .sched-note { margin: 0.5rem 0; }

  /* ===== Checkable assignments ===== */
  .check {
    position: relative;
    flex: 0 0 auto;
    width: 22px; height: 22px;
    border: 2px solid var(--border);
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    display: flex; align-items: center; justify-content: center;
    transition: all 0.15s;
  }
  /* A 22px circle is a 22px target, and on a phone that is a miss more often
     than it is a hit — and a miss lands on the row, which opens the note
     editor. So the checkbox "doesn't work": something happens, just not that.
     Computer mode had a 2.4rem minimum; the touch device, which is the one
     that needs it, had nothing.
     The hit area grows, the circle doesn't, and the row's height is unchanged,
     because a pseudo-element takes no space. It is positioned, so it also sits
     above the row text in hit-testing — which is the half that matters. */
  .check::before {
    content: '';
    position: absolute;
    inset: -12px -8px;
    border-radius: 50%;
  }
  body[data-layout="phone"] .check { width: 26px; height: 26px; }
  /* ===== Anything a finger has to find =====

     tools/probe_hits.mjs measures every control on Home, Week and Month in a
     real browser. Nothing on any of them turned out to be covered by anything
     else - what it found instead was size. On a phone, 33 of Month's 34
     controls and 21 of Week's 22 are under 44px in their smaller dimension. A
     star is 18x16. An edit pencil is 24x20. That is the whole of "I can't
     click on some part of the buttons": there is not much of them to click,
     and a miss lands on the row behind, which does something else.

     Same trick as .check::before above: the hit area grows, the drawing does
     not. An absolutely-positioned pseudo-element takes no space in the
     layout, so no row gets taller and nothing moves - it extends the target
     out past the glyph and sits above the row in hit-testing, which is the
     half that matters.

     ::after and not ::before, because .check uses ::before and .month-cell's
     test marker uses ::after; neither is in this list. */
  .item-star, .item-edit, .item-defer, .month-arrow, .home-nav-arrow,
  .group-seg-btn, .quiet-show, .info-chip, .dh-chip,
  .cta-btn, .quicklink, .ghost-link {
    position: relative;
  }
  /* .hu-x is absent on purpose, twice over. It is already position:
     absolute, so re-declaring it relative would move the dismiss cross into
     the middle of the banner - and it floats over the right-hand end of the
     sentence, which is itself a button, so a 44px target on it takes clicks
     that were meant to open the day. It has generous padding instead. */
  .item-star::after, .item-edit::after, .item-defer::after,
  .month-arrow::after, .home-nav-arrow::after, .group-seg-btn::after,
  .quiet-show::after, .info-chip::after, .dh-chip::after,
  .cta-btn::after, .quicklink::after, .ghost-link::after {
    content: '';
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: 100%; height: 100%;
    min-width: 44px; min-height: 44px;
  }
  /* The reorder arrows get NO invisible box, and that is the point.

     They had one, and tools/probe_hits.mjs found what it did: 44px of
     invisible target on a 21px glyph reaches sideways into the Edit button
     immediately to its right, so the left edge of Edit belonged to the arrow
     above it. "Sometimes I can't click Edit" - and the reason a control on a
     crowded row cannot simply be given a bigger target.

     They are drawn bigger instead, which is honest about the space they take
     and cannot overlap anything. */
  .todo-reorder .todo-move { padding: 0.3rem 0.55rem; font-size: 0.72rem; }

  body[data-layout="phone"] .check.done::after { font-size: 1rem; }
  .check.done {
    background: var(--accent);
    border-color: var(--accent);
  }
  .check.done::after {
    content: '✓';
    color: var(--bg);
    font-size: 0.875rem;
    font-weight: 700;
  }
  li.done .item-text {
    text-decoration: line-through;
    color: var(--fg-faint);
  }
  li.done .badge-test { opacity: 0.5; }
  .item-text { flex: 1; }
  .item-text-row { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }

  /* ===== Buttons ===== */
  .icon-btn {
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 12px;
    width: 44px; height: 44px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; flex: 0 0 auto;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.15s, transform 0.15s;
  }
  .icon-btn:active { transform: scale(0.94); }
  .icon-btn.spinning svg { animation: spin 0.8s linear infinite; }
  @keyframes spin { to { transform: rotate(360deg); } }

  footer {
    margin-top: 2.5rem; padding-bottom: 1rem;
    color: var(--fg-very-faint); font-size: 0.7rem; text-align: center;
  }
  footer a { color: var(--fg-faint); text-decoration: none; }

  /* ===== Dialog ===== */
  dialog {
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 16px;
    padding: 1.5rem; width: min(92vw, 400px);
    max-height: 85vh; overflow-y: auto;
  }
  dialog::backdrop { background: rgba(0,0,0,0.6); backdrop-filter: blur(4px); }
  dialog h3 { margin: 0 0 1rem; font-size: 1.125rem; }
  dialog h4 {
    margin: 1.5rem 0 0.5rem; font-size: 0.7rem;
    text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--fg-muted); font-weight: 600;
  }
  dialog h4:first-of-type { margin-top: 0.5rem; }
  dialog label {
    display: block; margin-bottom: 1rem;
    font-size: 0.875rem; color: var(--fg-muted);
  }
  dialog input[type="password"],
  dialog input[type="text"],
  dialog input[type="time"],
  dialog select {
    width: 100%; margin-top: 0.4rem;
    background: var(--bg-input); color: var(--fg);
    border: 1px solid var(--border); border-radius: 8px;
    padding: 0.6rem 0.75rem; font-size: 1rem;
    font-family: inherit;
  }
  dialog input:focus, dialog select:focus { outline: none; border-color: var(--accent); }
  .actions {
    display: flex; gap: 0.5rem; justify-content: flex-end;
    margin-top: 1.5rem;
  }
  .btn {
    background: var(--bg-input); color: var(--fg);
    border: 1px solid var(--border); border-radius: 8px;
    padding: 0.5rem 1rem; font-size: 0.875rem;
    cursor: pointer; font-family: inherit;
  }
  .btn-primary {
    background: var(--accent); color: var(--bg); border-color: var(--accent);
    font-weight: 600;
  }
  .btn.danger-text { color: var(--danger); }
  .btn:active { transform: scale(0.97); }

  /* ===== Schedules (in settings dialog) ===== */
  .sched-row {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border-soft);
  }
  .sched-row:last-of-type { border-bottom: none; }
  .sched-main { flex: 1; min-width: 0; }
  .sched-time { font-size: 0.95rem; font-weight: 600; font-variant-numeric: tabular-nums; }
  .sched-meta { font-size: 0.7rem; color: var(--fg-faint); margin-top: 0.1rem; }
  .sched-mode {
    display: inline-block;
    background: var(--bg-input); color: var(--accent);
    font-size: 0.55rem; font-weight: 700;
    letter-spacing: 0.06em;
    padding: 0.1rem 0.35rem;
    border-radius: 3px;
    text-transform: uppercase;
    margin-right: 0.3rem;
  }
  .sched-mode.afternoon { color: var(--warn); }
  .sched-disabled .sched-time,
  .sched-disabled .sched-meta { opacity: 0.4; }

  .toggle {
    position: relative; width: 38px; height: 22px;
    background: var(--toggle-off); border-radius: 999px;
    cursor: pointer; flex: 0 0 auto;
    transition: background 0.2s;
  }
  .toggle.on { background: var(--accent); }
  .toggle::after {
    content: ''; position: absolute;
    top: 2px; left: 2px;
    width: 18px; height: 18px;
    background: var(--toggle-knob); border-radius: 50%;
    transition: left 0.2s;
  }
  .toggle.on::after { left: 18px; }

  .small-btn {
    background: var(--bg-input); color: var(--fg-muted);
    border: 1px solid var(--border); border-radius: 8px;
    width: 30px; height: 30px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; font-size: 0.85rem;
    -webkit-tap-highlight-color: transparent;
  }

  .add-sched {
    width: 100%; margin-top: 0.5rem;
    background: transparent; color: var(--accent);
    border: 1px dashed var(--border); border-radius: 10px;
    padding: 0.6rem; cursor: pointer; font-size: 0.85rem;
  }
  .add-sched:active { background: var(--bg-input); }

  /* ===== Theme picker ===== */
  .theme-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
  .theme-swatch {
    flex: 1; min-width: 70px;
    padding: 0.6rem 0.4rem;
    border: 2px solid var(--border);
    border-radius: 14px;
    background: var(--bg-input);
    color: var(--fg);
    cursor: pointer;
    text-align: center;
    font-size: 0.75rem; font-weight: 500;
    font-family: inherit;
  }
  .theme-swatch.on { border-color: var(--accent); }
  .swatch-dot {
    display: inline-block; width: 16px; height: 16px;
    border-radius: 50%; vertical-align: middle;
    margin-right: 0.4rem;
    border: 1px solid rgba(255,255,255,0.1);
  }

  .day-row { display: flex; gap: 0.35rem; margin-top: 0.4rem; }
  .day-btn {
    flex: 1; padding: 0.5rem 0;
    background: var(--bg-input); color: var(--fg-muted);
    border: 1px solid var(--border); border-radius: 8px;
    font-size: 0.75rem; font-weight: 600;
    cursor: pointer; text-align: center;
    font-family: inherit;
  }
  .day-btn.on {
    background: var(--accent); color: var(--bg);
    border-color: var(--accent);
  }

  /* ===== Logo + title ===== */
  .title-row {
    display: flex; align-items: center; gap: 0.5rem;
    font-size: 1.25rem; font-weight: 700;
    letter-spacing: -0.01em;
    margin-bottom: 0.5rem;
  }
  .logo-sm {
    width: 24px; height: 24px;
    color: var(--accent);
    flex: 0 0 auto;
  }

  /* ===== Dropdown menu ===== */
  .header-actions {
    display: flex; gap: 0.5rem; flex: 0 0 auto; align-items: center;
  }
  /* Account avatar + dropdown (Google-style) */
  .user-menu { position: relative; }
  .user-avatar {
    display: inline-flex; align-items: center; gap: 9px; height: 44px;
    padding: 4px 16px 4px 4px; border-radius: 12px;
    border: 1px solid var(--border); background: var(--bg-card); color: var(--fg);
    cursor: pointer; font-family: inherit; font-weight: 600; font-size: 0.95rem; flex: 0 0 auto;
  }
  .user-avatar:hover { background: var(--bg-input); }
  .ua-pic {
    width: 36px; height: 36px; border-radius: 50%; overflow: hidden; flex: 0 0 auto;
    background: #6b7280; color: #fff; display: inline-flex; align-items: center;
    justify-content: center; font-weight: 700; font-size: 0.95rem;
  }
  .ua-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .ua-label { white-space: nowrap; }
  .ua-chev { color: var(--fg-muted); font-size: 1rem; line-height: 1; }
  .user-av-fallback { line-height: 1; }
  .user-dropdown {
    position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
    min-width: 220px; background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 14px; box-shadow: 0 12px 32px rgba(0,0,0,.35); padding: 8px;
  }
  .user-dd-head { display: flex; align-items: center; gap: 10px; padding: 8px 8px 12px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
  .user-dd-av { width: 38px; height: 38px; border-radius: 50%; overflow: hidden; background: #6b7280; color: #fff; display: inline-flex; align-items: center; justify-content: center; font-weight: 700; flex: 0 0 auto; }
  .user-dd-av img { width: 100%; height: 100%; object-fit: cover; }
  .user-dd-id { min-width: 0; }
  .user-dd-name { font-weight: 600; font-size: 0.92rem; color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .user-dd-email { font-size: 0.8rem; color: var(--fg-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .user-dd-item {
    display: block; width: 100%; text-align: left; background: none; border: none;
    color: var(--fg); font-size: 0.9rem; font-family: inherit; padding: 0.6rem 0.7rem;
    border-radius: 9px; cursor: pointer;
  }
  .user-dd-item:hover { background: var(--bg-input); }
  .user-dd-logout { color: var(--danger); }
  .menu-wrap { position: relative; }
  .menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    min-width: 220px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
    padding: 0.5rem 0;
    z-index: 50;
  }
  .menu-label {
    font-size: 0.65rem; font-weight: 700;
    letter-spacing: 0.1em;
    color: var(--fg-muted);
    text-transform: uppercase;
    padding: 0.4rem 0.85rem 0.3rem;
  }
  .menu-check {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.5rem 0.85rem;
    font-size: 0.9rem;
    color: var(--fg);
    cursor: pointer;
    margin: 0;
  }
  .menu-check input {
    width: 18px; height: 18px;
    accent-color: var(--accent);
    cursor: pointer;
    margin: 0;
  }
  .menu-check:active { background: var(--bg-input); }
  .menu-item {
    display: flex; align-items: center; gap: 0.6rem;
    width: 100%;
    padding: 0.55rem 0.85rem;
    font-size: 0.9rem;
    color: var(--fg);
    background: none;
    border: none;
    text-align: left;
    cursor: pointer;
    font-family: inherit;
  }
  .menu-item:active { background: var(--bg-input); }
  .menu-item svg { color: var(--fg-muted); flex: 0 0 auto; }
  .menu-divider {
    border-top: 1px solid var(--border-soft);
    margin: 0.4rem 0;
  }

  /* ===== Full-screen overlays (password, loading) ===== */
  .screen {
    position: fixed; inset: 0;
    display: none;
    align-items: center; justify-content: center;
    background: var(--bg);
    z-index: 100;
    padding:
      calc(env(safe-area-inset-top, 0px) + 1.5rem)
      1.5rem
      calc(env(safe-area-inset-bottom, 0px) + 1.5rem);
  }
  .screen-content {
    text-align: center;
    max-width: 340px;
    width: 100%;
  }
  .logo-big {
    width: 84px; height: 84px;
    color: var(--accent);
    margin: 0 auto 1.5rem;
    display: block;
  }
  .logo-big.spin { animation: spin 1.5s linear infinite; }
  .app-title {
    font-size: 2rem; font-weight: 700;
    margin: 0 0 0.5rem;
    letter-spacing: -0.02em;
    color: var(--fg);
  }
  .screen-sub {
    color: var(--fg-muted);
    margin: 0 0 1.75rem;
    font-size: 0.95rem;
  }
  #onboard-url,
  #onboard-home-url,
  .auth-input {
    width: 100%;
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 10px;
    padding: 0.85rem 1rem; font-size: 1rem;
    font-family: inherit;
    margin-bottom: 0.6rem;
  }
  #onboard-url:focus,
  #onboard-home-url:focus,
  .auth-input:focus { outline: none; border-color: var(--accent); }
  /* Code-entry inputs (6-digit OTP): roomy, centered, easy to read */
  .auth-code { text-align: center; letter-spacing: 0.35em; font-size: 1.2rem; font-variant-numeric: tabular-nums; }
  /* Password field with a show/hide toggle */
  .pw-wrap { position: relative; }
  .pw-wrap .auth-input { margin-bottom: 0.6rem; }
  .pw-toggle {
    position: absolute; top: 0.55rem; right: 0.5rem;
    background: none; border: none; color: var(--fg-muted);
    font-size: 0.78rem; font-weight: 600; cursor: pointer;
    font-family: inherit; padding: 0.3rem 0.4rem;
    -webkit-tap-highlight-color: transparent;
  }
  .auth-alt { font-size: 0.82rem; margin: 0.75rem 0 0; }
  .auth-alt a { color: var(--accent); font-weight: 600; text-decoration: none; }
  /* Helper text under a new-password field stating the Supabase policy */
  .pw-hint { margin: -0.1rem 0 0.6rem; font-size: 0.78rem; color: var(--fg-muted); }
  /* Password strength meter (red → yellow → green) */
  .pw-strength { margin: -0.15rem 0 0.55rem; }
  .pw-meter { height: 6px; border-radius: 999px; background: var(--border); overflow: hidden; }
  .pw-meter > span { display: block; height: 100%; width: 0; border-radius: 999px; transition: width .2s ease, background .2s ease; }
  .pw-strength-label { font-size: 0.72rem; margin-top: 0.25rem; display: inline-block; font-weight: 600; }
  /* "or" divider + Continue-with-Google button */
  .auth-divider { display: flex; align-items: center; gap: 0.6rem; margin: 1rem 0 0.75rem; color: var(--fg-muted); font-size: 0.8rem; }
  .auth-divider::before, .auth-divider::after { content: ''; flex: 1; height: 1px; background: var(--border); }
  .btn-google {
    display: flex; align-items: center; justify-content: center; gap: 0.6rem;
    width: 100%; padding: 0.85rem; font-size: 1rem; font-weight: 600;
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 10px;
    cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .btn-google:hover { border-color: var(--accent); }
  .btn-google svg { width: 18px; height: 18px; flex-shrink: 0; }
  /* One-time-code button - the same outline look as its neighbours.
     It shipped with a class and no rules at all, so the inline SVG had
     nothing constraining it and grew to fill the button. */
  .btn-otp {
    display: flex; align-items: center; justify-content: center; gap: 0.6rem;
    width: 100%; padding: 0.85rem; margin-bottom: 0.6rem;
    font-size: 1rem; font-weight: 600;
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 10px;
    cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .btn-otp:hover { border-color: var(--accent); }
  .btn-otp svg { width: 18px; height: 18px; flex-shrink: 0; }

  /* Passkey sign-in button — same outline look as Continue-with-Google */
  .btn-passkey {
    display: flex; align-items: center; justify-content: center; gap: 0.6rem;
    width: 100%; padding: 0.85rem; margin-top: 0.6rem; font-size: 1rem; font-weight: 600;
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 10px;
    cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  /* Without this the button is on the login screen of every browser that
     cannot do passkeys - initPasskeyLogin() leaves it hidden AND does not
     attach the click listener there, so it is a button that does nothing. */
  .btn-passkey[hidden] { display: none; }
  .btn-passkey:hover { border-color: var(--accent); }
  .btn-passkey svg { width: 18px; height: 18px; flex-shrink: 0; }
  /* "Last used" chip shown next to ONLY the single most-recent sign-in method.
     Subtle neutral-translucent pill; currentColor text keeps it readable on both
     the green primary button and the card-coloured Google/passkey buttons. */
  .lu-pill {
    display: inline-block; margin-left: 0.5rem; vertical-align: middle;
    font-size: 0.6rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
    padding: 2px 8px; border-radius: 999px; line-height: 1.4;
    background: rgba(128,128,128,0.22); color: currentColor;
  }
  /* Must restate display:none for [hidden] — the rule above would otherwise
     override the UA [hidden] style and force every badge to show. */
  .lu-pill[hidden] { display: none; }
  /* Helper hint under the passkey button (shown only when passkeys are supported) */
  .auth-hint { font-size: 0.78rem; color: var(--fg-muted); margin: 0.1rem 0 0.2rem; line-height: 1.4; }
  /* Passkey list in Settings → Account */
  .pk-row { display: flex; align-items: center; gap: 0.6rem; padding: 0.6rem 0; border-top: 1px solid var(--border); }
  .pk-row:first-child { border-top: 0; }
  .pk-row .pk-meta { flex: 1; min-width: 0; }
  .pk-row .pk-name { font-weight: 600; font-size: 0.92rem; }
  .pk-row .pk-sub { font-size: 0.76rem; color: var(--fg-muted); }
  .pk-row .pk-del { color: var(--danger); border-color: var(--danger); padding: 0.3rem 0.6rem; font-size: 0.8rem; }
  .btn-block {
    display: block; width: 100%;
    padding: 0.85rem; margin-top: 0.75rem;
    font-size: 1rem; font-weight: 600;
  }
  .login-error {
    color: var(--danger);
    margin: 1rem 0 0;
    font-size: 0.875rem;
  }
  /* Soft amber variant — for non-error notices like "passkey dismissed". */
  .login-error.warn { color: #fbbf24; }
  .login-ok {
    color: var(--accent);
    margin: 1rem 0 0;
    font-size: 0.875rem;
  }
  #login-form, #signup-form, #verify-form, #forgot-form, #reset-code-form, #reset-form, #onboard-form { margin: 0; }
  .signup-captcha { margin: 0.7rem 0 0.2rem; min-height: 0; display: flex; justify-content: center; }
  .signup-captcha:empty { display: none; }
  .signup-consent { font-size: 0.74rem; line-height: 1.5; color: var(--fg-muted); text-align: center; margin: 0.7rem 0 0; }
  .signup-consent a { color: var(--accent); }
  .auth-back { display: block; text-align: center; margin: -0.2rem 0 0.7rem; font-size: 0.82rem; color: var(--fg-muted); text-decoration: none; font-weight: 600; }
  .auth-back:hover { color: var(--accent); }
  .auth-back[hidden] { display: none; }

  /* Show only one screen at a time based on body[data-screen]=... */
  body[data-screen="password"] #password-screen { display: flex; }
  body[data-screen="loading"]  #loading-screen  { display: flex; }
  body[data-screen="reset"]    #reset-screen    { display: flex; }
  body[data-screen="autherror"] #auth-error-screen { display: flex; }
  body[data-screen="banned"] #banned-screen { display: flex; }
  body[data-screen="setpw"] #setpw-screen { display: flex; }
  body[data-screen="password"] #main-content,
  body[data-screen="loading"]  #main-content,
  body[data-screen="reset"]    #main-content,
  body[data-screen="banned"]   #main-content,
  body[data-screen="setpw"]    #main-content,
  body[data-screen="autherror"] #main-content {
    visibility: hidden;
  }
  /* Allow the suspended screen to scroll so its actions (appeal box, email us,
     log out) are always reachable on short phone screens. margin:auto keeps the
     card centred when it fits, and lets it scroll from the top when it doesn't. */
  body[data-screen="banned"] #banned-screen { align-items: flex-start; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  #banned-screen .banned-card { max-width: 460px; margin: auto; text-align: center; background: var(--bg-card); border: 1px solid var(--border); border-radius: 18px; padding: 32px 26px; }
  #banned-screen .banned-emoji { font-size: 44px; }
  #banned-screen .banned-reason { margin: 0 0 1rem; padding: 0.85rem 1rem; text-align: left;
    background: var(--bg-input); border-left: 3px solid var(--danger, #dc2626); border-radius: 8px;
    color: var(--fg); font-size: 0.95rem; line-height: 1.5; white-space: pre-wrap; }
  #banned-screen h1 { font-size: 1.5rem; margin: 12px 0 8px; }
  #banned-screen p { color: var(--fg-muted); line-height: 1.6; margin: 0 0 14px; }
  #banned-screen .banned-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 18px; }
  #banned-screen .banned-actions a { display: block; padding: 12px; border-radius: 10px; text-decoration: none; font-weight: 600; }
  #banned-screen .banned-primary { background: var(--accent); color: #fff; }
  #banned-screen .banned-ghost { border: 1px solid var(--border); color: var(--fg); }

  /* ===== Login key reveal (key screen + settings) ===== */
  .key-box {
    display: flex; align-items: center; gap: 0.5rem;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 10px; padding: 0.7rem 0.8rem;
    margin: 0.5rem 0;
  }
  .key-box code {
    flex: 1; min-width: 0;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 0.78rem; line-height: 1.4; word-break: break-all;
    color: var(--accent); text-align: left;
  }
  .copy-btn {
    flex: 0 0 auto;
    background: var(--accent); border: none; color: var(--bg);
    border-radius: 8px; padding: 0.5rem 0.8rem;
    font-weight: 700; cursor: pointer; font-family: inherit;
    font-size: 0.82rem;
    -webkit-tap-highlight-color: transparent;
  }
  .copy-btn.copied { background: var(--info); }
  .key-note { font-size: 0.8rem; color: var(--fg-muted); margin: 0.6rem 0 0; line-height: 1.4; }

  /* ===== Onboarding location step ===== */
  .onboard-skip {
    margin-top: 1rem; font-size: 0.8rem;
    background: none; border: none; color: var(--fg-muted);
    cursor: pointer; font-family: inherit; text-decoration: underline;
  }
  /* Obvious "open the setup guide" link on the iCal-paste step */
  .setup-guide-link {
    display: block; margin-top: 0.9rem; text-align: center;
    padding: 0.6rem 0.8rem;
    font-size: 0.85rem; font-weight: 600;
    color: var(--accent); text-decoration: none;
    border: 1px solid var(--border); border-radius: 10px;
    -webkit-tap-highlight-color: transparent;
  }
  .setup-guide-link:hover { border-color: var(--accent); }
  /* Onboarding: labeled calendar URL field(s) */
  .onboard-cal-field { margin-bottom: 0.9rem; }
  .onboard-field-label {
    display: block; font-size: 0.82rem; font-weight: 700; color: var(--fg-muted);
    margin: 0 0 0.35rem; text-transform: uppercase; letter-spacing: 0.04em;
  }
  /* `display: block` above outranks the browser's own rule for [hidden], which
     is why setting .hidden on one of these did nothing at all. Anything given
     a display here needs this line beside it. */
  .onboard-field-label[hidden] { display: none; }
  .onboard-phone-tip {
    font-size: 0.82rem; line-height: 1.5; color: var(--fg-muted);
    background: var(--bg-input); border: 1px solid var(--border); border-radius: 10px;
    padding: 0.6rem 0.75rem; margin: 0 0 0.7rem;
  }
  .onboard-help-row {
    display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
    font-size: 0.8rem; color: var(--fg-muted); margin: 0 0 0.4rem;
  }
  .onboard-help-row > span:first-child { flex-basis: 100%; }
  .onboard-help-link {
    background: none; border: none; padding: 0; font: inherit; font-size: 0.8rem;
    color: var(--accent); font-weight: 600; cursor: pointer; text-decoration: none;
  }
  .onboard-help-link:hover { text-decoration: underline; }
  .onboard-help-sep { color: var(--fg-muted); }

  /* ===== Onboarding: choose Computer vs Phone mode ===== */
  .layout-choice-row { display: flex; flex-direction: column; gap: 0.7rem; margin: 0.25rem 0 0.5rem; }
  .layout-choice {
    display: flex; flex-direction: column; gap: 0.2rem;
    text-align: left; width: 100%;
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 12px;
    padding: 0.9rem 1rem; cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .layout-choice:hover, .layout-choice.on { border-color: var(--accent); }
  .layout-choice .lc-title { font-weight: 700; font-size: 1rem; }
  .layout-choice .lc-emoji { font-size: 1.3rem; }
  .layout-choice .lc-desc { font-size: 0.82rem; color: var(--fg-muted); }

  /* ===== Settings: layout toggle ===== */
  .layout-toggle-row { display: flex; gap: 0.5rem; margin-top: 0.5rem; }
  .layout-opt {
    flex: 1; background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 10px;
    padding: 0.7rem 0.6rem; cursor: pointer; font-family: inherit;
    font-size: 0.9rem; font-weight: 600;
    -webkit-tap-highlight-color: transparent;
  }
  .layout-opt.on { background: var(--accent); border-color: var(--accent); color: var(--bg); }

  /* ===== Home: dismissible multi-sign-in-method notice ===== */
  .notice-card {
    display: flex; align-items: center; gap: 0.6rem;
    background: var(--bg-card); border: 1px solid var(--accent);
  }
  .notice-card .notice-text { flex: 1; font-size: 0.85rem; color: var(--fg); }
  .notice-card .notice-dismiss {
    flex: 0 0 auto; background: none; border: none; color: var(--fg-muted);
    font-size: 1rem; cursor: pointer; font-family: inherit; line-height: 1;
    padding: 0.2rem 0.35rem; -webkit-tap-highlight-color: transparent;
  }

  /* On a computer the per-row controls (star, note, push) only appear when the
     pointer is on the row. They were competing with the title for width — long
     assignment names pushed them onto a second line — and three faint glyphs on
     every row is clutter you only need when you're reaching for them. */
  @media (hover: hover) {
    body[data-layout="computer"] .assignment-li .item-star,
    body[data-layout="computer"] .assignment-li .item-edit,
    body[data-layout="computer"] .assignment-li .item-defer {
      opacity: 0; transition: opacity 0.12s;
    }
    body[data-layout="computer"] .assignment-li:hover .item-star,
    body[data-layout="computer"] .assignment-li:hover .item-edit,
    body[data-layout="computer"] .assignment-li:hover .item-defer,
    body[data-layout="computer"] .assignment-li:focus-within .item-star,
    body[data-layout="computer"] .assignment-li:focus-within .item-edit,
    body[data-layout="computer"] .assignment-li:focus-within .item-defer {
      opacity: 1;
    }
    /* A starred item keeps its star visible — that's state, not an action. */
    body[data-layout="computer"] .assignment-li .item-star.on { opacity: 1; }
  }

  /* ===== "Do today" suggestions ===== */
  .focus-item.suggested { opacity: 0.95; }
  .focus-why {
    display: block; font-size: 0.72rem; color: var(--fg-muted);
    margin-top: 0.1rem; text-transform: lowercase;
  }
  .focus-item-main { flex: 1 1 auto; min-width: 0; }
  /* When a focus item is due, for the ones that are not due today. Quieter
     than the class pill beside it: it is an answer to a question you only ask
     about some of the lines. */
  .focus-due {
    font-size: 0.72rem; color: var(--fg-muted); white-space: nowrap;
    border: 1px solid var(--border); border-radius: 999px;
    padding: 0.05rem 0.4rem; margin-left: 0.15rem;
  }
  .focus-item > .check { flex: 0 0 auto; align-self: center; }
  /* A task whose timing is still being worked out. It's already in the list and
     fully usable — this only says the date may still fill itself in. */
  .todo-pending .todo-text::after {
    content: 'scheduling…';
    margin-left: 0.5rem; font-size: 0.72rem; color: var(--accent);
    opacity: 0.9; white-space: nowrap;
  }
  .focus-add {
    background: none; border: none; color: var(--fg-muted);
    font-size: 1.05rem; line-height: 1; cursor: pointer;
    padding: 0 0.2rem; font-family: inherit; flex: 0 0 auto;
  }
  .focus-add:hover { color: var(--accent); }

  /* ===== A day opened in place, under the grid it was tapped in =====

     One panel, both layouts, both tabs. There were three of these: a card
     under the month grid, a paler block inside the week card, and the phone's
     bottom sheet - three shells around the same .day-detail, which is what
     let them drift to three different heading sizes and two different close
     buttons.

     It is darker than the page rather than lighter, so it reads as a window
     onto one day rather than as one more card in the stack. Only the shell
     is here; how a day READS is .day-detail. */
  .day-panel {
    display: block;
    /* Darker than whatever it sits on - the page under the month grid, the
       card inside the week list - so it reads as a window onto one day
       rather than as one more block in the stack. --bg-input was not enough
       for that: it is four points lighter than the page, so standing on the
       page it looked like a card. */
    background: color-mix(in srgb, var(--bg) 86%, #000);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 1rem 1.1rem 1.15rem;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03),
                0 14px 34px -24px rgba(0, 0, 0, 0.85);
  }
  /* Under the month grid it is its own block on the page, so it needs the
     break from the grid that a card would have given it. */
  .day-panel-solo { margin-top: 1.5rem; }
  /* Inside the week card it is a block within a card. */
  .day-panel:not(.day-panel-solo) { margin: 0.35rem 0 1.25rem; }
  .day-panel-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 1rem; margin-bottom: 1rem; padding-bottom: 0.85rem;
    border-bottom: 1px solid var(--border-soft);
  }
  .day-panel-titles { min-width: 0; }
  /* A real title. The bare h3 is this app's small uppercase section label,
     which left the day name quieter than the date underneath it. */
  .day-panel .day-panel-title {
    margin: 0; font-size: 1.35rem; font-weight: 800; line-height: 1.15;
    color: var(--fg); text-transform: none; letter-spacing: normal;
  }
  .day-panel-sub {
    font-size: 0.78rem; color: var(--fg-muted); margin-top: 0.25rem;
    text-transform: uppercase; letter-spacing: 0.06em;
  }
  .day-panel-close {
    background: none; border: 1px solid var(--border); color: var(--fg-muted);
    border-radius: 10px; min-width: 44px; min-height: 44px; font-size: 0.9rem;
    display: flex; align-items: center; justify-content: center;
    font-family: inherit; cursor: pointer; flex: 0 0 auto;
    -webkit-tap-highlight-color: transparent;
  }
  .day-panel-close:hover { color: var(--fg); border-color: var(--accent); }
  /* The open day is outlined in the strip so it's clear where the list came
     from. `today` keeps its filled treatment; this is a separate signal. */
  .week-day.selected {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
  }
  /* On a computer the sections run side by side so a day spreads across the
     measure instead of becoming a tall scroll. A grid (not balanced columns)
     keeps them in reading order and never splits one. */
  body[data-layout="computer"] .day-detail--inline {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
    gap: 1.75rem 2.5rem; align-items: start;
  }
  /* Side by side, a section's own bottom margin just pads the tallest column
     and leaves a dead band under the card. The row gap spaces them here. */
  body[data-layout="computer"] .day-detail--inline .day-sec { margin-bottom: 0; }
  body[data-layout="computer"] .day-detail--inline .day-sec-full { grid-column: 1 / -1; }
  body[data-layout="computer"] .day-panel-solo { margin-top: 1.75rem; }

  /* ===== Day header =====
     One block: who you are, one line of context, and the day arrows. */
  .day-head { display: none; }
  body[data-layout="computer"] .day-head {
    display: flex; align-items: flex-start; gap: 1.25rem;
    margin: 0.4rem 0 1.5rem;
  }
  .dh-main { flex: 1 1 auto; min-width: 0; }
  .dh-title {
    margin: 0; font-size: 1.75rem; font-weight: 800;
    letter-spacing: -0.02em; line-height: 1.15; color: var(--fg);
  }
  .dh-sub { margin: 0.3rem 0 0; font-size: 0.95rem; color: var(--fg); opacity: 0.85; }
  .dh-meta {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 0.4rem 0.85rem; margin-top: 0.5rem;
    font-size: 0.88rem; color: var(--fg-muted);
  }
  /* Separators between the plain readouts, but not before the chips — they
     have their own outline and a dot in front would read as a bullet. */
  .dh-meta > span:not(.dh-chip):not(.streak-chip) + span:not(.dh-chip):not(.streak-chip)::before {
    content: '·'; margin-right: 0.85rem; color: var(--fg-faint);
  }
  .dh-chip {
    display: inline-flex; align-items: center; gap: 0.3rem;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 999px; padding: 0.22rem 0.7rem;
    font-size: 0.8rem; color: var(--fg-muted); font-family: inherit;
  }
  .dh-chip b { color: var(--fg); font-weight: 700; }
  .dh-chip-of { opacity: 0.75; }
  .dh-chip b.danger { color: var(--danger); }
  .dh-chip.tap { cursor: pointer; }
  .dh-chip.tap:hover { border-color: var(--accent); color: var(--fg); }
  .dh-nav { display: flex; align-items: center; gap: 0.4rem; flex: 0 0 auto; }
  .dh-today {
    background: none; border: 1px solid var(--border); color: var(--fg-muted);
    border-radius: 9px; padding: 0.35rem 0.7rem; font-size: 0.8rem;
    font-family: inherit; cursor: pointer;
  }
  .dh-today:hover { color: var(--fg); border-color: var(--accent); }

  /* The one thing you're meant to act on gets the full width, above the rest. */
  body[data-layout="computer"] .dash-hero { margin: 0 0 1.35rem; }
  body[data-layout="computer"] .dash-hero > * { margin: 0; }
  body[data-layout="computer"] .dash-hero .focus-list {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: 0.6rem;
  }

  /* ===== Greeting ===== */
  .greet-block { margin: 0.2rem 0 0.9rem; }
  .greet-hi {
    margin: 0; font-size: 1.35rem; font-weight: 800;
    letter-spacing: -0.01em; color: var(--fg);
  }
  .greet-sub { margin: 0.15rem 0 0; font-size: 0.9rem; color: var(--fg-muted); }
  /* Desktop uses .day-head instead; these stay for phone. */
  /* The .home-nav hide is HOME-ONLY. Unscoped it also took out the Week tab's
     week stepper, which has no .day-head standing in for it — so on a computer
     there was no way to change week at all, in either direction. */
  body[data-layout="computer"][data-tab="glance"] #content > .home-nav { display: none; }
  body[data-layout="computer"] .greet-block,
  body[data-layout="computer"] #content > .info-chips,
  body[data-layout="computer"] #content > .stats,
  body[data-layout="computer"] #content > .glance-weather { display: none; }

  /* ===== Onboarding name step ===== */
  .onboard-name-row { display: flex; gap: 0.6rem; }
  .onboard-name-row input {
    flex: 1 1 0; min-width: 0;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 12px; padding: 0.85rem 1rem;
    color: var(--fg); font-size: 1rem; font-family: inherit;
  }
  .onboard-name-row input:focus { outline: none; border-color: var(--accent); }
  @media (max-width: 520px) { .onboard-name-row { flex-direction: column; } }

  /* ===== Inline line-icons (replace the old emoji chrome) ===== */
  .ico {
    width: 1.05em; height: 1.05em; flex: 0 0 auto;
    vertical-align: -0.15em;
  }
  [data-ico] { display: inline-flex; align-items: center; flex: 0 0 auto; }
  .settings-cat-ico { color: var(--fg-muted); }
  .settings-cat-ico .ico { width: 1.15rem; height: 1.15rem; }
  .settings-cat.on .settings-cat-ico { color: var(--accent); }
  .user-dd-item [data-ico],
  .ast-mode [data-ico],
  .layout-opt [data-ico] { margin-right: 0.45rem; color: var(--fg-muted); }
  .user-dd-logout [data-ico] { color: inherit; }

  /* ===== Desktop view switcher (hidden until computer mode) ===== */
  .deskviews { display: none; }

  /* ===== Computer mode (per-device desktop-friendly layout) =====
     Phone mode is the default and is the existing layout, unchanged.
     Computer mode lays the app out for a mouse and a big screen: one calm,
     centred measure and a real two-column dashboard on Home. It used to flow
     every card through CSS multi-column (column-width: 21rem), which read
     top-to-bottom-then-across instead of left-to-right, pulled full-width
     things like the day nav into a narrow column, and shattered into seven
     columns on an ultrawide. Grid keeps document order, never splits a card,
     and holds a fixed, predictable shape. */
  @media (min-width: 760px) {
    /* One container width for the whole app so the header, tabs, content and
       footer share an edge. Reading tabs stay near a comfortable measure; the
       calendar grids get more room because they're grids, not prose. */
    /* One width for every view. Per-tab widths meant the app bar, search and
       footer all resized when you switched tabs — very visible once the bar
       became persistent chrome rather than part of the page.

       That width is now the screen. The cap was 1240px, which on a wide
       monitor left a third of the display empty on each side while the week
       grid - seven columns of a day each - was squeezed into the middle. The
       things this app shows are grids, and a grid wants the room.

       Prose does not, so the two blocks that really are prose keep a measure
       of their own below rather than dragging the whole app back in. */
    body[data-layout="computer"] #main-content {
      max-width: none; margin: 0 auto; width: 100%;
    }
    /* Wider gutters when there is room, so nothing sits against the glass. */
    body[data-layout="computer"] { padding-left: 2rem; padding-right: 2rem; }
    @media (min-width: 1600px) {
      body[data-layout="computer"] { padding-left: 3rem; padding-right: 3rem; }
    }
    /* The reading-shaped things. A paragraph 2,000px wide is one line of text
       your eye cannot get back to the start of. */
    body[data-layout="computer"] .lp-sub,
    body[data-layout="computer"] .settings-panel { max-width: 68rem; }

    /* --- The app bar ---------------------------------------------------
       The phone header stacks brand, greeting, date and weather down the
       left, which on a wide screen is a tall block of text with a mouse
       pointer next to it. Desktop gets one bar instead: brand, the views,
       then the actions — and the day's context moves to a single line
       underneath, where it reads as a subtitle rather than a pile. */
    body[data-layout="computer"] header .row {
      align-items: center; gap: 1.25rem;
      min-height: 56px;
    }
    body[data-layout="computer"] header .row > div:first-child {
      display: flex; align-items: baseline; gap: 0.7rem; flex: 0 0 auto;
    }
    body[data-layout="computer"] .greeting,
    body[data-layout="computer"] .date,
    body[data-layout="computer"] header .weather { display: none; }
    body[data-layout="computer"] .title-row { font-size: 1.02rem; }
    /* The date and temperature moved into the day header, one line below —
       having them in the app bar as well was saying it twice. */
    body[data-layout="computer"] .meta-line { display: none; }
    body[data-layout="computer"] .header-actions { margin-left: 0.75rem; }
    /* Below this the date/weather line is the first thing to go — the tabs and
       search are controls, it's a readout, and it repeats what Home shows. */
    @media (max-width: 980px) {
      body[data-layout="computer"] .title-row span { display: none; }
    }

    /* The tabs must never shrink or wrap — they're the navigation. Search takes
       whatever is left and shrinks first; the date/weather line drops out
       before either. Previously the tabs grew to fill the row while search held
       a fixed 24rem, so at ordinary window widths the field overlapped the last
       tab. Nothing here can overlap now: one flexible item, the rest fixed. */
    body[data-layout="computer"] header .row { flex-wrap: nowrap; min-width: 0; }
    body[data-layout="computer"] .deskviews {
      display: flex; align-items: center; gap: 0.2rem;
      flex: 0 0 auto;
    }
    .deskview {
      background: none; border: none; font-family: inherit;
      color: var(--fg-muted); font-size: 0.92rem; font-weight: 600;
      padding: 0.42rem 0.85rem; border-radius: 8px;
      cursor: pointer; white-space: nowrap;
      transition: background 0.12s, color 0.12s;
    }
    .deskview:hover { color: var(--fg); background: var(--bg-input); }
    .deskview.on { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }

    /* The <select> is the phone control; the tabs above replace it here.
       Search keeps the row to itself and stops looking like an afterthought. */
    /* The <select> is the phone control; the tabs above replace it here.
       The search box is moved into this row by placeSearchBox(), so the bar
       sheds the sticky full-width chrome it needs on a phone. */
    body[data-layout="computer"] .filter-select { display: none; }
    body[data-layout="computer"] header .filter-bar {
      margin: 0; padding: 0; border: none; background: none;
      position: static;
      /* Take the whole gap between the weather block and the header buttons.
         It used to cap at 24rem and hug the buttons, so on a wide screen it
         read as a leftover slot rather than the search for the whole app. */
      /* basis 0, not auto: with `auto` the box sized itself from its content
         and simply sat there instead of taking the free space. */
      flex: 1 1 0%; min-width: 14rem; max-width: none;
      margin: 0 1.5rem;
    }
    /* The input's own 38% basis is the phone rule; here it fills its bar. */
    body[data-layout="computer"] header .filter-bar > .filter-search {
      flex: 1 1 100%;
    }
    body[data-layout="computer"] header .filter-search {
      width: 100%; font-size: 0.95rem;
      padding: 0.62rem 0.95rem 0.62rem 2.45rem;
      border-radius: 10px;
      background: var(--bg-input) no-repeat 0.75rem center;
      background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2' stroke-linecap='round'><circle cx='11' cy='11' r='7'/><path d='m20 20-3.5-3.5'/></svg>");
      background-size: 16px 16px;
    }
    body[data-layout="computer"] header .filter-search:focus {
      border-color: var(--accent); outline: none;
    }

    /* --- The day / week stepper ---
       On a phone this spans the screen, so the arrows land under both thumbs.
       On a desktop that same rule threw them ~1500px apart with the label
       marooned between them. Group them into one cluster instead. */
    body[data-layout="computer"] .home-nav {
      justify-content: flex-start; gap: 0.55rem;
      margin-bottom: 1.1rem;
    }
    body[data-layout="computer"] .home-nav-mid {
      flex: 0 0 auto; text-align: left; align-items: flex-start;
      order: -1; margin-right: 0.35rem;
    }
    body[data-layout="computer"] .home-nav-label { font-size: 1.15rem; }
    body[data-layout="computer"] .home-nav-arrow {
      width: 34px; height: 34px; font-size: 1.1rem; border-radius: 9px;
    }

    /* --- Home: the day's content in a main column, glanceable cards beside it.
       Two equal auto-flowing columns were tried first and looked ragged: grid
       rows align, so a short card next to a tall one left a large hole. Two
       explicit columns each stack their own cards, so there are no holes. --- */
    /* "Do today" is the one card here you actually act on, and its rows carry
       real assignment titles, so it gets half again the width of the three
       readouts beside it. Below this the strip wraps and they even out. */
    @media (min-width: 1180px) {
    }
    /* The day's content: two columns, each card kept whole. */
    /* Your schedule on the left, your work on the right. The work column is
       wider because its rows carry badges and per-item controls, which wrapped
       onto a second line at equal widths. */
    /* Two equal columns, filled by balanceHomeColumns() rather than by a fixed
       rule, so neither side is left short. Below 1000px it collapses to one. */
    body[data-layout="computer"] .dash-cols.is-split {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 0 1.25rem;
      align-items: start;
    }
    body[data-layout="computer"] .dash-col > * { margin: 0 0 1.25rem; }
    body[data-layout="computer"] .dash-col > :last-child { margin-bottom: 0; }

    /* The stepper sat hard against the app bar's underline. Give it air. */
    body[data-layout="computer"] .home-nav { margin-top: 0.55rem; }
    body[data-layout="computer"] #content { padding-top: 0.35rem; }
    /* Below the two-column threshold .dash-cols is a plain block and the two
       halves simply stack, so no card is ever lost at an in-between width. */

    /* --- Lists stay a single readable column --- */
    /* Tests, days off and search results are lists. Two columns of list makes
       them harder to scan, not easier, so they get one centred column. */
    body[data-layout="computer"][data-tab="off"]    #content,
    body[data-layout="computer"][data-tab="tests"]  #content,
    body[data-layout="computer"][data-tab="search"] #content {
      max-width: 780px; margin: 0 auto;
    }
    /* The same rhythm, one step roomier on a computer. */
  body[data-layout="computer"] #content > * { margin-bottom: 1.15rem; }
  body[data-layout="computer"] #content > :last-child { margin-bottom: 0; }

    /* The summary tiles are flex:1, so across the full desktop measure they
       stretch into three enormous blocks. Cap them and let the row start left. */
    body[data-layout="computer"] .stats { gap: 0.75rem; }
    body[data-layout="computer"] .stat { flex: 1 1 0; max-width: 250px; }

    /* The header bleeds 1.25rem past the container so it runs edge-to-edge on a
       phone. In a centred desktop container that just leaves its rule sticking
       out past the cards, so drop the bleed — the text lands in the same place. */
    body[data-layout="computer"] header { margin: 0; padding-left: 0; padding-right: 0; }

    body[data-layout="computer"] .month-cell { min-height: 80px; padding: 0.35rem 0.45rem; }
    body[data-layout="computer"] .month-num { font-size: 0.85rem; }
    body[data-layout="computer"] .month-dow { font-size: 0.68rem; padding-bottom: 0.45rem; }

    body[data-layout="computer"] .tab { padding: 0.6rem 1.25rem; font-size: 0.95rem; }
    body[data-layout="computer"] .card { padding: 1.05rem 1.25rem; }
    /* No min-height. This was meant to make the checkbox easier to hit with
       a mouse and instead stretched it: 22px wide by 38 tall, a bar rather
       than a box, in every assignment row on a computer. The hit area is
       .check::before's job, and it does it without changing the drawing. */
    /* A mouse pointer doesn't need phone-sized touch rows, and six 47px rows
       make a short class list look like a wall. Slightly tighter by default —
       Display → Compact still goes tighter, since its rules come later. */
    body[data-layout="computer"] #content li { padding-top: 0.55rem; padding-bottom: 0.55rem; }

    /* ===== Computer-mode To-dos ===== */
    body[data-layout="computer"][data-tab="todos"] #content {
      max-width: 1040px; margin: 0 auto;
    }
    body[data-layout="computer"] .todos-page .todo-input-row input[type="text"] {
      font-size: 1.2rem; padding: 0.95rem 1.15rem;
    }
    body[data-layout="computer"] .todo-sections {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(26rem, 1fr));
      gap: 1.1rem;
      align-items: start;
      margin-top: 0.25rem;
    }
    body[data-layout="computer"] .todo-section { margin: 0; }

    /* The little Apple-style pull-down line is a touch affordance — phone only. */
    body[data-layout="computer"] .sheet-grip { display: none; }
  }

  /* ===== Full-page Settings (route: /settings) ===== */
  .settings-page {
    position: fixed; inset: 0;
    background: var(--bg);
    z-index: 90;
    display: none;
    flex-direction: column;
  }
  body[data-view="settings"] .settings-page#settings-page { display: flex; }
  body[data-view="settings"] #main-content { visibility: hidden; }
  /* Feedback + Help pages (routes /feedback and /help) — reuse the .settings-page overlay. */
  body[data-view="feedback"] .settings-page#feedback-page { display: flex; }
  body[data-view="feedback"] #main-content { visibility: hidden; }
  body[data-view="help"] .settings-page#help-page { display: flex; }
  body[data-view="help"] #main-content { visibility: hidden; }
  .feedback-card { max-width: 520px; margin: 0.5rem auto; width: 100%; }
  .feedback-title { text-align: center; font-size: 1.25rem; font-weight: 800; color: var(--fg); margin: 0.9rem 0 0.4rem; }
  .feedback-subtitle { text-align: center; font-size: 0.92rem; color: var(--fg-muted); margin: 0 0 1rem; }
  .fb-textarea {
    width: 100%; box-sizing: border-box; min-height: 130px; resize: vertical;
    margin: 0.9rem 0; padding: 0.7rem 0.8rem; border-radius: 10px;
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    font-family: inherit; font-size: 0.95rem; line-height: 1.5;
  }
  .settings-topbar {
    position: sticky; top: 0;
    background: var(--bg);
    border-bottom: 1px solid var(--border-soft);
    display: flex; align-items: center; gap: 0.75rem;
    padding:
      calc(env(safe-area-inset-top, 0px) + 0.75rem)
      1.25rem
      0.75rem;
    z-index: 2;
  }
  .settings-back {
    background: var(--bg-card);
    border: 1px solid var(--border);
    color: var(--fg);
    border-radius: 8px;
    width: 36px; height: 36px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    font-size: 1.2rem;
    flex: 0 0 auto;
    -webkit-tap-highlight-color: transparent;
  }
  .settings-back:active { opacity: 0.6; }
  .settings-topbar h1 {
    font-size: 1.2rem; font-weight: 700; margin: 0;
  }
  /* ===== Two-level Settings (category nav + panels) ===== */
  /* Find a setting by what you'd call it, not by which of six sections it
     turned out to live in. */
  /* Inside the category nav, not beside it: on a computer .settings-layout is a
     two-column grid, and a third child of it took the nav's cell and pushed the
     nav into the panel's - so the box was on screen nowhere anyone would find
     it, which is a poor showing for a thing whose whole job is being found. */
  .settings-find { position: relative; margin-bottom: 0.35rem; }
  .settings-find-input {
    width: 100%; padding: 0.6rem 0.8rem;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 12px; color: var(--fg); font: inherit; font-size: 1rem;
  }
  .settings-find-input:focus { outline: none; border-color: var(--accent); }
  .settings-find-results {
    position: absolute; left: 0; right: 0; top: calc(100% + 0.3rem); z-index: 5;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 12px; box-shadow: 0 12px 30px rgba(0,0,0,0.35);
    overflow: hidden;
  }
  .settings-find-results[hidden] { display: none; }
  .settings-find-hit {
    display: flex; align-items: baseline; justify-content: space-between; gap: 0.7rem;
    width: 100%; text-align: left; font: inherit; cursor: pointer;
    background: none; border: none; border-bottom: 1px solid var(--border-soft);
    color: var(--fg); padding: 0.55rem 0.8rem;
  }
  .settings-find-hit:last-child { border-bottom: none; }
  .settings-find-hit:hover { background: var(--bg-input); }

  /* Ten seconds to change your mind about a delete. */
  /* Bottom LEFT, deliberately: the chat button, its panel and the tips bar all
     live in the bottom right, and a fourth thing in that corner is a corner
     nobody reads. */
  .undo-bar {
    position: fixed; left: 18px; transform: none;
    bottom: 18px; z-index: 1398;
    display: flex; align-items: center; gap: 0.8rem;
    max-width: min(520px, calc(100vw - 24px));
    padding: 0.6rem 0.7rem 0.6rem 0.95rem;
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 999px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.32); font-size: 0.9rem;
  }
  .undo-bar[hidden] { display: none; }
  .undo-bar .undo-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .undo-bar .undo-go {
    flex: 0 0 auto; font: inherit; font-weight: 700; cursor: pointer;
    background: var(--accent); border: 1px solid var(--accent); color: #fff;
    border-radius: 999px; padding: 0.3rem 0.9rem; font-size: 0.85rem;
  }

  .settings-cats { display: flex; flex-direction: column; gap: 0.5rem; }
  .settings-cat {
    display: flex; align-items: center; gap: 0.75rem;
    width: 100%; text-align: left; font-family: inherit;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 14px; padding: 0.85rem 1rem;
    color: var(--fg); font-size: 0.98rem; font-weight: 600;
    cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  /* `display: flex` above outranks the browser's own rule for [hidden], so
     without this every row shows in both modes - which is what "Account,
     Calendar, About, Account, Calendar, Display..." was. simpleSettingsOnly()
     sets .hidden on the rows the other mode owns and nothing was listening. */
  .settings-cat[hidden] { display: none; }
  .settings-cat:active { opacity: 0.7; }
  .settings-cat.on { border-color: var(--accent); }
  .settings-cat-ico { font-size: 1.15rem; flex: 0 0 auto; }
  .settings-cat-label { flex: 1; }
  .settings-cat-chev { color: var(--fg-muted); font-size: 1.2rem; flex: 0 0 auto; }
  .settings-panel { display: none; }
  .about-version { font-size: 0.9rem; color: var(--fg-muted); }
  /* Phone / default: push–pop. Tap a category → its panel replaces the list. */
  body:not([data-layout="computer"]) .settings-page.panel-open .settings-cats { display: none; }
  body:not([data-layout="computer"]) .settings-page.panel-open .settings-panel.active { display: block; }
  /* Computer: persistent left sidebar + panel beside it. */
  body[data-layout="computer"] .settings-layout {
    display: grid; grid-template-columns: 240px minmax(0, 1fr);
    gap: 1.25rem; align-items: start;
  }
  /* Settings follows the app: full width, with the panel itself keeping a
     readable measure (it is mostly labelled rows and explanations). */
  body[data-layout="computer"] .settings-body,
  body[data-layout="computer"] .settings-topbar {
    max-width: none; margin-left: auto; margin-right: auto; width: 100%;
  }
  body[data-layout="computer"] .settings-cats { position: sticky; top: 0; }
  /* The search box belongs inside the nav. If a CACHED index.html still has it
     as a direct child of this grid, it takes the nav's cell and shoves the nav
     into the panel's - which is what a stale shell looked like for one load
     after the fix. The three shell files can be at different versions (see the
     note in sw.js), so the new CSS has to survive the old markup. */
  body[data-layout="computer"] .settings-layout > .settings-find { grid-column: 1 / -1; }
  body[data-layout="computer"] .settings-cat-chev { display: none; }
  body[data-layout="computer"] .settings-panel.active { display: block; }
  /* Email-only mode (reached via the "Email preferences" link in emails):
     show just the Emails panel, no category nav. */
  /* Simple mode used to collapse this to one column, because it had one
     panel and no nav. It has three of each now and uses the ordinary grid;
     the panel just reads at a narrower measure, since these pages are short
     and a 68rem line of explanation is a line nobody finishes. */
  body[data-layout="computer"] .settings-page[data-simple="on"] .settings-panel {
    max-width: 46rem;
  }

  .settings-page[data-email-only] .settings-cats { display: none; }
  .settings-page[data-email-only] .settings-layout { display: block; }
  .settings-page[data-email-only] .settings-panel { display: none !important; }
  .settings-page[data-email-only] .settings-panel[data-panel="emails"] { display: block !important; }
  .settings-credit { text-align: center; font-size: 0.72rem; color: var(--fg-muted); opacity: 0.7; margin: 1.5rem 0 0.5rem; }
  /* Help & Feedback section: stacked on phone, side-by-side on computer. */
  .help-fb-btns { display: flex; flex-direction: column; gap: 0.6rem; }
  body[data-layout="computer"] .help-fb-btns { flex-direction: row; }
  body[data-layout="computer"] .help-fb-btns > .btn { flex: 1; }
  /* Numbered iPhone "Add to Home Screen" steps */
  .ios-steps { margin: 0.45rem 0; padding-left: 1.25rem; display: flex; flex-direction: column; gap: 0.28rem; }
  .ios-steps li { font-size: 0.85rem; line-height: 1.35; }
  /* Unified notifications grid (Email / Phone per reminder) */
  .notif-grid { display: flex; flex-direction: column; }
  .notif-row { display: grid; grid-template-columns: 1fr 44px 44px; align-items: center; gap: 6px; padding: 9px 0; border-top: 1px solid var(--border); }
  .notif-row.notif-head { border-top: 0; padding: 2px 0 4px; font-size: 0.72rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--fg-muted); }
  .notif-row.notif-head span { text-align: center; }
  .notif-row.notif-head span:first-child { text-align: left; font-weight: 600; }
  .notif-label { display: flex; flex-direction: column; gap: 1px; font-size: 0.92rem; font-weight: 600; }
  .notif-label small { font-weight: 400; font-size: 0.74rem; color: var(--fg-muted); }
  .notif-cell { display: flex; align-items: center; justify-content: center; margin: 0; }
  .notif-cell input { width: 18px; height: 18px; cursor: pointer; }
  .notif-cell input:disabled { opacity: 0.35; cursor: not-allowed; }
  .notif-na { color: var(--fg-muted); opacity: 0.5; font-weight: 400; }
  .notif-subrow { padding: 0 0 8px 6px; }
  .notif-subrow .sub-pref { margin: 0; }
  .help-fb-emails { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.92rem; }
  .help-fb-emails a { color: var(--accent); text-decoration: none; }
  .help-fb-emails a:hover { text-decoration: underline; }
  .hf-label { display: inline-block; min-width: 4.5rem; color: var(--fg-muted); font-weight: 600; }
  .settings-body {
    flex: 1;
    overflow-y: auto;
    padding: 1.25rem 1.25rem
      calc(env(safe-area-inset-bottom, 0px) + 2rem);
  }
  /* Arriving at a setting: go to it, and say which one. Two seconds of the
     accent behind it, because a page that has just scrolled somewhere still
     has to answer "to what". */
  @keyframes settingFlash {
    from { background: color-mix(in srgb, var(--accent) 26%, transparent); }
    to   { background: transparent; }
  }
  .setting-flash {
    animation: settingFlash 2.2s ease-out;
    border-radius: 12px;
    scroll-margin-top: 1rem;
  }

  .settings-section {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 1rem 1.1rem;
    margin-bottom: 1rem;
  }
  /* Category labels that group related setting cards (B7) */
  .settings-group {
    font-size: 0.95rem; font-weight: 800; color: var(--fg);
    margin: 1.6rem 0 0.6rem; padding: 0 0.15rem;
  }
  .settings-group:first-child { margin-top: 0; }
  .settings-group.danger { color: var(--danger); }
  /* A question that only matters while the switch above it is on. The rule
     it hangs off is drawn rather than indented with margin alone, so it reads
     as belonging to that switch and not as a third setting. */
  .menu-check-sub {
    margin: 0.5rem 0 0 0.35rem; padding-left: 0.85rem;
    border-left: 2px solid var(--border);
  }
  .menu-check-sub[hidden] { display: none; }

  .settings-divider { border: none; border-top: 1px solid var(--border); margin: 0.9rem 0; }
  .settings-section.danger-zone { border-color: var(--danger); }
  .settings-section > h2 {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--fg-muted);
    margin: 0 0 0.75rem;
    font-weight: 600;
  }
  .settings-section .menu-check {
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border-soft);
    font-size: 0.95rem;
  }
  .settings-section .menu-check:last-of-type { border-bottom: none; }
  /* Indented sub-preference under another toggle (e.g. "Skip weekend reminders"). */
  .menu-check.sub-pref { margin-left: 1.5rem; font-size: 0.9rem; }

  /* ===== Email preferences accordion ===== */
  .email-acc {
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 14px; margin-bottom: 0.7rem; overflow: hidden;
  }
  .email-acc > summary {
    list-style: none; cursor: pointer; -webkit-tap-highlight-color: transparent;
    display: flex; align-items: center; justify-content: space-between;
    padding: 0.9rem 1.1rem; font-size: 0.95rem; font-weight: 700; color: var(--fg);
  }
  .email-acc > summary::-webkit-details-marker { display: none; }
  .email-acc-caret { color: var(--fg-muted); font-size: 1.05rem; transition: transform 0.15s ease; }
  .email-acc[open] > summary .email-acc-caret { transform: rotate(180deg); }
  .email-acc-body { padding: 0 1.1rem 1rem; }
  .email-acc-body .menu-check:last-of-type { border-bottom: none; }
  /* Grouped sub-headers inside the Email types list */
  .email-group-title {
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--accent);
    margin: 1.1rem 0 0.35rem; padding-top: 0.9rem;
    border-top: 1px solid var(--border);
  }
  .email-acc-body > .email-group-title:first-child { margin-top: 0; padding-top: 0; border-top: none; }
  /* ===== Notifications, split by channel =====
     It was one grid: reminders down the side, Email and Phone as two columns
     of checkboxes, and a dash wherever a reminder only worked one way. You had
     to decode a matrix to answer "what am I actually going to get". Now each
     channel is its own list, and a reminder that only works one way simply
     lives in the list that supports it. */
  .notif-summary-card { background: var(--bg-input); }
  .notif-summary { margin: 0; font-size: 0.95rem; line-height: 1.5; color: var(--fg); }
  .notif-summary b { color: var(--accent); }

  .nrow {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.5rem 0.75rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border-soft);
  }
  .nrow:last-child { border-bottom: none; }
  .nrow-main { margin: 0; min-width: 0; }
  .nrow-main small { display: block; color: var(--fg-muted); font-size: 0.8rem; margin-top: 0.1rem; }
  .nrow-side { display: flex; align-items: center; gap: 0.45rem; flex: 0 0 auto; }
  .nrow-lead { width: auto; min-width: 8.5rem; margin: 0; }
  .nrow-test { padding: 0.35rem 0.7rem; font-size: 0.82rem; width: auto; }
  .nrow-help, .nrow-sub { grid-column: 1 / -1; margin: 0; }
  .nrow-sub { padding-left: 1.6rem; }
  /* A row whose channel is switched off reads as unavailable rather than
     merely unticked. */
  .nrow-main input:disabled + span { opacity: 0.45; }

  /* Narrow screens: the row cannot be two columns.
     "Test reminders" plus a lead-time select plus a Send-one button is about
     360px of content, and the label column is the only one that can give - so
     on a phone it was wrapping to one word per line beside a full-width
     select. Below 620px the controls drop to their own line underneath the
     thing they belong to, which is the only arrangement where the label gets
     the whole width. */
  @media (max-width: 620px) {
    .nrow {
      grid-template-columns: 1fr;
      gap: 0.45rem;
      padding: 0.75rem 0;
    }
    .nrow-side { grid-column: 1 / -1; padding-left: 1.6rem; flex-wrap: wrap; }
    .nrow-lead { flex: 1 1 9rem; min-width: 0; }
    .nrow-test { flex: 0 0 auto; }
    .nrow-main small { font-size: 0.82rem; }
  }

  .notif-device {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.7rem; padding: 0.5rem 0; border-bottom: 1px solid var(--border-soft);
    font-size: 0.9rem; color: var(--fg);
  }
  .notif-device:last-child { border-bottom: none; }
  .notif-device .nd-when { color: var(--fg-muted); font-size: 0.8rem; }
  .notif-device .nd-this { color: var(--accent); font-weight: 700; font-size: 0.78rem; }

  /* Pausing. Three buttons rather than one switch, in the red the rest of the
     panel already uses for "this stops something". A paused channel fills in,
     so the state is the button rather than a sentence under it. */
  .pause-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.15rem 0 0.6rem; }
  .pause-btn {
    flex: 1 1 8rem; width: auto; padding: 0.55rem 0.7rem; font-size: 0.88rem;
    color: var(--danger); border-color: var(--danger); background: none;
  }
  .pause-btn:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }
  .pause-btn[aria-pressed="true"] {
    background: var(--danger); border-color: var(--danger); color: #fff; font-weight: 700;
  }
  .pause-btn[disabled] { opacity: 0.4; cursor: default; }
  #pause-state { margin-top: 0; }
  #pause-state.is-paused { color: var(--danger); font-weight: 600; }
  @media (max-width: 420px) { .pause-btn { flex: 1 1 100%; } }

  /* Quiet hours. The only thing this app does that can wake somebody up. */
  .quiet-hours { margin-top: 0.8rem; }
  .quiet-row {
    display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
    margin: 0.5rem 0 0; font-size: 0.9rem; color: var(--fg-muted);
  }
  .quiet-row input[type="time"] {
    font: inherit; font-size: 1rem; color: var(--fg);
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 9px; padding: 0.35rem 0.55rem;
  }

  .summer-pause-note {
    background: color-mix(in srgb, var(--accent) 12%, var(--bg-card));
    border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
    border-radius: 12px; padding: 0.7rem 0.9rem; margin: 0 0 0.9rem;
    font-size: 0.85rem; line-height: 1.5; color: var(--fg);
  }
  .summer-pause-note b { font-weight: 600; }
  /* Indented container for a sub-preference that depends on the toggle above it */
  .email-subgroup {
    margin: 0.1rem 0 0.2rem 0.85rem;
    padding-left: 0.85rem;
    border-left: 2px solid var(--border);
  }
  .email-subgroup .cal-sub-help { margin-bottom: 0.2rem; }
  .email-field-label { display: block; font-size: 0.85rem; color: var(--fg-muted); font-weight: 600; margin: 0.6rem 0 0.35rem; }
  .email-select {
    width: 100%; padding: 0.6rem 0.7rem; border-radius: 10px;
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    font-family: inherit; font-size: 0.92rem;
  }
  .email-select:disabled { opacity: 0.5; cursor: not-allowed; }
  .email-test-btns { display: flex; flex-direction: column; gap: 0.5rem; }
  .email-test-btn:disabled { opacity: 0.6; }
  /* Small toast for email actions */
  .app-toast {
    /* Above the home indicator on an iPhone, not under it. */
    position: fixed; left: 50%;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 28px);
    transform: translateX(-50%) translateY(12px);
    background: var(--bg-card); color: var(--fg); border: 1px solid var(--border);
    border-radius: 10px; padding: 0.7rem 1.1rem; font-size: 0.9rem; font-weight: 600;
    box-shadow: 0 10px 30px rgba(0,0,0,0.35); z-index: 400; max-width: 90vw; text-align: center;
    opacity: 0; pointer-events: none; transition: opacity 0.2s ease, transform 0.2s ease;
  }
  .app-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
  .app-toast.err { border-color: var(--danger); color: var(--danger); }

  /* ===== New feature styles ===== */
  /* Subject colors + grouping */
  .subject-dot {
    display: inline-block; width: 10px; height: 10px; border-radius: 50%;
    margin-right: 0.4rem; vertical-align: middle; flex: 0 0 auto;
  }
  .subject-h {
    display: flex; align-items: center;
    font-size: 1rem; font-weight: 700; color: var(--fg);
    margin: 0 0 0.4rem;
  }
  .subject-count {
    margin-left: 0.4rem; font-size: 0.75rem; font-weight: 600;
    color: var(--fg-muted);
  }

  /* ===== Grouped list view — breathing room + wide-screen columns (#1) ===== */
  /* Each day / subject is its own block with clear separation between groups. */
  .list-groups { margin-top: 0.4rem; }
  .day-group, .list-group {
    margin: 0 0 1.4rem;
    padding-bottom: 1.1rem;
    border-bottom: 1px solid var(--border-soft);
  }
  .day-group:last-child, .list-group:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
  .day-group .day-h { margin-top: 0; }

  /* #53 — roomier Week view so groups don't feel cramped */
  body[data-tab="week"] #content > .card { padding: 1.25rem 1.3rem 1.35rem; }
  body[data-tab="week"] #content > .card > h2 { margin-bottom: 1rem; }
  body[data-tab="week"] .subject-h { margin: 0 0 0.8rem; }
  body[data-tab="week"] .list-group { margin-bottom: 2rem; padding-bottom: 1.6rem; }
  body[data-tab="week"] .list-group .assignment-li { padding-top: 0.6rem; padding-bottom: 0.6rem; }
  body[data-tab="week"] .more-toggle,
  body[data-tab="week"] .done-toggle { margin-top: 0.75rem; padding: 0.5rem 0.1rem; }
  /* Computer + Week view is otherwise empty on the right — lay the day/subject
     groups out in a grid so they read left-to-right and each group stays whole. */
  @media (min-width: 760px) {
    body[data-layout="computer"][data-tab="week"] .list-groups {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr));
      gap: 1.5rem 2.25rem;
      align-items: start;
    }
    body[data-layout="computer"][data-tab="week"] .day-group,
    body[data-layout="computer"][data-tab="week"] .list-group {
      border-bottom: none; padding-bottom: 0; margin: 0;
    }
  }
  .class-li { padding-left: 0.6rem; display: flex; align-items: center; gap: 0.4rem; }
  .class-li .class-main { flex: 1; min-width: 0; }

  /* ===== Declutter feature styles (Batch 3) ===== */
  /* D8 — star toggle */
  .item-star {
    flex: 0 0 auto; background: none; border: none;
    color: var(--fg-faint); font-size: 1rem; line-height: 1;
    cursor: pointer; padding: 0 0.15rem; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .item-star.on { color: #eab308; }
  .item-star:active { opacity: 0.6; }
  .assignment-li.starred { background: color-mix(in srgb, #eab308 8%, transparent); }

  /* D1/D2 — collapse toggles */
  .more-toggle, .done-toggle {
    background: none; border: none; color: var(--accent);
    font-size: 0.8rem; font-weight: 600; cursor: pointer;
    font-family: inherit; padding: 0.35rem 0.1rem; margin-top: 0.1rem;
    -webkit-tap-highlight-color: transparent;
  }
  .done-toggle { color: var(--fg-muted); }
  .more-toggle:active, .done-toggle:active { opacity: 0.6; }
  .done-list { opacity: 0.7; }

  /* D5 — combined class-per-day rows */
  .combo-list { list-style: none; padding: 0; margin: 0; }
  .combo-row { padding-left: 0.5rem; margin: 0.3rem 0; }
  .combo-head {
    width: 100%; text-align: left; background: none; border: none;
    color: var(--fg); font-family: inherit; font-size: 0.95rem; font-weight: 600;
    cursor: pointer; display: flex; align-items: center; gap: 0.1rem;
    padding: 0.45rem 0; -webkit-tap-highlight-color: transparent;
  }
  .combo-count { color: var(--fg-muted); font-weight: 500; font-size: 0.8rem; margin-left: 0.1rem; }
  .combo-caret { margin-left: auto; color: var(--fg-muted); }

  /* D10 — quiet weekend banner */
  .quiet-banner { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
  .quiet-show {
    flex: 0 0 auto; background: var(--accent); color: var(--bg);
    border: none; border-radius: 8px; padding: 0.4rem 0.8rem;
    font-weight: 700; cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .quiet-show:active { opacity: 0.8; }

  /* D9 — today's focus */
  .focus-card { border-color: var(--accent); }
  .focus-head { display: flex; align-items: center; justify-content: space-between; }
  .focus-head h2 { margin: 0; }
  .focus-edit {
    background: none; border: 1px solid var(--border); color: var(--fg);
    border-radius: 999px; padding: 0.2rem 0.7rem; font-size: 0.78rem;
    cursor: pointer; font-family: inherit; -webkit-tap-highlight-color: transparent;
  }
  .focus-empty { font-size: 0.85rem; color: var(--fg-muted); margin: 0.5rem 0 0; }
  .focus-list { list-style: none; padding: 0; margin: 0.6rem 0 0; }
  .focus-item {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.5rem 0.6rem; margin: 0.35rem 0;
    background: var(--bg-input); border-radius: 8px; font-weight: 600;
  }
  .focus-item.done .focus-item-text { text-decoration: line-through; color: var(--fg-muted); }
  .focus-item-text { flex: 1; min-width: 0; }
  .focus-rm {
    flex: 0 0 auto; background: none; border: none; color: var(--fg-faint);
    font-size: 0.95rem; cursor: pointer; font-family: inherit; padding: 0 0.2rem;
    -webkit-tap-highlight-color: transparent;
  }
  .focus-rm:hover { color: var(--danger); }
  .focus-pick-list { list-style: none; padding: 0; margin: 0.5rem 0 0; }
  /* Where today's work ends and the undated to-dos begin. Without it the
     picker reads as eight things all due today. */
  .focus-pick-div {
    list-style: none; margin: 0.6rem 0 0.2rem;
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--fg-muted);
  }
  .focus-pick-btn {
    width: 100%; text-align: left; background: var(--bg-input);
    border: 1px solid var(--border); color: var(--fg); border-radius: 8px;
    padding: 0.6rem 0.7rem; margin: 0.3rem 0; cursor: pointer;
    font-family: inherit; font-size: 0.92rem;
    -webkit-tap-highlight-color: transparent;
  }
  .focus-pick.on .focus-pick-btn { border-color: var(--accent); color: var(--accent); }
  .focus-pick-btn:disabled { opacity: 0.4; cursor: default; }

  /* D7 — compact density (independent of phone/computer mode). Tighter, but
     never collapses to elements touching: keeps min paddings, gaps & margins. */
  body[data-density="compact"] .card { padding: 0.7rem 0.85rem; margin: 0.55rem 0; }
  body[data-density="compact"] .assignment-li { padding: 0.4rem 0; }
  body[data-density="compact"] li { padding-top: 0.4rem; padding-bottom: 0.4rem; }
  body[data-density="compact"] .item-summary { font-size: 0.9rem; }
  body[data-density="compact"] h2 { font-size: 1rem; margin-bottom: 0.35rem; }
  body[data-density="compact"] .stats { margin: 0.5rem 0; gap: 0.4rem; }
  body[data-density="compact"] .week-cal { gap: 0.35rem; margin: 0.8rem 0 0.5rem; }
  body[data-density="compact"] .home-nav { margin-bottom: 0.6rem; }
  body[data-density="compact"] .combo-row,
  body[data-density="compact"] .focus-item { margin: 0.3rem 0; }

  /* ===== One vertical rhythm for the page =====
     Every top-level block in #content carries the same gap BELOW it and none
     above, so nothing has to know what it happens to sit next to. This replaces
     a pile of per-element margins that each guessed at their neighbour — which
     is how a control ended up 3px from the card under it, and how an opened day
     ended up flush against the week strip. Anything that needs a different gap
     opts out below, on purpose and with a reason. */
  #content > * { margin-top: 0; margin-bottom: 1rem; }
  #content > :last-child { margin-bottom: 0; }
  /* The colour legend is the strip's caption, so it belongs with the strip
     rather than floating a full step away from it. */
  #content > .week-cal { margin-bottom: 0.45rem; }
  /* These three used to re-set a gap the original rules had already set, a
     couple of thousand lines further up — so the value you read there was not
     the value on screen. The numbers moved to the rules themselves. */
  .more-toggle, .done-toggle { margin: 0.25rem 0 0.1rem; }

  /* Priority / urgency */
  .urgent-pill {
    display: inline-block; font-size: 0.6rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.04em;
    padding: 0.1rem 0.4rem; border-radius: 4px;
    background: var(--danger); color: #fff; margin-right: 0.35rem;
    vertical-align: 1px;
  }
  .urgent-pill.soon { background: var(--warn); }
  /* Late. Deliberately not the same red as "due today": one is a deadline you
     can still make and the other is one you have already missed, and a reader
     scanning a list needs to be able to tell those apart at a glance. */
  .urgent-pill.late {
    background: none; color: var(--danger);
    border: 1px solid var(--danger); padding: 0.05rem 0.35rem;
  }
  li.urgent .item-summary { font-weight: 600; }

  /* A card's header line: its title on the left, whatever controls that card on
     the right. Wraps rather than squashing the title on a narrow screen. */
  .card-head-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.75rem 1rem; flex-wrap: wrap; margin-bottom: 0.35rem;
  }
  .card-head-row h2 { margin: 0; }

  /* Group By-day / By-class segmented control.
     Outline only, no fill: it sits on a card now, and a filled pill on a card
     reads as a second card. A control should look like a control. */
  .group-seg {
    display: inline-flex; align-items: center; gap: 0.3rem;
    background: transparent; border: 1px solid var(--border);
    border-radius: 999px; padding: 0.2rem 0.3rem 0.2rem 0.7rem;
  }
  .group-seg-label { font-size: 0.72rem; font-weight: 700; color: var(--fg-muted);
    text-transform: uppercase; letter-spacing: 0.05em; margin-right: 0.15rem; }
  .group-seg-btn {
    background: none; border: none; color: var(--fg-muted);
    border-radius: 999px; padding: 0.32rem 0.7rem; font-size: 0.8rem; font-weight: 600;
    cursor: pointer; font-family: inherit; -webkit-tap-highlight-color: transparent;
    transition: background 0.12s ease, color 0.12s ease;
  }
  .group-seg-btn.on { background: var(--accent); color: var(--bg); }
  .group-seg-btn:not(.on):hover { color: var(--fg); }

  /* Quick links */
  .quicklinks { display: flex; flex-wrap: wrap; gap: 0.5rem; }
  .quicklink {
    display: inline-block; text-decoration: none;
    background: var(--bg-input); color: var(--accent);
    border: 1px solid var(--border); border-radius: 999px;
    padding: 0.45rem 0.85rem; font-size: 0.85rem; font-weight: 600;
    -webkit-tap-highlight-color: transparent;
  }
  .quicklink:active { transform: scale(0.97); }
  .ql-add-row { display: flex; gap: 0.4rem; margin-top: 0.6rem; flex-wrap: wrap; }
  .ql-add-row input {
    flex: 1; min-width: 6rem;
    background: var(--bg-input); border: 1px solid var(--border);
    color: var(--fg); border-radius: 8px; padding: 0.5rem 0.65rem;
    font-size: 0.9rem; font-family: inherit;
  }
  .ql-row {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.5rem 0; border-bottom: 1px solid var(--border-soft);
  }
  .ql-row:last-child { border-bottom: none; }
  .ql-row-label { font-weight: 600; font-size: 0.9rem; flex: 0 0 auto; }
  .ql-row-url {
    flex: 1; min-width: 0; font-size: 0.78rem; color: var(--fg-muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .ql-remove {
    flex: 0 0 auto; background: none; border: 1px solid var(--border);
    color: var(--danger); border-radius: 6px; width: 28px; height: 28px;
    cursor: pointer; font-family: inherit;
  }

  /* Offline banner. Two states, because "you have no connection" and "the
     server didn't answer" are different problems and only one of them is the
     user's to do anything about. Tapping either retries. */
  .offline-banner {
    margin: 0.5rem 0 0; padding: 0.5rem 0.75rem;
    background: var(--test-bg); color: var(--test-fg);
    border-radius: 8px; font-size: 0.8rem; font-weight: 600;
    text-align: center; cursor: pointer;
  }
  .offline-banner.is-stale {
    background: var(--bg-input); color: var(--fg-muted);
    border: 1px solid var(--border); font-weight: 500;
  }

  /* Glance weather strip */
  .glance-weather { display: flex; align-items: center; gap: 0.75rem; }
  .gw-emoji { font-size: 2.2rem; line-height: 1; }
  .gw-temp { font-size: 2rem; font-weight: 700; }
  .gw-meta { font-size: 0.8rem; color: var(--fg-muted); }

  /* Home day/week navigation (B12) */
  .home-nav {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.5rem; margin: 0 0 0.9rem;
  }
  .home-nav-arrow {
    background: var(--bg-card); border: 1px solid var(--border);
    color: var(--fg); border-radius: 10px;
    width: 42px; height: 42px; flex: 0 0 auto;
    font-size: 1.4rem; line-height: 1; cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .home-nav-arrow:active { opacity: 0.6; }
  .home-nav-arrow:disabled { opacity: 0.3; cursor: default; }
  .home-nav-mid {
    flex: 1; min-width: 0; text-align: center;
    display: flex; flex-direction: column; align-items: center; gap: 0.1rem;
  }
  .home-nav-label { font-size: 1rem; font-weight: 700; }
  .home-nav-today {
    background: none; border: none; color: var(--accent);
    font-size: 0.75rem; cursor: pointer; font-family: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .home-nav-today:active { opacity: 0.6; }

  /* Month calendar */
  .month-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.6rem; }
  .month-title { font-size: 1rem; font-weight: 700; }
  .month-arrow {
    background: var(--bg-input); border: 1px solid var(--border); color: var(--fg);
    border-radius: 8px; width: 36px; height: 36px; font-size: 1.2rem;
    cursor: pointer; font-family: inherit; -webkit-tap-highlight-color: transparent;
  }
  .month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
  .month-dow {
    text-align: center; font-size: 0.6rem; font-weight: 700; color: var(--fg-muted);
    text-transform: uppercase; padding-bottom: 0.2rem;
  }
  .month-cell {
    position: relative; min-height: 40px; border-radius: 8px;
    background: var(--bg-input); border: 1px solid var(--border-soft);
    padding: 0.2rem; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .month-cell.empty { background: none; border: none; cursor: default; }
  /* A month cell is a tap target seven-across on a phone: 43px wide is as
     wide as the screen allows, so the height is where the 44px comes from. */
  body[data-layout="phone"] .month-cell { min-height: 46px; }
  .month-cell.today { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
  .month-cell.has-items { background: var(--bg-card); }
  /* Two signals, not one — the same split the week strip uses. Today is always
     marked, and the day you OPENED is marked more strongly, so the box moves to
     whichever day you tap. It used to be one outline, permanently on today, and
     nothing on screen said which day the list underneath belonged to. */
  .month-cell.selected {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
  }
  .month-num { font-size: 0.75rem; font-variant-numeric: tabular-nums; }
  .month-cell.today .month-num { color: var(--accent); font-weight: 700; }
  .month-cell.selected .month-num { color: var(--accent); font-weight: 700; }
  .month-dots { display: flex; gap: 2px; flex-wrap: wrap; margin-top: 0.15rem; }
  .mdot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
  .month-cell.has-test::after {
    content: ''; position: absolute; top: 4px; right: 4px;
    width: 6px; height: 6px; border-radius: 50%; background: var(--danger);
  }
  .month-hint { font-size: 0.72rem; color: var(--fg-muted); margin: 0.6rem 0 0; }
  /* Calendar color legend (Week / Month) */
  .cal-legend-wrap { margin-top: 0.7rem; }
  .cal-legend-toggle {
    font: inherit; font-size: 0.78rem; font-weight: 600;
    background: none; border: none; padding: 0; cursor: pointer;
    color: var(--fg-muted);
  }
  .cal-legend-toggle:hover { color: var(--accent); }
  .cal-legend-wrap .cal-legend { margin-top: 0.45rem; }
  .cal-legend { display: flex; flex-wrap: wrap; gap: 0.5rem 0.9rem; margin: 0.7rem 0 0; }
  .cal-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.78rem; color: var(--fg-muted); }
  .cal-legend-dot { width: 9px; height: 9px; border-radius: 2px; display: inline-block; flex: 0 0 auto; }
  .cal-legend-dot.round { border-radius: 50%; }
  .cal-legend-ic { font-size: 0.9rem; line-height: 1; }

/* ---------------------------------------------------------------- */

  body[data-screen="landing"] #landing-screen { display: block; }
  body[data-screen="landing"] #main-content { visibility: hidden; }
  #landing-screen {
    position: fixed; inset: 0; display: none; overflow-y: auto; z-index: 100;
    background:
      radial-gradient(1100px 540px at 78% -8%, rgba(34,197,94,0.13), transparent 60%),
      radial-gradient(900px 520px at 8% 4%, rgba(59,130,246,0.10), transparent 55%),
      var(--bg);
    color: var(--fg); -webkit-overflow-scrolling: touch;
  }
  .lp { max-width: 1080px; margin: 0 auto; padding: 0 22px; }
  /* Nav */
  .lp-nav { position: sticky; top: 0; z-index: 5; backdrop-filter: blur(10px);
    background: color-mix(in srgb, var(--bg) 82%, transparent); border-bottom: 1px solid var(--border); }
  .lp-nav-in { max-width: 1080px; margin: 0 auto; padding: 13px 22px; display: flex; align-items: center; justify-content: space-between; }
  .lp-brand { display: flex; align-items: center; gap: 9px; font-weight: 800; font-size: 1.06rem; letter-spacing: -0.01em; }
  .lp-brand .lp-sun { font-size: 1.25rem; }
  .lp-nav-actions { display: flex; align-items: center; gap: 9px; }
  .lp-btn { display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    font-weight: 700; font-size: 0.95rem; border-radius: 11px; padding: 11px 20px; cursor: pointer;
    border: 1px solid transparent; text-decoration: none; transition: transform .07s ease, filter .15s ease, background .15s ease; white-space: nowrap; }
  .lp-btn:active { transform: translateY(1px); }
  .lp-btn-primary { background: var(--accent); color: #fff; }
  .lp-btn-primary:hover { filter: brightness(1.08); }
  .lp-btn-ghost { background: transparent; color: var(--fg); border-color: var(--border); }
  .lp-btn-ghost:hover { background: var(--bg-card); }
  .lp-btn-lg { padding: 14px 26px; font-size: 1.02rem; }
  /* Hero */
  /* One screen.
     The hero was 64px of padding above a headline sized off the viewport
     width, so on a laptop the buttons sat below the fold and the picture of
     the app - the only thing on the page that answers "what is this" - was
     half cut off. It is sized against the viewport HEIGHT now: the nav, the
     headline, the buttons and the whole screenshot inside 100vh, with the
     scroll hint underneath it. */
  .lp-hero {
    display: grid; grid-template-columns: 1.05fr 0.95fr;
    gap: 40px; align-items: center;
    padding: clamp(18px, 4vh, 56px) 0 clamp(14px, 3vh, 30px);
    min-height: min(620px, calc(100vh - 210px));
  }
  .lp-eyebrow { display: inline-flex; align-items: center; gap: 7px; font-size: 0.8rem; font-weight: 700;
    color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); padding: 5px 12px; border-radius: 999px; }
  .lp-h1 { font-size: clamp(1.9rem, min(5vw, 6.2vh), 3.3rem); line-height: 1.05; letter-spacing: -0.03em; font-weight: 800; margin: 18px 0 0; }
  .lp-h1 .lp-grad { background: linear-gradient(110deg, #4ade80, #22c55e 50%, #38bdf8); -webkit-background-clip: text; background-clip: text; color: transparent; }
  .lp-lead { font-size: 1.14rem; line-height: 1.6; color: var(--fg-muted); margin: 18px 0 0; max-width: 33ch; }
  .lp-hero-cta { display: flex; gap: 12px; margin-top: 28px; flex-wrap: wrap; }
  .lp-microcopy { margin-top: 14px; font-size: 0.85rem; color: var(--fg-muted); }
  .lp-microcopy b { color: var(--fg); }
  .lp-row { display: flex; align-items: center; gap: 10px; padding: 11px 12px; border-radius: 11px; background: var(--bg-input); border: 1px solid var(--border-soft); margin-bottom: 8px; }
  .lp-row .lp-cdot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
  .lp-row .lp-rtext { font-size: 0.9rem; }
  .lp-row .lp-rsub { font-size: 0.74rem; color: var(--fg-muted); margin-top: 1px; }
  .lp-row .lp-chk { margin-left: auto; width: 17px; height: 17px; border-radius: 5px; border: 1.5px solid var(--border); flex: 0 0 auto; }
  .lp-row.done .lp-rtext { text-decoration: line-through; color: var(--fg-muted); }
  .lp-row.done .lp-chk { background: var(--accent); border-color: var(--accent); }
  .lp-pill { display: inline-block; font-size: 0.66rem; font-weight: 700; padding: 2px 7px; border-radius: 999px; margin-left: 6px; vertical-align: middle; }
  .lp-pill.test { background: #7f1d1d; color: #fecaca; }
  /* ===== The app, on the landing page =====

     The page used to show a stylised sketch: three dots, four grey rows, a
     couple of coloured circles. It said "this is an app" and nothing else, and
     it looked like every generated landing page there has ever been.

     These are the app's own shapes at the app's own sizes - the day header,
     the class colour down the side of a row, the test badge, the fortnight
     bars, the assistant's proposal button, a notification with its two
     actions. Built in HTML rather than photographed, so they cannot go stale
     against a redesign and they stay sharp on any screen. The names in them
     are invented; nothing else is. */
  .lp-shot {
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 16px; overflow: hidden;
    box-shadow: 0 24px 60px -28px rgba(0,0,0,0.55);
    text-align: left;
  }
  .lp-shot-bar {
    display: flex; align-items: center; gap: 6px;
    padding: 10px 13px; border-bottom: 1px solid var(--border);
    background: var(--bg-input);
  }
  .lp-shot-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--border); }
  .lp-shot-url {
    margin-left: 10px; font-size: 0.72rem; color: var(--fg-muted);
    background: var(--bg); border-radius: 6px; padding: 3px 10px;
  }
  .lp-shot-body { padding: 14px; }
  /* A screenshot fills its frame. Taken at 2x and set to its CSS size, so it
     stays sharp on a retina screen without the page reserving twice the room
     for it - and width/height are on the tag so nothing reflows as it loads. */
  .lp-shot > img { display: block; width: 100%; height: auto; }
  /* The hero screenshot is a tall phone, and the hero has to fit a laptop
     screen. It is cropped from the top rather than scaled down: the top of
     the app is the part that answers "what is this", and shrinking the whole
     phone to fit would make every word in it unreadable. */
  .lp-shot-hero > img {
    height: min(520px, 58vh); object-fit: cover; object-position: top;
  }
  @media (max-width: 860px) { .lp-shot-hero > img { height: auto; } }
  /* The crop lands wherever the viewport height puts it, which is often
     halfway through a line of text - and a sentence sliced in half reads as a
     rendering fault rather than as a screenshot that continues. A fade at the
     bottom edge says "there is more of this" instead. */
  .lp-shot-hero { position: relative; }
  .lp-shot-hero::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 64px;
    background: linear-gradient(to bottom, transparent, var(--bg-card));
    pointer-events: none;
  }
  @media (max-width: 860px) { .lp-shot-hero::after { display: none; } }
  /* The photographed figures crop to one height, so the three read as a row
     rather than as three unrelated objects. */
  .lp-shotfig > img {
    display: block; width: 100%; height: 290px;
    object-fit: cover; object-position: top;
    border: 1px solid var(--border); border-radius: 14px;
    box-shadow: 0 20px 48px -26px rgba(0,0,0,0.55);
  }
  @media (max-width: 860px) { .lp-shotfig > img { height: auto; } }
  .lp-shot-hero { pointer-events: none; }

  .lp-app-head { margin-bottom: 12px; }
  .lp-app-title { font-size: 1.05rem; font-weight: 800; }
  .lp-app-meta { font-size: 0.76rem; color: var(--fg-muted); margin-top: 3px; }
  .lp-app-meta b { color: var(--fg); }
  .lp-red { color: #f87171 !important; }

  .lp-panel {
    background: var(--bg-input); border: 1px solid var(--border-soft);
    border-radius: 12px; padding: 11px 12px; margin-bottom: 10px;
  }
  .lp-panel:last-child { margin-bottom: 0; }
  .lp-panel-h {
    font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--fg-muted); margin-bottom: 8px;
  }
  .lp-panel-note { font-size: 0.75rem; color: var(--fg-muted); margin-top: 9px; }
  .lp-panel .lp-row { background: var(--bg-card); }
  .lp-panel .lp-row:last-child { margin-bottom: 0; }

  /* The fortnight chart on the landing page is a real screenshot now
     (docs/lp/fortnight.png), so the CSS that drew a fake one is gone. It used
     `height: var(--h)` with no fallback and nothing ever set --h, which is
     what it looked like from the outside: three-pixel stubs. */
  /* Three screens, side by side. */
  .lp-shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 28px; }
  /* The figures crop to one height too, so the same fade applies: a card cut
     in half at the bottom edge reads as a fault, a fade reads as "continues". */
  .lp-shotfig { margin: 0; position: relative; }
  .lp-shotfig::after {
    content: ''; position: absolute; left: 0; right: 0; top: 234px; height: 56px;
    background: linear-gradient(to bottom, transparent, var(--bg));
    border-radius: 0 0 14px 14px; pointer-events: none;
  }
  @media (max-width: 860px) { .lp-shotfig::after { display: none; } }
  .lp-shotfig .lp-shot { height: 290px; display: flex; flex-direction: column; }
  .lp-shotfig .lp-shot-body { flex: 1 1 auto; overflow: hidden; }
  .lp-shotfig figcaption {
    font-size: 0.87rem; color: var(--fg-muted); line-height: 1.5; margin-top: 12px;
  }
  .lp-shotfig figcaption b { color: var(--fg); }

  /* The assistant, mid-conversation. */
  .lp-chat { display: flex; flex-direction: column; gap: 7px; }
  .lp-msg {
    font-size: 0.8rem; line-height: 1.45; border-radius: 12px;
    padding: 8px 11px; max-width: 92%;
  }
  .lp-msg.me { align-self: flex-end; background: var(--accent); color: #fff; }
  .lp-msg.ai { align-self: flex-start; background: var(--bg-input); border: 1px solid var(--border-soft); }
  .lp-msg.lp-ok { color: var(--accent); font-weight: 600; }
  .lp-undo { color: var(--fg-muted); font-weight: 400; text-decoration: underline; }
  .lp-do {
    display: block; margin-top: 8px; width: 100%;
    background: var(--accent); color: #fff; border: none; border-radius: 8px;
    padding: 7px 10px; font: inherit; font-size: 0.78rem; font-weight: 700;
  }

  /* The board photo, and what it turned into. */
  .lp-board-note {
    background: var(--bg-input); border: 1px dashed var(--border);
    border-radius: 12px; padding: 11px 12px; text-align: center;
  }
  .lp-board-cam { font-size: 1.15rem; }
  .lp-board-lines { display: flex; flex-direction: column; gap: 5px; align-items: center; margin: 7px 0 6px; }
  .lp-board-lines span { display: block; height: 5px; border-radius: 3px; background: var(--border); }
  .lp-board-cap { font-size: 0.68rem; color: var(--fg-faint); }
  .lp-arrow { text-align: center; color: var(--fg-muted); font-size: 0.95rem; margin: 6px 0; }

  /* Notifications, with the buttons that make them worth having. */
  .lp-notif {
    background: var(--bg-input); border: 1px solid var(--border-soft);
    border-radius: 12px; padding: 9px 11px; margin-bottom: 8px;
  }
  .lp-notif-app { font-size: 0.63rem; color: var(--fg-faint); text-transform: uppercase; letter-spacing: 0.05em; }
  .lp-notif-t { font-size: 0.83rem; font-weight: 700; margin-top: 3px; }
  .lp-notif-b { font-size: 0.78rem; color: var(--fg-muted); margin-top: 2px; }
  .lp-notif-acts { display: flex; gap: 6px; margin-top: 8px; }
  .lp-notif-acts span {
    flex: 1 1 0; text-align: center; font-size: 0.72rem; font-weight: 700;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 7px; padding: 5px 0;
  }
  .lp-quiet {
    font-size: 0.72rem; color: var(--fg-muted); text-align: center;
    border-top: 1px dashed var(--border); padding-top: 8px; margin-top: 2px;
  }

  /* Trust strip */
  .lp-trust { text-align: center; padding: 26px 0 8px; }
  .lp-trust-label { font-size: 0.82rem; color: var(--fg-muted); }
  .lp-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 9px; margin-top: 14px; }
  .lp-chip { font-size: 0.85rem; font-weight: 600; color: var(--fg); background: var(--bg-card); border: 1px solid var(--border); border-radius: 999px; padding: 7px 15px; }
  /* Sections */
  .lp-section { padding: 38px 0; }
  .lp-kicker { text-align: center; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); }
  .lp-h2 { text-align: center; font-size: clamp(1.6rem, 3.4vw, 2.3rem); letter-spacing: -0.02em; font-weight: 800; margin: 10px 0 0; }
  .lp-sub { text-align: center; color: var(--fg-muted); font-size: 1.04rem; line-height: 1.6; margin: 14px auto 0; max-width: 52ch; }
  /* A fact is a line, not a card.

     Nine cards in a 3-up grid is three rows of 22px padding around one
     sentence each: about 700px of page to carry 60 words. As a two-column
     list of lines it is a third of that and reads faster, because the eye
     runs down one column instead of tracking round nine boxes. */
  .lp-facts {
    list-style: none; padding: 0; margin: 26px 0 0;
    /* Three across on a laptop, two on a tablet, one on a phone - whatever
       fits, rather than a fixed two that leaves half the width empty at
       1080px and takes twice the height it needs to. */
    display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 11px 30px;
  }
  .lp-facts li {
    display: list-item;                 /* not the app's flex `li` */
    font-size: 0.95rem; line-height: 1.55; color: var(--fg-muted);
    padding-left: 16px; position: relative;
  }
  .lp-facts li::before {
    content: ''; position: absolute; left: 0; top: 0.62em;
    width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
  }
  .lp-facts b { color: var(--fg); font-weight: 700; }
  /* "Show all" belongs to the phone layout only - on a computer the facts are
     three across and short. Declared HERE, before the media query that turns
     it on: a later rule of equal specificity wins wherever it lands, and this
     one sitting after the query hid the button on phones too. */
  .lp-more { display: none; }

  /* The three numbered steps, as three lines. */
  .lp-steps-lite {
    margin: 24px auto 0; padding: 0; max-width: 46rem;
    counter-reset: lpstep; list-style: none;
  }
  .lp-steps-lite li {
    display: list-item;
    counter-increment: lpstep;
    position: relative; padding-left: 40px; margin-bottom: 14px;
    font-size: 0.95rem; line-height: 1.55; color: var(--fg-muted);
  }
  .lp-steps-lite li::before {
    content: counter(lpstep);
    position: absolute; left: 0; top: -1px;
    width: 26px; height: 26px; border-radius: 50%;
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    color: var(--accent); font-weight: 800; font-size: 0.85rem;
    display: flex; align-items: center; justify-content: center;
  }
  .lp-steps-lite b { color: var(--fg); font-weight: 700; }

  /* One picture, the width of the page: simple mode's whole point is that the
     week uses the whole screen, which a 420px column cannot show. */
  .lp-figwide { margin: 26px 0 0; }
  .lp-figwide img {
    width: 100%; height: auto; display: block;
    border: 1px solid var(--border); border-radius: 16px;
  }
  .lp-shots-2 { grid-template-columns: repeat(2, 1fr); }

  .lp-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 38px; }
  .lp-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 16px; padding: 22px; }
  .lp-card-ico { font-size: 1.7rem; }
  .lp-card h3 { font-size: 1.08rem; margin: 12px 0 6px; }
  .lp-card p { font-size: 0.94rem; color: var(--fg-muted); line-height: 1.55; margin: 0; }
  /* Split use cases */
  .lp-split { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 38px; }
  .lp-usecase { border-radius: 18px; padding: 28px; border: 1px solid var(--border); }
  .lp-usecase.school { background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 12%, var(--bg-card)), var(--bg-card)); }
  .lp-usecase.home { background: linear-gradient(180deg, color-mix(in srgb, #3b82f6 12%, var(--bg-card)), var(--bg-card)); }
  .lp-usecase h3 { font-size: 1.25rem; margin: 6px 0 4px; }
  .lp-usecase > p { color: var(--fg-muted); margin: 0 0 14px; }
  .lp-ul { list-style: none; padding: 0; margin: 0; }
  .lp-ul li { position: relative; padding: 7px 0 7px 28px; font-size: 0.95rem; color: var(--fg); }
  .lp-ul li::before { content: '✓'; position: absolute; left: 0; top: 7px; color: var(--accent); font-weight: 800; }
  /* Steps */
  .lp-assist-note { max-width: 640px; margin: 22px auto 0; text-align: center; font-size: 15px; line-height: 1.6; color: var(--lp-muted, #a1a1aa); }
  .lp-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 38px; counter-reset: step; }
  .lp-step { background: var(--bg-card); border: 1px solid var(--border); border-radius: 16px; padding: 24px; }
  .lp-step-n { width: 34px; height: 34px; border-radius: 50%; background: var(--accent); color: #fff; font-weight: 800; display: flex; align-items: center; justify-content: center; }
  .lp-step h3 { font-size: 1.06rem; margin: 14px 0 6px; }
  .lp-step p { font-size: 0.94rem; color: var(--fg-muted); line-height: 1.55; margin: 0; }
  /* FAQ */
  .lp-faq { max-width: 720px; margin: 34px auto 0; }
  .lp-faq details { background: var(--bg-card); border: 1px solid var(--border); border-radius: 13px; padding: 4px 18px; margin-bottom: 10px; }
  .lp-faq summary { cursor: pointer; list-style: none; padding: 15px 0; font-weight: 700; font-size: 1rem; display: flex; justify-content: space-between; align-items: center; gap: 12px; }
  .lp-faq summary::-webkit-details-marker { display: none; }
  .lp-faq summary::after { content: '+'; color: var(--fg-muted); font-size: 1.3rem; line-height: 1; }
  .lp-faq details[open] summary::after { content: '–'; }
  .lp-faq p { margin: 0 0 16px; color: var(--fg-muted); line-height: 1.6; font-size: 0.95rem; }
  /* Final CTA */
  .lp-cta { text-align: center; background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 18%, var(--bg-card)), var(--bg-card)); border: 1px solid var(--border); border-radius: 22px; padding: 52px 24px; margin: 20px 0 0; }
  .lp-cta h2 { font-size: clamp(1.6rem, 3.6vw, 2.4rem); letter-spacing: -0.02em; margin: 0; }
  .lp-cta p { color: var(--fg-muted); font-size: 1.05rem; margin: 14px auto 26px; max-width: 46ch; }
  /* Footer */
  .lp-footer { border-top: 1px solid var(--border); margin-top: 56px; padding: 30px 0 60px; }
  .lp-footer-in { display: flex; flex-wrap: wrap; gap: 14px 26px; align-items: center; justify-content: space-between; color: var(--fg-muted); font-size: 0.88rem; }
  .lp-footer a { color: var(--fg-muted); text-decoration: none; }
  .lp-footer a:hover { color: var(--fg); text-decoration: underline; }
  .lp-foot-links { display: flex; flex-wrap: wrap; gap: 18px; }
  @media (max-width: 860px) {
    .lp-hero { grid-template-columns: 1fr; gap: 28px; padding: 40px 0 18px; min-height: 0; }
    .lp-shots { grid-template-columns: 1fr; }
    .lp-shotfig .lp-shot { height: auto; }
    .lp-shot-hero { order: 2; }
    .lp-lead { max-width: none; }
    .lp-grid, .lp-steps { grid-template-columns: 1fr; }
    .lp-facts, .lp-shots-2 { grid-template-columns: 1fr; }
    .lp-split { grid-template-columns: 1fr; }
    .lp-nav-actions .lp-hide-sm { display: none; }
  }

  /* ===== A phone is holding this =====

     Measured at 390px: eight and a half screens of scrolling, of which the
     two feature sections were more than half. Three of these are about that,
     and one is about a picture that had stopped being one.

     The screenshots become a row you swipe rather than a column you scroll.
     Three stacked figures cost about 1,100px of page to show content that
     fits in one screen; side by side with snap points they cost one figure's
     height, and swiping through screenshots is a thing phones are for. Each
     keeps its caption. */
  @media (max-width: 620px) {
    .lp-shots, .lp-shots-2 {
      display: flex;
      overflow-x: auto;
      scroll-snap-type: x mandatory;
      -webkit-overflow-scrolling: touch;
      gap: 12px;
      /* Bled to the screen edges so the next one peeks in: that is what says
         "swipe" rather than "this column is cut off". */
      margin-inline: -22px;
      padding: 0 22px 4px;
      scrollbar-width: none;
    }
    .lp-shots::-webkit-scrollbar,
    .lp-shots-2::-webkit-scrollbar { display: none; }
    .lp-shotfig {
      flex: 0 0 82%;
      scroll-snap-align: center;
      margin: 0;
    }
    .lp-shotfig > img { height: 260px; object-fit: cover; object-position: top; }

    /* The full-width board shot is a 1900px-wide picture. On a phone it lands
       about 90px tall, which is not a screenshot, it is a grey stripe where
       one used to be. The two panel shots under it are legible at this width
       and say the same thing, so this one waits for a bigger screen. */
    .lp-figwide { display: none; }

    /* A little off everything else, which adds up over seven sections. */
    .lp-section { padding: 30px 0; }
    .lp-facts { gap: 9px 0; margin-top: 20px; }

    /* Six, then a button. Twenty-one facts across two sections is about
       1,900px of phone - a wall, and the ones past the sixth are the ones
       people scroll rather than read. Everything is still in the markup;
       this only hides the tail. */
    .lp-facts li:nth-child(n+7) { display: none; }
    .lp-facts.all li { display: list-item; }
    .lp-more {
      display: block; margin: 14px auto 0;
      background: none; border: 1px solid var(--border); color: var(--fg);
      border-radius: 999px; padding: 0.55rem 1.1rem; min-height: 44px;
      font: inherit; font-size: 0.88rem; font-weight: 700; cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .lp-more:hover { border-color: var(--accent); color: var(--accent); }
  }
  /* Scroll-reveal: elements rise + fade in as they enter the viewport */
  .lp-reveal { opacity: 0; transform: translateY(30px); transition: opacity .7s cubic-bezier(.2,.6,.2,1), transform .7s cubic-bezier(.2,.6,.2,1); will-change: opacity, transform; }
  .lp-reveal.in { opacity: 1; transform: none; }
  .lp-reveal.d1 { transition-delay: .06s; } .lp-reveal.d2 { transition-delay: .12s; }
  .lp-reveal.d3 { transition-delay: .18s; } .lp-reveal.d4 { transition-delay: .24s; }
  .lp-reveal.d5 { transition-delay: .30s; } .lp-reveal.d6 { transition-delay: .36s; }
  .lp-brand { cursor: pointer; }
  /* Hover/float flourishes */
  .lp-card, .lp-step, .lp-usecase { transition: transform .2s ease, border-color .2s ease, box-shadow .2s ease; }
  .lp-card:hover { transform: translateY(-5px); border-color: color-mix(in srgb, var(--accent) 42%, var(--border)); box-shadow: 0 22px 46px -30px rgba(0,0,0,.65); }
  .lp-step:hover, .lp-usecase:hover { transform: translateY(-4px); }
  .lp-chip { transition: transform .15s ease, border-color .15s ease, color .15s ease; }
  .lp-chip:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); color: var(--accent); }
  .lp-shot-hero { animation: lp-float 7s ease-in-out infinite; }
  @keyframes lp-float { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }
  @media (prefers-reduced-motion: reduce) {
    .lp-reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
    .lp-shot-hero { animation: none; }
  }

/* ---------------------------------------------------------------- */

  /* Floating launcher — only on the signed-in app screen. */
  #assist-fab { display: none; }
  body[data-screen="main"] #assist-fab.show { display: flex; }
  /* ===== "Did you know" bar =====
     Everything this app can do that nobody finds: the assistant acting for you,
     the board photo, Enter in the search box. A tour said all of it once, on day
     one, and a tour is the worst place to learn a thing you have no use for yet.
     One tip at a time, above the launcher, and every one of them can be turned
     off from the bar itself — which is the part that makes it fair to show them. */
  .tip-bar {
    position: fixed; left: 50%; transform: translateX(-50%);
    bottom: 84px; z-index: 1399;
    display: flex; align-items: center; gap: 0.55rem;
    max-width: min(620px, calc(100vw - 24px));
    padding: 0.6rem 0.7rem 0.6rem 0.85rem;
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 999px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.32);
    font-size: 0.9rem; line-height: 1.35;
  }
  .tip-bar[hidden] { display: none; }
  .tip-ico { flex: 0 0 auto; }
  .tip-text { flex: 1 1 auto; min-width: 0; }
  .tip-text b { font-weight: 700; }
  .tip-bar button {
    flex: 0 0 auto; font: inherit; cursor: pointer;
    border-radius: 999px; border: 1px solid var(--border);
    background: var(--bg-input); color: var(--fg);
    padding: 0.3rem 0.7rem; font-size: 0.82rem; font-weight: 600;
  }
  .tip-bar .tip-act { background: var(--accent); border-color: var(--accent); color: #fff; }
  .tip-bar .tip-never { border-color: transparent; background: none; color: var(--fg-muted); }
  .tip-bar .tip-x { border-color: transparent; background: none; color: var(--fg-muted); padding: 0.3rem 0.4rem; }
  .tip-bar button:hover { border-color: var(--accent); }
  /* Narrow: neither bar has room to be beside anything, so they stack full
     width - tips above the launcher, undo along the bottom. */
  @media (max-width: 620px) {
    /* Directly above the assistant launcher, not two bars up. The undo bar
       is only on screen for ten seconds after a delete; the tip was being
       positioned as though it were always there, which left it floating in
       the middle of the page most of the time. */
    .tip-bar {
      left: 10px; right: 10px; transform: none; max-width: none;
      border-radius: 16px; flex-wrap: wrap; bottom: 86px;
    }
    /* ...and moves up out of the way on the rare occasion both are out. */
    body:has(.undo-bar:not([hidden])) .tip-bar { bottom: 150px; }
    .tip-bar .tip-text { flex: 1 1 100%; }
    /* Clear of the assistant launcher.
       Both sat at bottom:18px, so "Undo" landed directly under the chat
       button - and the whole point of an undo bar is that it is pressed in a
       hurry, by somebody who has just deleted the wrong thing. Missing it and
       opening the assistant instead is the worst available outcome. The
       launcher is ~52px tall from 18px up, so this starts above it. */
    .undo-bar { left: 10px; right: 10px; bottom: 86px; border-radius: 16px; }
  }
  /* Lifted out of the way of anything you are meant to be able to press.

     Both of these float over the page in the bottom-right corner, and the
     fortnight chart's last columns live in that corner - measured, the
     launcher covered 139px of it on a computer and the right edge of the last
     bar of each row on a phone. That is what "only part of the bars are
     clickable" was. keepCornerClear() in app.js sets --corner-lift when they
     would overlap; it is 0 the rest of the time, which is nearly always. */
  #assist-fab {
    position: fixed; right: 18px;
    bottom: calc(18px + var(--corner-lift, 0px)); z-index: 1400;
    transition: transform .15s ease, box-shadow .15s ease, bottom .18s ease;
    align-items: center; gap: 0.5rem;
    background: var(--accent); color: #fff;
    border: none; border-radius: 999px;
    padding: 0.7rem 0.95rem; cursor: pointer;
    box-shadow: 0 8px 24px rgba(0,0,0,0.28);
    font: inherit; font-weight: 700; font-size: 0.92rem;
  }
  #assist-fab:hover { transform: translateY(-2px); box-shadow: 0 12px 30px rgba(0,0,0,0.34); }
  #assist-fab svg { width: 22px; height: 22px; flex: 0 0 auto; }
  #assist-fab .assist-fab-label { white-space: nowrap; }
  .assist-fab-dismiss {
    position: absolute; top: -7px; right: -7px;
    width: 20px; height: 20px; border-radius: 50%;
    background: var(--bg-card, #1f2937); color: var(--fg-muted, #9ca3af);
    border: 1px solid var(--border, #374151);
    font-size: 12px; line-height: 18px; text-align: center;
    cursor: pointer; padding: 0; display: none;
  }
  #assist-fab:hover .assist-fab-dismiss { display: block; }
  @media (max-width: 520px) {
    #assist-fab .assist-fab-label { display: none; }
    #assist-fab { padding: 0.8rem; }
    /* Phone: a floating card, not a full-screen takeover. */
    #assist-panel { right: 10px; bottom: 14px; width: calc(100vw - 20px); height: 68vh; max-height: 560px; }
  }
  /* Phone (touch devices, or the app's phone layout): always show the launcher's
     dismiss × — no hover needed to reveal it. */
  @media (hover: none) { .assist-fab-dismiss { display: block; width: 22px; height: 22px; line-height: 20px; } }
  body[data-layout="phone"] .assist-fab-dismiss { display: block; width: 22px; height: 22px; line-height: 20px; }

  /* Chat panel */
  #assist-panel { display: none; }
  #assist-panel.open { display: flex; }
  #assist-panel {
    position: fixed; right: 18px; bottom: 18px; z-index: 1401;
    width: min(380px, calc(100vw - 24px));
    height: min(600px, calc(100vh - 36px));
    flex-direction: column;
    background: var(--bg-card); color: var(--fg);
    border: 1px solid var(--border); border-radius: 18px;
    box-shadow: 0 18px 48px rgba(0,0,0,0.40); overflow: hidden;
  }
  /* Open during the tour, the panel leaves the top of the screen clear for the
     step it is covering. Full height there means the tour is simply gone. */
  body[data-coach="on"] #assist-panel {
    height: min(460px, calc(100vh - 190px));
    width: min(360px, calc(100vw - 24px));
  }
  body[data-coach="on"] #assist-fab { z-index: 1402; }
  .ast-head { display: flex; align-items: center; gap: 0.6rem; padding: 0.85rem 1rem;
    background: var(--accent); color: #fff; }
  .ast-head .ast-ic { width: 26px; height: 26px; flex: 0 0 auto; }
  .ast-head .ast-title { font-weight: 800; font-size: 0.98rem; line-height: 1.1; }
  .ast-head .ast-sub { font-size: 0.72rem; opacity: 0.9; }
  .ast-head .ast-x { margin-left: auto; background: rgba(255,255,255,0.18); border: none; color: #fff;
    width: 30px; height: 30px; border-radius: 8px; cursor: pointer; font-size: 18px; line-height: 1; }
  .ast-head .ast-x:hover { background: rgba(255,255,255,0.30); }
  .ast-modes { display: flex; gap: 0.4rem; padding: 0.55rem 0.8rem; border-bottom: 1px solid var(--border); }
  .ast-mode { flex: 1; padding: 0.4rem 0.5rem; border-radius: 999px; border: 1px solid var(--border);
    background: var(--bg-input); color: var(--fg-muted); font: inherit; font-size: 0.8rem; font-weight: 600; cursor: pointer; }
  .ast-mode.on { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); border-color: var(--accent); }
  .ast-body { flex: 1; overflow-y: auto; padding: 0.9rem; display: flex; flex-direction: column; gap: 0.6rem; }
  .ast-msg { max-width: 85%; padding: 0.55rem 0.75rem; border-radius: 14px; font-size: 0.9rem; line-height: 1.45; white-space: pre-wrap; word-wrap: break-word; }
  .ast-msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 5px; }
  .ast-msg.bot { align-self: flex-start; background: var(--bg-input); color: var(--fg); border-bottom-left-radius: 5px; }
  .ast-msg.note { align-self: center; background: transparent; color: var(--fg-muted); font-size: 0.78rem; text-align: center; max-width: 100%; }
  .ast-typing { align-self: flex-start; color: var(--fg-muted); font-size: 0.85rem; }
  .ast-foot { border-top: 1px solid var(--border); padding: 0.6rem; }
  .ast-inrow { display: flex; gap: 0.45rem; align-items: flex-end; }
  #assist-input { flex: 1; resize: none; max-height: 110px; min-height: 40px;
    background: var(--bg-input); color: var(--fg); border: 1px solid var(--border);
    border-radius: 12px; padding: 0.55rem 0.7rem; font: inherit; font-size: 0.9rem; }
  #assist-send { flex: 0 0 auto; background: var(--accent); color: #fff; border: none;
    border-radius: 12px; width: 42px; height: 42px; cursor: pointer; font-size: 17px; }
  #assist-send:disabled { opacity: 0.5; cursor: default; }
  .ast-limit { margin: 0.4rem 0 0; }
  .ast-hint { display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin: 0.5rem 0.15rem 0; font-size: 0.72rem; color: var(--fg-muted); }
  .ast-hint a { color: var(--accent); cursor: pointer; text-decoration: none; }
  .ast-hint a:hover { text-decoration: underline; }
  .ast-hint-left { flex: 1 1 auto; min-width: 0; }
  .ast-hint-hide { flex: 0 0 auto; font-weight: 600; white-space: nowrap; }
