/* ============================================================================
   CUI Lahore Timetable — design tokens & shared UI

   Product-register system: this is a task tool, not a landing page. Density
   over drama, familiar patterns over flavor, one accent used sparingly for
   selection/action/state — not decoration. OKLCH throughout so lightness
   steps read as equal steps. Neutrals carry a hair of the accent hue instead
   of sitting at zero chroma.
   ============================================================================ */

:root {
  /* --- The chrome is deliberately monochrome. This app colour-codes courses,
         and those colours are the information. A coloured UI accent would
         compete with them, so the accent here IS ink: near-black on paper in
         light mode, near-white in dark. Colour on screen means "course". --- */
  --hue: 80;                                  /* a warm paper cast, not a violet one */
  --accent-90: oklch(92% 0.004 var(--hue));
  --accent-70: oklch(72% 0.006 var(--hue));
  --accent-55: oklch(24% 0.012 var(--hue));   /* ink */
  --accent-45: oklch(16% 0.012 var(--hue));   /* ink, pressed */
  --accent-30: oklch(12% 0.010 var(--hue));

  /* --- semantic --- */
  --ok-55:   oklch(58% 0.14 152);
  --ok-90:   oklch(94% 0.045 152);
  --warn-55: oklch(72% 0.15 80);
  --warn-90: oklch(94% 0.06 80);
  --bad-55:  oklch(56% 0.19 25);
  --bad-90:  oklch(94% 0.05 25);

  /* --- tinted neutrals (light) --- */
  --n-0:  oklch(99.3% 0.003 var(--hue));
  --n-10: oklch(97.3% 0.005 var(--hue));
  --n-20: oklch(94.5% 0.007 var(--hue));
  --n-30: oklch(90% 0.009 var(--hue));
  --n-40: oklch(82% 0.011 var(--hue));
  --n-50: oklch(68% 0.013 var(--hue));
  --n-60: oklch(54% 0.015 var(--hue));
  --n-70: oklch(40% 0.016 var(--hue));
  --n-80: oklch(28% 0.014 var(--hue));
  --n-90: oklch(18% 0.012 var(--hue));

  --bg: var(--n-0);
  --surface: oklch(99.6% 0.002 var(--hue));
  --surface-2: var(--n-10);
  --border: var(--n-30);
  --border-soft: var(--n-20);
  --rule: var(--n-20);                        /* hairlines: the main structural device */
  --rule-strong: var(--n-40);
  --text: var(--n-90);
  --text-dim: var(--n-60);
  --text-faint: var(--n-50);
  --accent: var(--accent-55);
  --accent-ink: oklch(99% 0.003 var(--hue));
  --accent-tint: var(--accent-90);
  /* follows the theme: ink on paper, paper on ink. Pinning this to a fixed
     value left dark mode with a near-invisible focus ring. */
  --focus-ring: var(--accent);

  --content: 1280px;

  /* Near-square. Rounded-everything is the single loudest "generic app" tell. */
  --radius-sm: 2px;
  --radius: 3px;
  --radius-lg: 3px;
  --shadow-1: none;
  --shadow-2: 0 10px 34px oklch(20% 0.02 var(--hue) / 0.13), 0 2px 6px oklch(20% 0.02 var(--hue) / 0.07);

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --dark: 1; }
}
:root[data-theme="dark"] { --dark: 1; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --n-0:  oklch(17% 0.010 var(--hue));
    --n-10: oklch(20% 0.011 var(--hue));
    --n-20: oklch(24% 0.012 var(--hue));
    --n-30: oklch(30% 0.013 var(--hue));
    --n-40: oklch(38% 0.014 var(--hue));
    --n-50: oklch(52% 0.014 var(--hue));
    --n-60: oklch(66% 0.013 var(--hue));
    --n-70: oklch(78% 0.011 var(--hue));
    --n-80: oklch(88% 0.008 var(--hue));
    --n-90: oklch(95% 0.005 var(--hue));

    --bg: var(--n-0);
    --surface: var(--n-10);
    --surface-2: var(--n-20);
    --border: var(--n-30);
    --border-soft: oklch(26% 0.012 var(--hue));
    --rule: oklch(27% 0.012 var(--hue));
    --rule-strong: oklch(38% 0.013 var(--hue));
    --text: var(--n-90);
    --text-dim: var(--n-60);
    --text-faint: var(--n-50);
    --accent: oklch(94% 0.006 var(--hue));
    --accent-ink: oklch(15% 0.010 var(--hue));
    --accent-tint: oklch(28% 0.010 var(--hue));

    --ok-55: oklch(70% 0.13 152); --ok-90: oklch(24% 0.05 152);
    --warn-55: oklch(78% 0.14 80); --warn-90: oklch(26% 0.06 80);
    --bad-55: oklch(68% 0.17 25); --bad-90: oklch(26% 0.07 25);
    --shadow-1: 0 1px 2px oklch(0% 0 0 / 0.35);
    --shadow-2: 0 8px 24px oklch(0% 0 0 / 0.45);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --n-0:  oklch(17% 0.010 var(--hue));
  --n-10: oklch(20% 0.011 var(--hue));
  --n-20: oklch(24% 0.012 var(--hue));
  --n-30: oklch(30% 0.013 var(--hue));
  --n-40: oklch(38% 0.014 var(--hue));
  --n-50: oklch(52% 0.014 var(--hue));
  --n-60: oklch(66% 0.013 var(--hue));
  --n-70: oklch(78% 0.011 var(--hue));
  --n-80: oklch(88% 0.008 var(--hue));
  --n-90: oklch(95% 0.005 var(--hue));
  --bg: var(--n-0); --surface: var(--n-10); --surface-2: var(--n-20);
  --border: var(--n-30); --border-soft: oklch(26% 0.012 var(--hue));
  --rule: oklch(27% 0.012 var(--hue)); --rule-strong: oklch(38% 0.013 var(--hue));
  --text: var(--n-90); --text-dim: var(--n-60); --text-faint: var(--n-50);
  --accent: oklch(94% 0.006 var(--hue)); --accent-ink: oklch(15% 0.010 var(--hue));
  --accent-tint: oklch(28% 0.010 var(--hue));
  --ok-55: oklch(70% 0.13 152); --ok-90: oklch(24% 0.05 152);
  --warn-55: oklch(78% 0.14 80); --warn-90: oklch(26% 0.06 80);
  --bad-55: oklch(68% 0.17 25); --bad-90: oklch(26% 0.07 25);
  --shadow-1: 0 1px 2px oklch(0% 0 0 / 0.35);
  --shadow-2: 0 8px 24px oklch(0% 0 0 / 0.45);
}

