/* Tap Deck — portal design system.
 *
 * Implements `docs/TapDeck Branding - Style Guide (Midnight Heat).md`, which has
 * governed every surface but the kiosk and the guest phone since August 2026. **This file used to implement the retired portal
 * guide** — light, zero-radius, iconless, `border-radius: 0 !important` on the
 * universal selector — and Phase 13 replaced that implementation without
 * renaming a single class.
 *
 * The retired system's TOKEN names are gone as of August 2026. They survived
 * the Phase 13 swap as alias lines — `--ink-soft` pointing at `--slate`,
 * `--line-soft` at `--line` — so that a missed inline reference degraded to a
 * sensible colour instead of an unstyled hole, and they were marked "do not add
 * new uses; these exist to retire". Ten references were left; each has been
 * replaced with the token it aliased and the block deleted. The CLASS names
 * still have not changed, and that is a different decision — see below.
 *
 * ---------------------------------------------------------------------------
 * WHY THE CLASS NAMES DID NOT CHANGE
 * ---------------------------------------------------------------------------
 * The four portals share this one stylesheet, which means they cannot migrate
 * one at a time — and "a page is migrated whole or left alone" was the rule
 * precisely because a dark rounded card in a light zero-radius page is worse
 * than either system. Swapping the implementation under a stable vocabulary
 * moves all four together, in one commit, with no intermediate state that a
 * venue could see.
 *
 * It also means `dashboard.html` and its siblings keep every line of their
 * logic, which is what the design handoff asks for. The class names are
 * structural — `card`, `pill`, `micro`, `table` — and structure is what
 * survived; only what they look like changed.
 *
 * ---------------------------------------------------------------------------
 * FOUR RULINGS THIS SURFACE NEEDED, AND WHY
 * ---------------------------------------------------------------------------
 * The guide was written for the member site. Four of its rules needed a
 * decision before they could apply to a tool somebody uses monthly. All four
 * are recorded in the guide's §11 amendment block — this is the short version:
 *
 *   1. `--success` means **live presence — a person or a screen.** The guide
 *      scoped it to "players checked in right now"; a portal's status dot means
 *      a TV is online, which is the same fact about a different thing.
 *   2. The retired amber "needs a decision" becomes `--ember` ("new or worth
 *      noticing"). Not `--gold`, which means a result — a pending approval is
 *      the opposite of a result.
 *   3. `--danger` means **irreversible**, not "delete account only". The member
 *      site has exactly one irreversible act so the guide named it; a portal
 *      revokes screens, deletes events and rejects campaigns. One meaning per
 *      accent is preserved — the surface simply has more of them.
 *   4. "One filled crimson button per screen" becomes **one per card or form**.
 *      A portal page is a stack of independent tools, each with its own commit
 *      action; demoting all of them to outline buttons makes a dense page
 *      unreadable. The guide already carves out density between the surfaces.
 *
 * Structure (radius scale, type scale, spacing, the accent meanings) is
 * platform-fixed and never tenant-overridable. Tenant palettes apply to the
 * KIOSK only — no portal page calls `applyPalette` — so nothing here is
 * per-venue.
 *
 * TWO RULES GOT NO EXEMPTION, and this file used to break one of them: the
 * **11px type floor** (§3, and §14's anti-pattern list) and the 44px touch
 * target. The retired system set overlines at 10px and captions at 9.5px, and
 * that survived the Phase 13 swap because the rewrite was of colour and radius,
 * not of scale. Every size here is now at or above the floor; helper prose,
 * which is neither an overline nor a caption, sits at the guide's 12px caption
 * size. `.bars .lab` is the one 11px label that is not an overline — a chart
 * tick, at the floor rather than below it.
 *
 * Token names are the guide's values under BARE names, not its `--td-` prefix
 * (§12), which is also what the retired aliases below point at.
 */

:root {
  /* Surfaces — three levels, maximum. midnight -> obsidian -> charcoal. */
  --midnight: #0D0D0D;
  --obsidian: #1A1A2E;
  --charcoal: #2C2C3A;

  /* Ink. Warm off-white, never #FFFFFF. */
  --snow: #F5F0EB;
  --snow-75: rgba(245, 240, 235, 0.75);
  --snow-70: rgba(245, 240, 235, 0.7);
  --snow-30: rgba(245, 240, 235, 0.3);
  --slate: #6B6B80;

  --line: rgba(44, 44, 58, 0.4);
  --line-card: rgba(44, 44, 58, 0.5);
  --line-frame: rgba(44, 44, 58, 0.6);
  --line-live: rgba(230, 57, 70, 0.4);

  /* Accents. One meaning each. See ruling 1-3 above. */
  --crimson: #E63946;   /* now, and the action that commits */
  --ember: #FF6B35;     /* new, or worth noticing — needs a decision */
  --gold: #FFB830;      /* a result */
  --success: #4ADE80;   /* live presence: a person here, or a screen online */
  --danger: #F87171;    /* irreversible */
  --heat: linear-gradient(135deg, #E63946, #FF6B35);

  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 24px;
  --radius-full: 9999px;

  --display: 'Space Grotesk', system-ui, sans-serif;
  --sans: 'Inter', system-ui, sans-serif;

}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  background: var(--midnight);
  color: var(--snow);
  font: 400 14px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
}
a { color: var(--snow-75); text-decoration: none; }
a:hover { color: var(--snow); }
b, strong { font-weight: 600; }

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

.shell { display: grid; grid-template-columns: 212px 1fr; min-height: 100vh; }
.content { padding: 26px 28px; display: flex; flex-direction: column; gap: 22px; max-width: 1200px; }

.topbar {
  grid-column: 1 / -1;
  height: 52px; background: var(--obsidian); border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between; padding: 0 20px;
}
.topbar .left, .topbar .right { display: flex; align-items: center; gap: 16px; }
.topbar .right { gap: 14px; font-size: 13px; color: var(--slate); }

/*
 * The wordmark, and the one thing the guide leaves genuinely open (§15.2):
 * `Tap` + crimson `Deck` is the consumer treatment, the portal's is
 * letterspaced. The portal keeps its own — the pages pass the string "TAP DECK"
 * and letterspacing is what makes that read as a mark rather than as a heading.
 * Both are now Space Grotesk, so they are one family with two settings rather
 * than two wordmarks.
 */
.wordmark {
  font: 700 12px/1 var(--display); letter-spacing: 0.22em; color: var(--snow);
}
.topbar .divider { width: 1px; height: 18px; background: var(--line); }
.topbar .venue { font: 500 13px/1 var(--sans); }
.topbar .tag {
  font: 600 11px/1 var(--sans); letter-spacing: 0.14em; text-transform: uppercase;
  border: 1px solid var(--line-card); border-radius: var(--radius-sm);
  padding: 4px 8px; color: var(--slate);
}
.avatar {
  width: 28px; height: 28px; border-radius: var(--radius-full);
  background: var(--charcoal); color: var(--snow);
  font: 600 11px/1 var(--display);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}

.sidebar {
  background: var(--obsidian); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; padding: 18px 10px 0;
  min-height: calc(100vh - 52px);
}
.navgroup { padding-bottom: 14px; }
.navgroup > .glabel {
  font: 600 11px/1.4 var(--sans); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--slate); padding: 0 12px 8px;
}
/*
 * The active row (§7): a charcoal fill, a crimson label in Grotesk, AND a 3px
 * crimson left border. The guide asks for all three — an earlier note here read
 * the fill as having replaced the border, which was a misreading; §7 spells out
 * "3px transparent left border" on every row precisely so the active one can
 * take colour without shifting the label by three pixels.
 */
.navitem {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; min-height: 44px;
  border-radius: var(--radius-md);
  border-left: 3px solid transparent;
  font: 500 13px/1 var(--sans); color: var(--snow-70); cursor: pointer;
}
.navitem:hover { color: var(--snow); }
.navitem .bullet { width: 5px; height: 5px; border-radius: var(--radius-full); background: var(--slate); flex: none; }
.navitem.active {
  background: var(--charcoal);
  border-left-color: var(--crimson);
  color: var(--crimson);
  font-family: var(--display);
  font-weight: 600;
}
.navitem.active .bullet { background: var(--crimson); }