/* ---------------------------------------------------------------- reset */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font: 400 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* Tabular figures throughout: this app is mostly times, counts and codes,
     and they should sit in columns rather than jitter line to line. */
  font-variant-numeric: tabular-nums;
}
h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -0.012em; }
p { margin: 0; }
a { color: inherit; }
button { font: inherit; color: inherit; }
input, select { font: inherit; color: inherit; }
::selection { background: var(--accent-tint); }

.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

/* focus ring, keyboard-first */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---------------------------------------------------------------- layout shell */
.shell { min-height: 100vh; display: flex; flex-direction: column; }

.topbar {
  position: sticky; top: 0; z-index: 40;
  background: var(--bg);
  border-bottom: 1px solid var(--rule-strong);
}
.topbar-inner {
  max-width: var(--content); margin: 0 auto; padding: 11px 24px;
  display: flex; align-items: center; gap: 20px;
}
.brand { display: flex; align-items: center; gap: 9px; text-decoration: none; flex: 0 0 auto; }
.brand-mark {
  width: 26px; height: 26px; border-radius: 2px;
  background: var(--text); color: var(--bg);
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 11px; letter-spacing: 0.01em;
}
.brand-text { line-height: 1.1; }
.brand-title { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; }
.brand-sub { font-size: 9.5px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.09em; font-weight: 600; }

.nav { display: flex; gap: 2px; flex: 1 1 auto; min-width: 0; }
.nav a {
  padding: 6px 2px; margin-right: 16px; font-size: 13px; font-weight: 500;
  text-decoration: none; color: var(--text-dim); white-space: nowrap;
  border-bottom: 2px solid transparent;
  transition: color 140ms var(--ease-out), border-color 140ms var(--ease-out);
}
.nav a:hover { color: var(--text); }
.nav a.active { color: var(--text); font-weight: 650; border-bottom-color: var(--text); }

.topbar-actions { display: flex; align-items: center; gap: 8px; }

.icon-btn {
  width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); border: 1px solid transparent; background: transparent;
  cursor: pointer; color: var(--text-dim);
  transition: background 140ms var(--ease-out), transform 120ms var(--ease-out);
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); }
.icon-btn:active { transform: scale(0.94); }