.sidefoot {
  margin-top: auto; border-top: 1px solid var(--line); padding: 14px 12px; color: var(--snow);
}
.sidefoot .glabel {
  font: 600 11px/1.4 var(--sans); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--slate); margin-bottom: 6px;
}
.sidefoot .big { font: 700 20px/1.2 var(--display); letter-spacing: -0.01em; }
.sidefoot .sub { font-size: 12px; color: var(--slate); }
.sidefoot .track { height: 3px; border-radius: var(--radius-full); background: var(--charcoal); margin-top: 8px; }
.sidefoot .fill { height: 3px; border-radius: var(--radius-full); background: var(--crimson); }
.sidefoot .hint { font-size: 12px; line-height: 1.45; color: var(--slate); margin-top: 8px; }

/* ---- type ----------------------------------------------------------------- */
/* Space Grotesk announces, Inter explains — the guide's load-bearing split. */

h1.page { font: 700 28px/1.25 var(--display); letter-spacing: -0.01em; }
h1.greet { font: 700 32px/1.2 var(--display); letter-spacing: -0.015em; }
.sub { color: var(--slate); font-size: 13px; }
.micro {
  font: 600 11px/1.4 var(--sans); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--slate);
}
/*
 * `.mono` was JetBrains Mono in the retired system, where machine values were
 * set in a monospace face. The guide has two families and neither is mono, so
 * this is now Grotesk at a tabular setting: an id or a timestamp still reads as
 * a machine value, without a third font.
 */
.mono { font-family: var(--display); font-size: 12px; font-variant-numeric: tabular-nums; }
.meta { font-size: 12px; color: var(--slate); }
.warn-text { color: var(--ember); }
.pagehead { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
.pagehead .actions { display: flex; gap: 10px; flex: none; }

/* ---- components ----------------------------------------------------------- */

.card {
  background: var(--obsidian); border: 1px solid var(--line-card);
  border-radius: var(--radius-lg); padding: 20px;
}
.card.flush { padding: 0; overflow: hidden; }
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
}
.card-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 14px 18px; border-top: 1px solid var(--line); font-size: 12.5px; color: var(--slate);
}
.card-body { padding: 18px; display: flex; flex-direction: column; gap: 12px; }
.card h3 { font: 600 16px/1.3 var(--display); letter-spacing: -0.01em; }

/*
 * `.dark` meant "invert this card" in a light system. Inverting a dark system
 * would produce the one thing the guide forbids outright — a card inside a
 * card — so it is now the guide's inset instead: the same emphasis, one surface
 * level down rather than up.
 */
.dark { background: var(--midnight); border: 1px solid var(--line); color: var(--snow); }
.dark .micro { color: var(--slate); }
.dark p { color: var(--snow-75); font-size: 13px; line-height: 1.6; }

.note {
  background: var(--midnight); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 14px 18px; font-size: 12.5px; line-height: 1.6; color: var(--snow-75);
}

/*
 * The status dot. An 8px circle — never a ring, never a square (§7).
 * `portal.js` emits this class from `dot()`; it used to emit a 6px square.
 */
.dot {
  display: inline-block; width: 8px; height: 8px; border-radius: var(--radius-full);
  background: var(--charcoal); vertical-align: baseline;
}
.dot.on { background: var(--success); }     /* online / running — live presence */
.dot.warn { background: var(--ember); }     /* needs a decision */
/*
 * A step that is finished. Gold, not green: §2 gives `--success` exactly one
 * meaning — live presence, a person here now or a screen online — and a
 * completed setup step is neither. It is a RESULT, which is what gold means.
 * Reusing green for "done" is the same accent carrying two jobs, which is the
 * rule §2 exists to hold.
 */
.dot.done { background: var(--gold); }

.pill {
  font: 600 11px/1 var(--display); letter-spacing: 0.02em; text-transform: uppercase;
  padding: 6px 10px; display: inline-block; white-space: nowrap;
  border: 1.5px solid var(--snow-30); border-radius: var(--radius-sm);
  color: var(--snow); background: transparent;
}
/* Outline asks, fill confirms — so a state you are IN gets a fill. */
/*
 * Two meanings used to share this class, and §2 allows an accent exactly one.
 *
 *   `.live`  — success green. Live presence: a campaign running now, a screen
 *              on air, a special showing on a TV at this moment.
 *   `.good`  — gold. A RESULT: approved, enrolled, completed, paid, on the bill.
 *
 * A paid invoice is not a live thing and a running campaign is not a finish;
 * painting both green made the dot on a screens table mean nothing in
 * particular, which is how an accent stops carrying information.
 */
.pill.live { background: rgba(74, 222, 128, 0.15); border: 1px solid var(--success); color: var(--success); }
.pill.good { background: rgba(255, 184, 48, 0.15); border: 1px solid var(--gold); color: var(--gold); }
.pill.count { background: var(--charcoal); border-color: transparent; color: var(--snow); border-radius: var(--radius-full); }
.pill.decide { background: rgba(255, 107, 53, 0.15); border: 1px solid var(--ember); color: var(--ember); }

button, .btn {
  font: 600 13px/1 var(--display); letter-spacing: 0.02em; text-transform: uppercase;
  cursor: pointer; white-space: nowrap;
  background: transparent; color: var(--snow);
  border: 1.5px solid var(--snow-30); border-radius: var(--radius-md);
  padding: 0 16px; min-height: 44px;
  transition: filter 0.12s ease;
}
button:hover, .btn:hover { filter: brightness(1.15); }
button:active, .btn:active { filter: brightness(0.9); }
/* Ruling 4: one per card or form, not one per screen. */
.btn-primary { background: var(--crimson); color: var(--snow); border-color: var(--crimson); }
.btn-commit { border-color: var(--snow-30); }
.btn-danger { color: var(--danger); border-color: rgba(248, 113, 113, 0.5); }
.btn-link {
  border: none; background: none; padding: 0; min-height: 0;
  color: var(--crimson); font: 500 12.5px/1.5 var(--sans);
  letter-spacing: 0; text-transform: none;
}
.btn-link:hover { color: var(--crimson); text-decoration: underline; filter: none; }
button:disabled { opacity: 0.4; cursor: default; }
button:disabled:hover { filter: none; }
.btn-block { width: 100%; }

label.field { display: flex; flex-direction: column; gap: 6px; }
label.field > .micro { color: var(--slate); }
input, select, textarea {
  font: 400 14px/1.5 var(--sans); color: var(--snow);
  background: var(--charcoal); border: 1.5px solid transparent;
  border-radius: var(--radius-md); padding: 11px 12px; width: 100%;
  min-height: 44px;
}
input::placeholder, textarea::placeholder { color: var(--slate); }
/* 1.5px in both states, so focus never shifts layout by a pixel (§5). */
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--crimson); }
button:focus-visible, .btn:focus-visible, .navitem:focus-visible {
  outline: none; box-shadow: 0 0 0 2px rgba(230, 57, 70, 0.5);
}
.helper { font-size: 12px; color: var(--slate); }

/*
 * The live screen preview: the real kiosk, in a TV-shaped box.
 *
 * 16:9 because that is the shape of the thing on the wall. Getting this wrong
 * would make the preview worse than useless — a venue would lay out a rotation
 * against one aspect and hang it in another.
 *
 * The iframe is a FIXED 1280x720 scaled with `transform`, not a fluid box.
 * A fluid iframe would re-run the kiosk's own responsive rules at whatever
 * width the card happens to be, so the preview would show a layout no TV ever
 * renders — smaller type reflowing into more lines, exactly the mistake this is
 * meant to catch. Scaling a fixed viewport shrinks the real thing instead, so
 * text that is too small across a room is too small here too.
 *
 * The scale factor cannot be written here: it is the card's width divided by
 * 1280, and CSS has no way to divide by a container's measured width. So
 * `--tv-scale` is set by a ResizeObserver in dashboard.html and this file only
 * says what to do with it. The fallback matters — if that script never runs the
 * preview is small rather than a 1280px iframe overflowing its card.
 */