.main { flex: 1 1 auto; max-width: var(--content); margin: 0 auto; padding: 30px 24px 80px; width: 100%; }

.footer {
  border-top: 1px solid var(--rule); padding: 16px 24px 26px;
  color: var(--text-faint); font-size: 12px;
}
.footer-inner { max-width: var(--content); margin: 0 auto; display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.footer a { color: var(--text-dim); text-decoration: underline; text-underline-offset: 2px; }

/* ---------------------------------------------------------------- search */
.searchbox {
  position: relative; flex: 1 1 260px; max-width: 460px; min-width: 140px;
}
.searchbox input {
  width: 100%; padding: 8px 12px 8px 34px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  font-size: 13.5px; transition: border-color 140ms var(--ease-out), background 140ms var(--ease-out);
}
.searchbox input:focus { background: var(--surface); border-color: var(--accent); outline: none; }
.searchbox input::placeholder { color: var(--text-faint); }
.searchbox svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-faint); pointer-events: none; }
.searchbox kbd {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  font-size: 10.5px; color: var(--text-faint); border: 1px solid var(--border); border-radius: 4px;
  padding: 1px 5px; font-family: inherit;
}

.search-results {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 50;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2); max-height: 70vh; overflow-y: auto; padding: 6px;
}
.search-group-label {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-faint); padding: 8px 10px 4px;
}
.search-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--radius-sm);
  text-decoration: none; color: var(--text); cursor: pointer;
  /* reset for when this class lands on a <button> (the picker dropdowns) instead of an <a> (header search) */
  width: 100%; text-align: left; background: none; border: none; font: inherit;
}
.search-row:hover, .search-row.hl { background: var(--surface-2); }
.search-row-title { font-size: 13px; font-weight: 560; flex: 1 1 auto; min-width: 0; }
.search-row-title span.t { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.search-row-meta { font-size: 11.5px; color: var(--text-faint); flex: 0 0 auto; }
.search-empty { padding: 20px 14px; color: var(--text-faint); font-size: 13px; text-align: center; }

/* ---------------------------------------------------------------- page header */
/* The page header is where the type scale earns its keep: a small tracked-out
   label, a title with real size and negative tracking, then quiet body. */
.page-head { margin-bottom: 26px; padding-bottom: 18px; border-bottom: 1px solid var(--rule-strong); }
.page-eyebrow {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--text-faint); margin-bottom: 9px;
}
.page-title { font-size: 32px; font-weight: 680; letter-spacing: -0.028em; line-height: 1.06; }
.page-desc { font-size: 13.5px; color: var(--text-dim); margin-top: 9px; max-width: 66ch; }

/* ---------------------------------------------------------------- tags / badges */
.tag {
  display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600;
  padding: 2px 6px; border-radius: 2px; border: 1px solid var(--border); color: var(--text-dim);
  background: var(--surface-2); white-space: nowrap;
}
.tag.code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: 0.01em; }
.tag.accent { background: var(--accent-tint); color: var(--accent); border-color: transparent; }
.tag.ok { background: var(--ok-90); color: var(--ok-55); border-color: transparent; }
.tag.bad { background: var(--bad-90); color: var(--bad-55); border-color: transparent; }
.tag.warn { background: var(--warn-90); color: color-mix(in oklch, var(--warn-55) 70%, var(--text)); border-color: transparent; }

/* A labeled group of chips with no card chrome - for lists with many small,
   unevenly-sized groups (e.g. 35+ programs on the Sections page), where a
   bordered panel per group is mostly empty padding repeated forty times.
   Rhythm comes from the margin between groups, not from a box around each one. */