.tv-frame {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line-card);
  background: var(--midnight);
  overflow: hidden;
  position: relative;
}
.tv-screen {
  position: absolute;
  top: 0; left: 0;
  width: 1280px; height: 720px;
  border: 0;
  transform: scale(var(--tv-scale, 0.24));
  transform-origin: top left;
  /* The kiosk is interactive; the preview is not. Clicking through to a page
     that thinks it is a TV would let someone drive a screen from a card. */
  pointer-events: none;
}

/*
 * Field hints — an explanation the reader opens, rather than one every reader
 * has to scroll past.
 *
 * Slate, not crimson. A form gets one crimson fill and it is the submit button;
 * an accent on a help toggle is §14's "accent as decoration" and it would also
 * compete with the action the form exists for. The chevron is the character
 * `›`/`▾` (§8: no icons), rotated by content rather than by transform so a
 * screen reader hears the state from `aria-expanded` and nothing else.
 */
.hint-toggle {
  align-self: flex-start;
  border: none; background: none; padding: 2px 0; min-height: 0;
  display: inline-flex; align-items: baseline; gap: 6px;
  color: var(--slate); font: 500 12px/1.5 var(--sans);
  letter-spacing: 0; text-transform: none; cursor: pointer;
}
.hint-toggle:hover { color: var(--snow-75); text-decoration: underline; filter: none; }
.hint-toggle .chev { font-size: 12px; line-height: 1; }

.hint-body {
  margin: 0;
  border-left: 2px solid var(--line);
  padding: 2px 0 2px 12px;
  font-size: 12px; line-height: 1.6; color: var(--snow-75);
}

.check { display: flex; align-items: center; gap: 10px; font-size: 13px; cursor: pointer; min-height: 44px; }
.check .box {
  width: 18px; height: 18px; border-radius: var(--radius-sm);
  border: 1.5px solid var(--snow-30); background: var(--charcoal); flex: none;
}
.check.on .box { background: var(--crimson); border-color: var(--crimson); }

table { width: 100%; border-collapse: collapse; background: transparent; }
th {
  font: 600 11px/1.4 var(--sans); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--slate); text-align: left;
  padding: 12px 18px; border-bottom: 1px solid var(--line);
}
td { font-size: 13.5px; padding: 14px 18px; border-bottom: 1px solid var(--line); vertical-align: middle; }
tr:last-child td { border-bottom: 0; }
td:first-child { font-weight: 500; }
/* A row that needs a decision. Ember, per ruling 2, at the alpha a fill takes. */
tr.warnrow td { background: rgba(255, 107, 53, 0.08); }
tr.dim td { color: var(--slate); font-weight: 400; }

/*
 * KPI strip. The retired system drew it as a 1px grid of white tiles on a grey
 * line; here the line IS the border and the tiles are the card surface, because
 * a gap-as-border trick reads as a seam on a dark ground.
 */
.kpis {
  display: grid; gap: 1px; background: var(--line);
  border: 1px solid var(--line-card); border-radius: var(--radius-lg); overflow: hidden;
}
.kpis > div { background: var(--obsidian); padding: 18px 20px; }
.kpis .num { font: 700 28px/1.2 var(--display); letter-spacing: -0.01em; }
.kpis .num small { font: 400 14px/1 var(--sans); color: var(--slate); }

.attn { display: flex; flex-direction: column; gap: 12px; }
.attn > div { border-left: 3px solid var(--charcoal); padding-left: 12px; font-size: 13px; line-height: 1.5; }
.attn > div.warn { border-left-color: var(--ember); }
.attn > div.good { border-left-color: var(--gold); }   /* a result, not presence (§2) */

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; align-items: start; }
.split { display: grid; grid-template-columns: 1.4fr 1fr; gap: 20px; align-items: start; }
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.stack { display: flex; flex-direction: column; gap: 12px; }

.ph {
  border-radius: var(--radius-md); border: 1px solid var(--line);
  background: repeating-linear-gradient(135deg, rgba(44,44,58,0.55) 0 12px, rgba(26,26,46,0.9) 12px 24px);
  display: flex; align-items: flex-end; padding: 8px 10px;
}
.ph .cap { font: 600 11px/1.4 var(--sans); letter-spacing: 0.1em; color: var(--slate); text-transform: uppercase; }

.tabsrow { display: flex; border-bottom: 1px solid var(--line); }
.tabsrow button {
  border: none; background: none; border-radius: 0; padding: 12px 16px;
  color: var(--slate); border-bottom: 2px solid transparent; min-height: 44px;
}
.tabsrow button:hover { color: var(--snow); filter: none; }
.tabsrow button.active { color: var(--crimson); border-bottom-color: var(--crimson); }

.choice {
  border: 1.5px solid var(--line-card); border-radius: var(--radius-md);
  padding: 16px 18px; cursor: pointer; background: var(--obsidian);
}
.choice:hover { border-color: var(--snow-30); }
.choice.selected { border-color: var(--crimson); }
.choice .t { font: 600 15px/1.3 var(--display); }
.choice .d { font-size: 12.5px; color: var(--slate); margin-top: 4px; }

.codebox {
  font-family: var(--display); font-size: 12px; font-variant-numeric: tabular-nums;
  border: 1px solid var(--line-card); border-radius: var(--radius-md);
  padding: 11px 12px; white-space: nowrap; overflow: hidden;
  background: var(--charcoal); color: var(--snow);
}

/* auth (marketing-style) */
.authwrap { min-height: 100vh; display: grid; grid-template-columns: 1fr 480px; }
.authwrap .pitch {
  padding: 64px 48px; display: flex; flex-direction: column; gap: 28px;
  border-right: 1px solid var(--line);
}
.authwrap .pitch h1 { font: 700 40px/1.1 var(--display); letter-spacing: -0.02em; max-width: 520px; }
.authwrap .pitch .lede { font-size: 16px; line-height: 1.65; color: var(--snow-75); max-width: 480px; }
.authwrap .formcol {
  background: var(--obsidian); border-left: 1px solid var(--line);
  padding: 48px 44px; display: flex; flex-direction: column; gap: 20px;
}
/* The `--dusk` band: the guide allows it on a marketing hero and nowhere else. */
.authdark {
  background: linear-gradient(180deg, #1A1A2E, #0D0D0D); color: var(--snow);
  padding: 56px 48px; display: flex; flex-direction: column; justify-content: space-between;
}
.authdark h1 { font: 700 32px/1.2 var(--display); letter-spacing: -0.02em; }
.authdark p { color: var(--snow-75); font-size: 14px; line-height: 1.7; }
.statline { font: 600 11px/1.4 var(--sans); letter-spacing: 0.1em; color: var(--slate); }

#toast {
  position: fixed; bottom: 20px; right: 20px; padding: 14px 20px; z-index: 99; max-width: 360px;
  background: var(--obsidian); color: var(--snow);
  border: 1px solid var(--line-card); border-radius: var(--radius-md);
  border-left: 3px solid var(--gold); font-size: 13px; display: none;   /* "saved" is a result */
}
#toast.err { border-left-color: var(--danger); }

.bars { display: flex; align-items: flex-end; gap: 10px; height: 78px; }
.bars > div { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.bars .lab { font: 400 11px/1.4 var(--sans); color: var(--slate); text-align: center; margin-top: 4px; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}

@media (max-width: 900px) {
  .shell { grid-template-columns: 1fr; }
  .sidebar {
    flex-direction: row; overflow-x: auto; min-height: 0;
    /* `min-width: 0` is what stops the nav from widening the grid track it
       lives in: without it, a row of items wider than the screen makes the
       whole PAGE scroll sideways rather than just the nav. Measured at 414px,
       where it cost 45px of body overflow. */
    min-width: 0;
    border-right: 0; border-bottom: 1px solid var(--line); padding: 8px;
  }
  .navgroup { padding-bottom: 0; display: flex; align-items: center; flex: none; }
  .navitem { flex: none; }
  /* Two stacked actions rather than a squeezed row. */
  .pagehead { flex-direction: column; align-items: stretch; gap: 12px; }
  .pagehead .actions { flex-wrap: wrap; }
  .content { min-width: 0; }
  table { display: block; overflow-x: auto; white-space: nowrap; }
  .navgroup > .glabel { display: none; }
  .sidefoot { display: none; }
  .content { padding: 16px; }
  .grid2, .grid3, .split { grid-template-columns: 1fr; }
  .authwrap { grid-template-columns: 1fr; }
}