.group-block { margin-top: 28px; }
.group-block:first-child { margin-top: 0; }
.group-heading {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--text-faint); margin: 0 0 10px; padding-bottom: 7px;
  border-bottom: 1px solid var(--rule);
  display: flex; align-items: center; gap: 7px;
}
.group-heading .count { font-size: 10.5px; font-weight: 600; color: var(--text-faint); text-transform: none; letter-spacing: 0; }
.chip-row { display: flex; flex-wrap: wrap; gap: 7px; }
.chip-row .tag { padding: 6px 11px; font-size: 12.5px; }

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 6px 13px; border-radius: var(--radius); border: 1px solid var(--rule-strong);
  background: transparent; color: var(--text); font-size: 12.5px; font-weight: 560;
  cursor: pointer; transition: transform 120ms var(--ease-out), background 140ms var(--ease-out), border-color 140ms var(--ease-out);
}
.btn:hover { background: var(--surface-2); }
.btn:active { transform: scale(0.97); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn.primary:hover { background: var(--accent-45); }
.btn.ghost { border-color: transparent; background: transparent; }
.btn.ghost:hover { background: var(--surface-2); }
.btn.sm { padding: 4px 9px; font-size: 12px; border-radius: var(--radius-sm); }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.btn:active:not([disabled]) { transform: scale(0.97); }

/* ---------------------------------------------------------------- panels & lists (no boxed card grids) */
/* A "panel" is a rule and a label, not a card: no border box, no radius, no
   fill. Structure comes from the hairline between rows and from the space
   around the group, which is what a printed schedule does. */
.panel { border-top: 1px solid var(--rule-strong); }
.panel + .panel { margin-top: 34px; }
.panel-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 10px 0 9px;
}
.panel-head h3 {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--text-faint);
}
.panel-body { padding: 12px 0; }

.list-row {
  display: flex; align-items: center; gap: 14px; padding: 9px 0;
  border-top: 1px solid var(--rule);
}
.list-row:first-child { border-top: none; }
.list-row.link { cursor: pointer; transition: background 110ms var(--ease-out); text-decoration: none; color: inherit; }
.list-row.link:hover { background: var(--surface-2); }

.section-list { display: flex; flex-wrap: wrap; gap: 7px; }

/* ---------------------------------------------------------------- filter bar */
.filterbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; align-items: center; }
.chip-select {
  padding: 6px 10px; border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--surface); color: var(--text); font-size: 12.5px; cursor: pointer;
}
.chip-toggle {
  padding: 5px 11px; border-radius: 2px; border: 1px solid var(--rule-strong); background: transparent;
  font-size: 12.5px; font-weight: 560; color: var(--text-dim); cursor: pointer;
  transition: background 120ms var(--ease-out), color 120ms var(--ease-out), border-color 120ms var(--ease-out);
}
.chip-toggle:hover { background: var(--surface-2); }
.chip-toggle.on { background: var(--text); border-color: var(--text); color: var(--bg); }
.filter-spacer { flex: 1 1 auto; }

/* ---------------------------------------------------------------- week grid
   Days listed down the left as rows, time running left-to-right across the
   top as columns — the same orientation as the official university timetable
   and as a printed timetable generally. Necessarily wider than tall, so it
   scrolls horizontally inside its own frame rather than the page. */
.weekgrid-wrap { overflow-x: auto; border: 1px solid var(--border-soft); border-radius: var(--radius-lg); background: var(--surface); }
.weekgrid-h { display: grid; }
.wg-corner-h {
  position: sticky; left: 0; top: 0; z-index: 4; background: var(--surface);
  border-right: 1px solid var(--border-soft); border-bottom: 1px solid var(--border-soft);
}
.wg-hours-track {
  position: sticky; top: 0; z-index: 3; background: var(--surface);
  border-bottom: 1px solid var(--border-soft); overflow: hidden;
}
.wg-hourmark {
  position: absolute; top: 0; height: 100%; display: flex; align-items: center;
  font-size: 10.5px; color: var(--text-dim); padding-left: 5px; box-sizing: border-box;
  font-variant-numeric: tabular-nums;
}
/* the :30 tick is a reading aid, not a label - it should recede */
.wg-hourmark.half { font-size: 9px; color: var(--text-faint); opacity: 0.75; }
.wg-daylabel-h {
  position: sticky; left: 0; z-index: 2; background: var(--surface);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  border-right: 1px solid var(--border-soft); border-top: 1px solid var(--border-soft);
  font-size: 12.5px; font-weight: 700;
}
.wg-daylabel-h .n { font-size: 9px; font-weight: 500; color: var(--text-faint); }
.wg-dayrow-track { position: relative; border-top: 1px solid var(--border-soft); }

.wg-block-h {
  position: absolute; border-radius: 7px; padding: 4px 7px; box-sizing: border-box;
  overflow: hidden; border: 1px solid var(--blk-border, var(--border));
  background: var(--blk-bg, var(--surface-2));
  display: flex; flex-direction: column; justify-content: center; gap: 1px;
  cursor: default; transition: filter 120ms var(--ease-out);
}
.wg-block-h:hover { filter: brightness(0.97); }
:root[data-theme="dark"] .wg-block-h:hover { filter: brightness(1.12); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .wg-block-h:hover { filter: brightness(1.12); }
}
.wg-block-h .t { font-size: 11px; font-weight: 650; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wg-block-h .m { font-size: 9.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wg-block-h.clash { --blk-bg: var(--bad-90); --blk-border: var(--bad-55); }
.wg-block-h.clash .t { color: color-mix(in oklch, var(--bad-55) 75%, var(--text)); }
.weekgrid-h.compact .wg-block-h .m { display: none; }

/* empty state */
.empty {
  padding: 44px 20px; text-align: center; color: var(--text-faint);
}
.empty .big { font-size: 28px; margin-bottom: 8px; }
.empty h4 { color: var(--text-dim); font-size: 14px; margin-bottom: 4px; }
.empty p { font-size: 12.5px; max-width: 40ch; margin: 0 auto; }

/* ---------------------------------------------------------------- stat row */
.stats { display: flex; gap: 0; flex-wrap: wrap; }
.stat { flex: 1 1 120px; padding: 12px 16px; border-right: 1px solid var(--border-soft); }
.stat:last-child { border-right: none; }
.stat .v { font-size: 19px; font-weight: 700; letter-spacing: -0.01em; }
.stat .l { font-size: 11px; color: var(--text-faint); margin-top: 1px; }
.stat.bad .v { color: var(--bad-55); }

/* ---------------------------------------------------------------- planner offering rows */
.offer-row {
  display: flex; align-items: flex-start; gap: 12px; padding: 10px 0; border-top: 1px solid var(--rule);
}
.offer-row:first-child { border-top: none; }
.offer-check {
  width: 18px; height: 18px; border-radius: 5px; border: 1.5px solid var(--border); background: var(--surface);
  flex: 0 0 auto; margin-top: 1px; cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background 120ms var(--ease-out), border-color 120ms var(--ease-out), transform 100ms var(--ease-out);
}
.offer-check:active { transform: scale(0.9); }
.offer-check.on { background: var(--accent); border-color: var(--accent); }
.offer-check svg { opacity: 0; transform: scale(0.6); transition: opacity 100ms var(--ease-out), transform 100ms var(--ease-out); color: var(--accent-ink); }
.offer-check.on svg { opacity: 1; transform: scale(1); }
.offer-main { flex: 1 1 auto; min-width: 0; }
.offer-title { font-size: 13.5px; font-weight: 600; letter-spacing: -0.005em; }
.offer-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------------------------------------------------------------- responsive */
/* ============================================================================
   MOBILE — an app, not a squeezed page.

   The old rule here hid .nav below 860px and put nothing in its place, so every
   destination disappeared on every phone and search was the only way to move
   around. The shell below is built for a thumb: primary navigation sits in a
   bottom tab bar, the header carries only context, and rows are big enough to
   hit without aiming.
   ============================================================================ */

.tabbar { display: none; }

@media (max-width: 860px) {
  /* --- top bar: context only. Navigation moved to the bottom. --- */
  .nav { display: none; }
  .searchbox { display: none; }              /* search has its own screen now */
  .topbar { border-bottom: 1px solid var(--rule); }
  .topbar-inner {
    padding: 10px 16px;
    padding-top: calc(10px + env(safe-area-inset-top));
    gap: 12px;
  }
  .brand-mark { width: 30px; height: 30px; font-size: 12px; }
  .brand-title { font-size: 15px; }
  .brand-sub { font-size: 9px; }

  /* --- the tab bar itself --- */
  .tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
    display: grid; grid-template-columns: repeat(5, 1fr);
    background: var(--bg);
    border-top: 1px solid var(--rule-strong);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .tabbar a {
    position: relative;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; padding: 9px 4px 8px; min-height: 56px;
    text-decoration: none; color: var(--text-faint);
    -webkit-tap-highlight-color: transparent;
  }
  .tabbar svg { width: 23px; height: 23px; }
  .tabbar span { font-size: 10.5px; font-weight: 600; letter-spacing: -0.01em; }
  .tabbar a.active { color: var(--text); }
  .tabbar a.active svg { stroke-width: 2.1; }
  .tabbar a:active { background: var(--surface-2); }
  .tab-badge {
    position: absolute; top: 5px; left: 50%; margin-left: 4px;
    min-width: 17px; height: 17px; padding: 0 4px; border-radius: 9px;
    background: var(--accent); color: var(--accent-ink);
    font-size: 10px !important; font-weight: 700; line-height: 17px; text-align: center;
  }
  .tab-badge:empty { display: none; }

  /* room for the bar, plus the home indicator */
  .main { padding-bottom: calc(76px + env(safe-area-inset-bottom)); }
  .footer { display: none; }                 /* the meta line lives on Home instead */

  /* --- typography: a phone headline is not a desktop headline --- */
  .main { padding-left: 16px; padding-right: 16px; }
  .page-head { padding-bottom: 14px; margin-bottom: 4px; }
  .page-title { font-size: 25px; letter-spacing: -0.022em; line-height: 1.16; }
  .page-desc { font-size: 14px; margin-top: 8px; }
  .page-eyebrow { font-size: 10px; }

  /* --- rows you can actually hit --- */
  .list-row {
    padding: 13px 2px; min-height: 52px; gap: 11px;
    -webkit-tap-highlight-color: transparent;
  }
  .list-row .offer-title { font-size: 15px; }
  .list-row .offer-sub { font-size: 13px; }
  .btn { min-height: 42px; padding: 9px 14px; font-size: 14px; }
  .btn.sm { min-height: 42px; padding: 9px 13px; font-size: 13.5px; }
  /* Only the tappable ones get a finger-sized target. A course-code badge is
     not a button and shouldn't be inflated to look like one. */
  a.tag, button.tag, .chip-toggle, .agenda-day {
    min-height: 42px; display: inline-flex; align-items: center; justify-content: center;
    font-size: 13.5px; padding-left: 12px; padding-right: 12px;
  }
  .tag { font-size: 12.5px; }
  .chip-row { gap: 8px; }
  .text-input, .time-select { min-height: 46px; font-size: 16px; }  /* 16px stops iOS zooming on focus */
  .icon-btn { width: 42px; height: 42px; }

  /* single column everywhere; two columns on a phone is just small text */
  .panel.cols-2, .two-col { display: block; }
  .index-cols { column-count: 1; }

  /* --- wide things scroll inside their own box, never the page --- */
  .weekgrid-wrap { -webkit-overflow-scrolling: touch; }

  /* --- dialogs become bottom sheets --- */
  .modal-wrap { align-items: flex-end; padding: 0; }
  .modal-card {
    width: 100%; max-width: 100%;
    border-radius: 14px 14px 0 0; border-bottom: none;
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
    max-height: 88vh;
  }
  .modal-card.chat { width: 100%; }
  .modal-card .panel-head { flex-wrap: wrap; }

  .cta-tile { padding: 16px; gap: 13px; }
  .cta-title { font-size: 17px; }
}

@media (max-width: 420px) {
  .page-title { font-size: 23px; }
  .tabbar span { font-size: 10px; }
}

/* ---------------------------------------------------------------- misc */
.skeleton {
  background: linear-gradient(100deg, var(--surface-2) 30%, var(--n-20) 50%, var(--surface-2) 70%);
  background-size: 220% 100%; animation: shimmer 1.3s ease-in-out infinite; border-radius: var(--radius-sm);
}
@keyframes shimmer { to { background-position: -220% 0; } }

.hidden { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }

.divider { height: 1px; background: var(--border-soft); margin: 18px 0; }

.help-text { font-size: 12px; color: var(--text-faint); }

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 720px) { .two-col { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------- density
   Long single-column lists on a 1280px canvas leave two thirds of the screen
   empty. These are short rows, so they column up cleanly and halve the scroll. */
.panel.cols-2 { column-count: 2; column-gap: 40px; }
.panel.cols-2 .list-row { break-inside: avoid; }
@media (max-width: 900px) { .panel.cols-2 { column-count: 1; } }

/* The section index on the home page: many small groups, so column them up
   rather than running one narrow list down a wide screen. */
.index-cols { column-count: 4; column-gap: 34px; padding-top: 4px; }
.index-cols .group-block { break-inside: avoid; margin-top: 20px; }
.index-cols .group-block:first-child { margin-top: 0; }
.index-cols .chip-row { gap: 5px; }
.index-cols .tag { padding: 4px 7px; font-size: 11.5px; }
@media (max-width: 1100px) { .index-cols { column-count: 3; } }
@media (max-width: 800px)  { .index-cols { column-count: 2; } }
@media (max-width: 520px)  { .index-cols { column-count: 1; } }

/* Course colour swatch: a bare dot. It sits inside a .tag elsewhere for
   layout convenience, but it should read as a colour mark, not a chip. */
.swatch {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; display: inline-block;
}

/* Clash detail inside an auto-build option: the specific overlaps, spelled out.
   Tinted rather than boxed, so it reads as a warning on the option itself
   rather than as yet another card nested inside one. */
.clash-detail {
  margin-top: 9px; padding: 8px 10px; border-left: 0;
  background: var(--bad-90); border-radius: 2px;
}
.clash-line { font-size: 12px; color: color-mix(in oklch, var(--bad-55) 78%, var(--text)); }
.clash-line + .clash-line { margin-top: 3px; }
.clash-line .when { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.clash-line .amt { font-weight: 700; margin-left: 4px; }

/* Freshness stamp + its manual refresh, kept on one line in the footer. */
.footer-freshness { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.footer-freshness .btn { padding: 3px 9px; font-size: 11.5px; }
.footer-freshness .btn[disabled] { opacity: 0.55; }

/* Time column in room occupancy rows: fixed width so the times line up down
   the page rather than stepping in and out with the course title. */
.list-row .when {
  flex: 0 0 auto; min-width: 84px;
  font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.group-block .list-row:first-of-type { border-top: none; }

/* The free-room finder is the thing most people open the Rooms page for, so it
   gets a real target rather than a link buried in the subtitle. */
.cta-tile {
  display: flex; align-items: center; gap: 16px;
  margin-top: 20px; padding: 18px 20px;
  border: 1px solid var(--rule-strong); border-radius: var(--radius);
  background: var(--surface-2); text-decoration: none;
  transition: border-color 120ms var(--ease-out), background 120ms var(--ease-out);
}
.cta-tile:hover { border-color: var(--accent); background: var(--surface); }
.cta-icon {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--accent-ink);
}
.cta-icon svg { width: 20px; height: 20px; }
.cta-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cta-title { font-size: 16px; font-weight: 650; letter-spacing: -0.015em; }
.cta-sub { font-size: 12.5px; color: var(--text-dim); }
.cta-go { margin-left: auto; font-size: 18px; color: var(--text-faint); }
.cta-tile:hover .cta-go { color: var(--text); }
@media (max-width: 520px) { .cta-go { display: none; } }

/* Time window: the two dropdowns were bare and read as disabled. Give them a
   surface, a real border and From/to labels so they look operable. */
.time-window { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding-top: 2px; }
.time-label { font-size: 12px; color: var(--text-dim); }
.time-select {
  padding: 8px 10px; font-size: 14px; font-variant-numeric: tabular-nums;
  border: 1px solid var(--rule-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); cursor: pointer;
}
.time-select:hover { border-color: var(--accent); }


/* Rooms-by-time day grid */
.weekgrid-h.roomday .wg-daylabel-h.roomlabel {
  align-items: flex-start; justify-content: center; padding: 0 10px;
  font-size: 12px; font-weight: 600; text-align: left; display: flex; flex-direction: column; gap: 1px;
}
.weekgrid-h.roomday .roomlabel a { text-decoration: none; }
.weekgrid-h.roomday .roomlabel a:hover { text-decoration: underline; }
.weekgrid-h.roomday .roomlabel .n { font-size: 9.5px; font-weight: 500; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.06em; }
.weekgrid-h.roomday .wg-block-h .t { font-size: 11px; }
.weekgrid-h.roomday .wg-block-h .m { font-size: 9.5px; }

/* The window you asked for. On a free room it reads as "this is your slot";
   on an occupied one it just marks where to look. */
.wg-window { position: absolute; top: 0; bottom: 0; background: var(--accent-tint); opacity: 0.55; pointer-events: none; }
.wg-window.clear { background: var(--ok-90); opacity: 1; border-left: 1px solid var(--ok-55); border-right: 1px solid var(--ok-55); }

/* Long lab names must not spill into the next room's row: clamp to two lines
   and keep the full name on hover. */
.weekgrid-h.roomday .roomlabel { overflow: hidden; }
.weekgrid-h.roomday .roomlabel .nm {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.25;
}

/* Room-day grid was too small to read comfortably: lift the block text and the
   room labels to real reading sizes. */
.weekgrid-h.roomday .wg-block-h .t { font-size: 12px; }
.weekgrid-h.roomday .wg-block-h .m { font-size: 11px; }
.weekgrid-h.roomday .wg-daylabel-h.roomlabel { font-size: 13px; }
.weekgrid-h.roomday .roomlabel .n { font-size: 10.5px; }
.wg-hourmark { font-size: 11.5px; }
.wg-hourmark.half { font-size: 10.5px; opacity: 0.85; }

/* A legend beats a sentence of grey micro-copy for explaining a grid. */
.grid-legend {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin: 10px 0 14px; font-size: 13px; color: var(--text-dim);
}
.grid-legend .key {
  display: inline-block; width: 22px; height: 12px; border-radius: 2px; vertical-align: middle;
}
.grid-legend .key-free { background: var(--ok-90); border: 1px solid var(--ok-55); }
.grid-legend .key-busy { background: var(--surface-2); border: 1px solid var(--rule-strong); }

/* ---- swap board ---- */
.text-input {
  flex: 1 1 220px; min-width: 0; padding: 9px 11px; font-size: 14px;
  border: 1px solid var(--rule-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
}
.text-input:focus { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.swap-form-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px; }

/* Dialogs: a swap is a decision you make while looking at a timetable, so the
   week and the chat open over the board rather than navigating away from it. */
.modal-wrap {
  position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center;
  padding: 20px; background: oklch(20% 0.02 80 / 0.45);
}
.modal-card {
  background: var(--bg); border: 1px solid var(--rule-strong); border-radius: var(--radius);
  box-shadow: var(--shadow-2); padding: 18px; max-width: min(1100px, 100%);
  max-height: calc(100vh - 40px); overflow: auto;
}
.modal-card .panel-head { margin-top: 0; }
.modal-card.chat { width: min(560px, 100%); display: flex; flex-direction: column; }
.chat-log {
  flex: 1 1 auto; min-height: 220px; max-height: 50vh; overflow-y: auto;
  border-top: 1px solid var(--rule); padding-top: 10px;
}
.chat-msg {
  display: flex; align-items: baseline; gap: 8px; padding: 6px 0;
  border-bottom: 1px solid var(--rule);
}
.chat-msg.mine .chat-body { font-weight: 600; }
.chat-body { flex: 1 1 auto; font-size: 13.5px; overflow-wrap: anywhere; }
.chat-time { flex: 0 0 auto; font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* Find screen */
.find-box { margin: 14px 0 4px; display: flex; }
.find-box .text-input { width: 100%; }
/* the chevron that tells you a row goes somewhere */
.row-go { flex: 0 0 auto; color: var(--text-faint); font-size: 20px; line-height: 1; padding-left: 6px; }

/* Home: your own week leads, so the summary and actions sit tight under the
   title rather than inside yet another card. */
.home-summary { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.home-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
@media (max-width: 860px) {
  .home-actions { margin-top: 12px; }
  .home-actions .btn { flex: 1 1 auto; justify-content: center; }
}

/* ---- agenda: the week, one day at a time (phones) ---- */
.agenda-days {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 6px; margin: 14px 0 4px;
}
.agenda-day {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 9px 2px; min-height: 52px;
  background: transparent; border: 1px solid var(--rule); border-radius: var(--radius-sm);
  color: var(--text-dim); cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.agenda-day .d { font-size: 13px; font-weight: 700; }
.agenda-day .n { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.agenda-day.empty { opacity: 0.5; }
.agenda-day.on {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.agenda-day.on .n { color: var(--accent-ink); opacity: 0.75; }

.agenda-list { margin-top: 10px; }
.agenda-row {
  display: flex; align-items: stretch; gap: 11px;
  padding: 12px 0; border-top: 1px solid var(--rule);
}
.agenda-row:first-child { border-top: none; }
.agenda-time {
  flex: 0 0 56px; display: flex; flex-direction: column; gap: 1px;
  font-variant-numeric: tabular-nums; font-size: 12px; color: var(--text-faint);
}
.agenda-time strong { font-size: 14px; font-weight: 650; color: var(--text); }
.agenda-bar { flex: 0 0 3px; border-radius: 2px; }
.agenda-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.agenda-title { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.agenda-meta { font-size: 13px; color: var(--text-dim); }
.agenda-row.clash .agenda-title { color: var(--bad-55); }
.agenda-gap {
  padding: 5px 0 5px 67px; font-size: 11.5px; color: var(--text-faint);
  border-top: 1px dashed var(--rule);
}
.agenda-free { padding: 26px 0; text-align: center; color: var(--text-dim); font-size: 14px; }
