/* Podium — shared styles for the display and the controller. */

:root {
  --bg: #0b0d10;
  --panel: #14181d;
  --panel-2: #1c2127;
  --line: #2a3038;
  --ink: #e8ecf1;
  --dim: #97a2b0;
  /* Text fields and dropdowns: a shade darker than a panel here, white in
     every light scope below - the light theme, and a light display sheet. */
  --field: #0e1216;
  /* The deck editor's markdown colours, each readable on its own panel. */
  --code-heading: #ffd166; --code-sep: #ffc94d; --code-link: #6ea8fe; --code-mono: #ffb38a;
  --code-dim: #97a2b0; --code-quote: #b7c0cc; --code-comment: #7fc8a9; --code-tag: #ff8fa3;
  --code-string: #a5d6a7; --code-keyword: #c49bff;
  --accent: #6ea8fe;
  --live: #ff4d4f;
  /* A red FILL with white text on it (Take, Laser, a confirmed delete).
     --live itself is kept for red text and dots, where it reads fine on the
     dark panels, but white on it is 3.27:1 - under WCAG AA's 4.5:1 for text
     this size. This is 4.98:1 (Issue #156). */
  --live-fill: #d32f2f;
  /* Text on an --accent fill (a selected button, Go live): dark here, white in
     the light theme, whose accent is a deeper blue (Issue #210). */
  --on-accent: #07101f;
  --cue: #ffc94d;
  --ok: #45d483;
  /* Words that say something went wrong (or is about to be destroyed), and
     words that say something is out of date. One token each, so a new rule
     can't pick a pink that only reads on dark again; the light theme
     redefines both. */
  --bad: #ffb3b3;
  --warn: #ffd9a8;
  --radius: 12px;
  --tap: 46px;
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* Author rules like .sheet{display:grid} outrank the UA's [hidden] rule, which
   would leave dismissed overlays invisible but still swallowing taps. */
[hidden] { display: none !important; }

/* Issue #156: where keyboard focus is, always visible against the dark
   panels - the browser's own ring varies from barely-there to nothing on
   some of these backgrounds. :focus-visible, so a tap on an iPad does not
   leave a ring behind on the button it pressed. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Read by a screen reader, not shown - e.g. #sr-announce on the controller. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.45 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

button, input, select, textarea { font: inherit; color: inherit; }

button {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0 14px;
  min-height: var(--tap);
  cursor: pointer;
  touch-action: manipulation;
}
button:hover { background: #242b33; }
button:active { transform: translateY(1px); }
button:disabled { opacity: .4; cursor: default; }
button.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
button.is-danger { background: var(--live-fill); border-color: var(--live-fill); color: #fff; font-weight: 600; }
button.danger-outline { border-color: #6b2b2b; color: #ff9d9d; }

input[type=text], input[type=url], input[type=number], input[type=password], input[type=search], input[type=email], select, textarea {
  background: var(--field);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 11px 12px;
  width: 100%;
  min-height: var(--tap);
}
textarea { min-height: 96px; resize: vertical; }
label { display: block; font-size: 13px; color: var(--dim); margin-bottom: 5px; }
.field { margin-bottom: 12px; }
.hint { color: var(--dim); font-size: 13px; margin: 6px 0 0; }
/* Issue #174: "this is the cue, not the screen" - in the cue's own colour. */
.hint.is-cued-note { color: var(--cue); font-weight: 600; }
/* A deck kept for reference while something else is up (Issue #216). */
.deck-recall { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 6px 0 2px; }
.deck-recall .hint { color: var(--cue); font-weight: 600; flex: 1 1 220px; }
.deck-recall .big-button { flex: none; }
.error { color: var(--bad); font-size: 13px; min-height: 18px; }

/* ===================== DISPLAY ====================================
   Both pages share this stylesheet, so anything positioned or sized here is
   scoped to body.display. An unscoped #blank once turned the controller's
   Blank button into an invisible full-screen layer that ate every tap. */

body.display { overflow: hidden; cursor: default; }
body.display.is-live { cursor: none; }

body.display #stage { position: fixed; inset: 0; background: #000; display: grid; }

/* Splitting the screen (see LAYOUTS in protocol.js) is a CSS grid over
   #stage: each layout picks its own template and which cells its panels
   occupy, and a panel not shown by the current layout is simply not
   rendered (a grid item that is display:none takes no space), so no
   per-layout JS sizing math is needed here. */
#stage.layout-single { grid-template-columns: 1fr; grid-template-rows: 1fr; }
#stage.layout-2h { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr; }
#stage.layout-2v { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; }
#stage.layout-3 { grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; }
#stage.layout-4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }

.panel-slot { position: relative; overflow: hidden; background: #000; display: none; }
.panel-slot.is-on { display: block; }
#stage.layout-single .panel-slot[data-panel='a'] { grid-column: 1; grid-row: 1; }
#stage.layout-2h .panel-slot[data-panel='a'] { grid-column: 1; grid-row: 1; }
#stage.layout-2h .panel-slot[data-panel='b'] { grid-column: 2; grid-row: 1; }
#stage.layout-2v .panel-slot[data-panel='a'] { grid-column: 1; grid-row: 1; }
#stage.layout-2v .panel-slot[data-panel='b'] { grid-column: 1; grid-row: 2; }
#stage.layout-3 .panel-slot[data-panel='a'] { grid-column: 1; grid-row: 1 / 3; }
#stage.layout-3 .panel-slot[data-panel='b'] { grid-column: 2; grid-row: 1; }
#stage.layout-3 .panel-slot[data-panel='c'] { grid-column: 2; grid-row: 2; }
#stage.layout-4 .panel-slot[data-panel='a'] { grid-column: 1; grid-row: 1; }
#stage.layout-4 .panel-slot[data-panel='b'] { grid-column: 2; grid-row: 1; }
#stage.layout-4 .panel-slot[data-panel='c'] { grid-column: 1; grid-row: 2; }
#stage.layout-4 .panel-slot[data-panel='d'] { grid-column: 2; grid-row: 2; }
/* A split's seams, so two adjacent panels don't visually run together. */
#stage:not(.layout-single) .panel-slot { outline: 1px solid #000; }

/* Picture-in-picture (Issue #110): unlike every layout above, which panes
   go where is a fixed rule per letter - here it is state.pip's own choice
   (main/inset can be any two of A-D), so positioning is driven by JS-set
   role classes rather than [data-panel='x'] selectors. A single grid cell
   is what makes "main fills the stage" need no rule of its own: it is just
   an ordinary panel-slot with nothing pulling it out of flow, the same as
   .layout-single's lone panel. */
#stage.layout-pip { grid-template-columns: 1fr; grid-template-rows: 1fr; }
#stage.layout-pip .panel-slot { grid-column: 1; grid-row: 1; }
#stage.layout-pip .panel-slot.is-pip-main { z-index: 1; }
#stage.layout-pip .panel-slot.is-pip-inset {
  position: absolute; z-index: 5;
  width: 20%; height: 20%;
  border: 3px solid #fff; border-radius: 6px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, .55);
}
#stage.layout-pip .panel-slot.is-pip-inset.corner-tl { top: 5%; left: 5%; }
#stage.layout-pip .panel-slot.is-pip-inset.corner-tr { top: 5%; right: 5%; }
#stage.layout-pip .panel-slot.is-pip-inset.corner-bl { bottom: 5%; left: 5%; }
#stage.layout-pip .panel-slot.is-pip-inset.corner-br { bottom: 5%; right: 5%; }

.layer { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
.layer[data-role='program'] { opacity: 1; z-index: 2; pointer-events: auto; }
.layer[data-role='preview'] { opacity: 0; z-index: 1; }
.layer[data-role='idle'] { opacity: 0; z-index: 0; }

/* width/height are NOT redundant next to inset:0 here, and leaving them off
   is a real bug: a <canvas> is a replaced element, so an absolutely
   positioned one with `width:auto` takes its INTRINSIC width - its width
   attribute, read as CSS pixels - rather than stretching to left:0/right:0.
   That attribute is the backing store, which is sized in DEVICE pixels, so
   on a 2x screen the canvas is laid out at twice the viewport, anchored top
   left, and the window shows you the top-left quarter of it: ink at double
   size, drifting further off the further from the corner you draw. Stating
   the size explicitly is what pins the element's box to the viewport it is
   supposed to cover. (#pad on the controller always had this; #ink did
   not, which is why the pad was fine and the projector was not.) */
body.display #ink {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: 5; pointer-events: none;
}
body.display #blank {
  position: fixed; inset: 0; z-index: 6; background: #000;
  opacity: 0; transition: opacity .18s ease; pointer-events: none;
}
body.display #blank.is-on { opacity: 1; }

body.display #overlay {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 7;
  padding: 3vh 5vw calc(3vh + env(safe-area-inset-bottom));
  background: linear-gradient(transparent, rgba(0, 0, 0, .82) 45%);
  font-size: clamp(24px, 4.2vw, 68px); font-weight: 600; line-height: 1.2;
  text-shadow: 0 2px 18px #000;
  transform: translateY(110%); transition: transform .25s ease;
}
body.display #overlay.is-on { transform: none; }

/* A name or logo pinned to one corner for the whole lecture, not content - so
   it sits above everything else on the stage, including blank and ink: a
   watermark that a stray pen stroke could cover, or that vanished the moment
   someone hit the panic button, would not do the one job it has, which is to
   still be there when a screen grab is taken later. */
body.display #watermark {
  position: fixed; right: 0; bottom: 0; z-index: 8; pointer-events: none;
  padding: 1.6vh 1.8vw; max-width: 30vw;
  font-size: clamp(12px, 1.7vw, 22px); font-weight: 600; color: rgba(255, 255, 255, .82);
  text-shadow: 0 1px 6px rgba(0, 0, 0, .6);
  opacity: 0; transition: opacity .18s ease;
}
body.display #watermark.is-on { opacity: 1; }
body.display #watermark.pos-tl { right: auto; bottom: auto; left: 0; top: 0; text-align: left; }
body.display #watermark:not(.pos-tl) { text-align: right; }
body.display #watermark img { display: block; max-height: 10vh; max-width: 26vw; }

/* The laser's colour is one custom property, set from a data-color attribute,
   so the dot on the projector and the dot on the controller's own preview are
   the same three colours from the same place. Red is what everyone expects;
   green survives a dark slide or a photograph, where red disappears into it;
   blue reads on a bright one. */
.laser-red { --laser: 255, 64, 64; }
.laser-green { --laser: 60, 235, 120; }
.laser-blue { --laser: 90, 165, 255; }

/* The white-hot core and the dark rim are not decoration: a coloured blob is
   only as visible as the contrast between its hue and whatever is behind it,
   and a slide can be any colour - including, as it happens, the exact green of
   the green pointer. A near-white centre and a thin dark ring read against any
   of them, so picking the "wrong" colour for a slide costs you brightness
   rather than the dot itself. */
body.display #spotlight {
  position: fixed; inset: 0; z-index: 5;
  pointer-events: none; opacity: 0;
  transition: opacity .12s ease;
  --spotlight-x: 50vw;
  --spotlight-y: 50vh;
  --spotlight-radius: 120px;
  background: radial-gradient(
    circle var(--spotlight-radius) at var(--spotlight-x) var(--spotlight-y),
    transparent 0%,
    transparent 85%,
    rgba(0, 0, 0, .45) 94%,
    rgba(0, 0, 0, .72) 100%
  );
}
body.display #spotlight.is-on { opacity: 1; }

body.display #laser {
  position: fixed; top: 0; left: 0; width: 34px; height: 34px; z-index: 6;
  border-radius: 50%; pointer-events: none; opacity: 0;
  --laser: 255, 64, 64;
  background: radial-gradient(circle,
    #fff 0%, rgba(var(--laser), .97) 26%, rgba(var(--laser), .55) 52%, transparent 74%);
  box-shadow: 0 0 18px 5px rgba(var(--laser), .6), 0 0 0 1.5px rgba(0, 0, 0, .38);
  transform: translate(-50%, -50%);
}
body.display #laser[data-color='green'] { --laser: 60, 235, 120; }
body.display #laser[data-color='blue'] { --laser: 90, 165, 255; }
body.display #laser.is-on { opacity: 1; }

body.display #hud {
  position: fixed; right: 14px; bottom: 12px; z-index: 9;
  padding: 7px 13px; border-radius: 999px;
  background: rgba(10, 14, 20, .82); border: 1px solid var(--line);
  font-size: 13px; color: var(--dim); transition: opacity .5s ease 2.5s;
}
body.display #hud.is-quiet { opacity: 0; }
body.display #hud[data-status='error'], body.display #hud[data-status='mismatch'] { color: #ffb3b3; border-color: #6b2b2b; opacity: 1; }
body.display #hud[data-status='offline'] { color: var(--cue); opacity: 1; }

body.display #storage-warn {
  position: fixed; left: 50%; top: 12px; z-index: 10; transform: translateX(-50%);
  padding: 7px 14px; border-radius: 999px;
  background: #2a1216; border: 1px solid #6b2b2b; color: #ffc4c4;
  font-size: 13px; font-weight: 600;
}
body.display #storage-warn[hidden] { display: none; }

body.display.is-frozen #stage::after {
  content: ''; position: absolute; inset: 0; z-index: 8; pointer-events: none;
  border: 3px solid var(--cue); opacity: .5;
}

/* Full-screen overlays on the display.
   These all share a stacking context, so they need an explicit ladder: with a
   single z-index the last one in the document wins, which put the pairing QR
   *underneath* the arming screen and made "Pair a device" look like it did
   nothing. Order here is the order you want to see them in. */
.sheet {
  position: fixed; inset: 0; z-index: 20; display: grid; place-items: center;
  background: radial-gradient(120% 120% at 50% 0%, #131a24, #07090c);
  padding: 24px; overflow: auto;
  /* A live display hides the pointer (body.display.is-live). Any sheet over it
     is something you are meant to click, so give the pointer back here rather
     than leaving whoever is at the lectern clicking Done blind. */
  cursor: default;
}
#standby { z-index: 20; }
#arm { z-index: 21; }
#pair { z-index: 22; }
#setup { z-index: 23; }
/* Above the rest: the shortcut card answers "how do I get out of this?", which
   is asked with something else already on screen. */
#keys { z-index: 24; }
#modal-countdown { z-index: 25; }
#slide-popout { z-index: 25; }
.card {
  background: var(--panel); border: 1px solid var(--line); border-radius: 18px;
  padding: 28px; max-width: 620px; width: 100%;
}
.card h1 { margin: 0 0 6px; font-size: 26px; }
.card h2 { margin: 0 0 14px; font-size: 19px; }
.card p { color: var(--dim); margin: 0 0 18px; }
.big-button {
  width: 100%; min-height: 66px; font-size: 19px; font-weight: 600;
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.arm-window {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.pair-button  {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.arm-settings {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.row { display: flex; gap: 10px; flex-wrap: wrap; }
.row > * { flex: 1; }

body.display #standby .card { text-align: center; }
#standby .room { font-size: 15px; color: var(--dim); }
#standby .room b { color: var(--ink); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
body.display #standby:not(.is-on) { display: none; }

/* The three pairing modes, by what they let someone do (Issue #203). */
.pair-mode-btn { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; padding: 8px 14px; line-height: 1.25; }
.pair-mode-btn span { font-size: 12px; color: var(--dim); font-weight: 400; }
.pair-mode-btn.is-on span { color: inherit; opacity: .8; }
#pair-mode { flex-wrap: wrap; justify-content: center; }
#pair-qr svg { width: min(56vh, 340px); height: auto; background: #fff; padding: 12px; border-radius: 12px; }
#pair-url {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
  color: var(--dim); word-break: break-all; margin-top: 12px;
}
.warn {
  background: #2a1e12; border: 1px solid #6b4a22; color: #ffd9a8;
  border-radius: 10px; padding: 10px 12px; font-size: 13px; margin-bottom: 16px;
}
/* The same box, for a problem past "worth knowing" - matches the red already
   used for #storage-warning and #link-help.is-mismatch. */
.warn.is-bad { background: #2a1216; border-color: #6b2b2b; color: #ffc4c4; }

kbd {
  display: inline-block; min-width: 30px; padding: 3px 8px;
  background: var(--panel-2); border: 1px solid var(--line);
  border-bottom-width: 2px; border-radius: 7px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px;
  text-align: center; color: var(--ink);
}

/* Key on the left, what it does on the right, aligned down the card. */
dl.keys {
  display: grid; grid-template-columns: auto 1fr; gap: 10px 14px;
  align-items: baseline; margin: 0 0 18px;
}
dl.keys dt { justify-self: start; }
dl.keys dd { margin: 0; color: var(--dim); }

/* ===================== RENDERERS ==================================
   Renderers size themselves against their own box, not the viewport, so the
   same markup fills a projector and shrinks honestly into the controller's
   preview thumbnail. vw/vh here would measure the whole screen and blow the
   content far outside a 300px preview. */

.layer, #preview-stage, #item-preview, #message-preview-box { container-type: size; }
.r-fill, .r-black, .r-text, .r-qr, .r-timer, .r-whiteboard, .r-audio, .r-set, .r-poll, .r-att { position: absolute; inset: 0; }
/* What the room sees is never themed (theme.js): the projector's page always
   has the dark palette. Previews of it on a controller, planner or My Files
   in Light mode get the same colours, so a big-text card is not drawn dark
   navy on black on the iPad while the room sees it white. Each renderer's
   own colours, further down, still win. */
.r-fill, .r-black, .r-text, .r-qr, .r-timer, .r-whiteboard, .r-audio, .r-set, .r-poll, .r-att, .r-camera {
  --bg: #0b0d10; --panel: #14181d; --panel-2: #1c2127; --line: #2a3038;
  --ink: #e8ecf1; --dim: #97a2b0; --accent: #6ea8fe; --cue: #ffc94d; --ok: #45d483; --live: #ff4d4f;
  color: var(--ink);
}
.r-black { background: #000; }
.r-image, .r-video { width: 100%; height: 100%; display: block; background: #000; }
.r-frame { width: 100%; height: 100%; border: 0; background: #fff; }
.r-pdf { display: flex; align-items: center; justify-content: center; background: #000; overflow: hidden; }
.r-pdf-canvas { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
/* Issue #262: zoomed, the canvas is the panel's own shape and fills it. */
.r-pdf-canvas.is-zoomed { width: 100%; height: 100%; max-width: none; max-height: none; }
.r-image-box { overflow: hidden; }
.r-image.is-zoomed { position: absolute; max-width: none; max-height: none; object-fit: fill; background: transparent; }

.r-web { background: #101418; }
.r-embed-warn {
  position: absolute; inset: auto 0 0 0; padding: 1.4cqh 1.8cqw; color: var(--dim);
  background: rgba(8, 11, 15, .9); font-size: clamp(7px, 1.4cqw, 14px); text-align: center;
}
.r-web.is-loaded .r-embed-warn { display: none; }
.r-embed-url { font-family: ui-monospace, Menlo, monospace; font-size: clamp(6px, 1.2cqw, 12px); opacity: .7; margin-top: 4px; }

/* The live browser (renderLiveWeb): pictures of a page running on the server,
   letterboxed if a preview is not the projector's shape. */
.r-live { background: #101418; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.r-live-canvas { display: block; width: 100%; height: 100%; object-fit: contain; }
.r-live:not(.has-frame) .r-live-canvas { visibility: hidden; }
.r-live-status {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); max-width: 80%;
  padding: 1.2cqh 2cqw; border-radius: 10px; color: #dfe6ee; background: rgba(8, 11, 15, .82);
  font-size: clamp(8px, 1.6cqw, 18px); text-align: center;
}
.r-live.has-frame .r-live-status { top: auto; bottom: 3cqh; transform: translateX(-50%); }
.r-live.is-error .r-live-status { color: #ffd7d2; }
.r-live-status[hidden] { display: none; }
/* Said instead of a frame the site would refuse. */
.r-webcard { display: flex; align-items: center; justify-content: center; background: linear-gradient(160deg, #182130, #0a0d12); color: #eef2f6; text-align: center; }
.r-webcard-body { max-width: 80%; display: grid; gap: 1.6cqh; justify-items: center; }
.r-webcard-icon { font-size: clamp(16px, 9cqh, 96px); line-height: 1; }
.r-webcard-title { font-size: clamp(10px, 4.6cqh, 48px); font-weight: 700; }
.r-webcard-url { font-family: ui-monospace, Menlo, monospace; font-size: clamp(7px, 2.2cqh, 22px); color: #8fc2ff; word-break: break-all; }
.r-webcard-note { font-size: clamp(7px, 2.6cqh, 26px); color: #b8c3cf; line-height: 1.35; }

.r-audio {
  display: grid; grid-auto-flow: column; align-items: center; justify-content: center;
  gap: 5cqw; background: linear-gradient(160deg, #182130, #0a0d12);
}
.r-audio-art { width: 30cqh; height: 30cqh; object-fit: cover; border-radius: 2cqh; }
.r-audio-title { font-size: clamp(11px, 4cqw, 64px); font-weight: 700; }
.r-audio-sub { font-size: clamp(8px, 2cqw, 30px); color: var(--dim); margin-top: 1cqh; }
.r-bars { display: flex; gap: 0.6cqw; align-items: flex-end; height: 10cqh; margin-top: 4cqh; }
.r-bars span { width: 0.8cqw; height: 2cqh; background: var(--accent); border-radius: 0.4cqw; }
.r-audio.is-playing .r-bars span { animation: bar .9s ease-in-out infinite alternate; }
@keyframes bar { from { height: 1.6cqh; } to { height: 9.6cqh; } }

.r-text {
  display: grid; place-items: center; padding: 6cqh 7cqw; background: #0a0d12;
  text-align: center;
}
.r-text[data-align='left'] { text-align: left; justify-items: start; }
.r-text-body { font-weight: 650; line-height: 1.15; }
/* 's' already existed as a valid size in PLAN_TYPES.text and protocol.js's
   own enum before Issue #103's controller UI ever offered it - it just had
   no clamp of its own, so it rendered unstyled. Given its own size now that
   something actually selects it. */
.r-text[data-size='s'] .r-text-body { font-size: clamp(9px, 3.2cqw, 48px); }
.r-text[data-size='m'] .r-text-body { font-size: clamp(10px, 5cqw, 76px); }
.r-text[data-size='l'] .r-text-body { font-size: clamp(12px, 8cqw, 132px); }
.r-text[data-size='xl'] .r-text-body { font-size: clamp(14px, 13cqw, 220px); }
.r-text code { font-family: ui-monospace, Menlo, monospace; background: #1b222c; padding: 0 .2em; border-radius: 6px; }

/* Issue #103: headings/lists inside a message, a font choice, and an
   optional picture with a caption underneath the text. */
.r-text-body h1, .r-text-body h2 { margin: 0 0 .2em; font-weight: 800; line-height: 1.15; }
.r-text-body h1 { font-size: 1.15em; }
.r-text-body h2 { font-size: 1.05em; }
/* Centred alignment still centres a list AS A BLOCK; the bullets/numbers
   inside it read left-to-right regardless, the same as any real slide. */
.r-text-body ul, .r-text-body ol { margin: .25em 0; padding-left: 1em; text-align: left; display: inline-block; }
.r-text-body li { margin-bottom: .15em; }
.r-text[data-font='serif'] { font-family: Georgia, 'Times New Roman', serif; }
.r-text[data-font='mono'] { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; }
.r-text[data-font='rounded'] { font-family: 'Trebuchet MS', 'Comic Sans MS', sans-serif; }
.r-text[data-font='display'] { font-family: 'Arial Black', 'Helvetica Neue', Impact, sans-serif; }
.r-text-image-wrap { margin-top: 3cqh; display: grid; justify-items: center; gap: 1cqh; }
.r-text-image { max-width: 60cqw; max-height: 35cqh; border-radius: 1.5cqh; display: block; object-fit: contain; }
.r-text-caption { font-size: clamp(8px, 2.4cqw, 34px); color: var(--dim); font-weight: 500; }

.r-qr { display: grid; place-items: center; gap: 3cqh; background: #fff; color: #111; align-content: center; }
.r-qr-code svg { width: min(58cqh, 58cqw); height: auto; display: block; }
.r-qr-caption { font-size: clamp(7px, 2.6cqw, 40px); font-weight: 600; overflow-wrap: anywhere; padding: 0 6cqw; text-align: center; }

.r-timer { display: grid; place-items: center; align-content: center; gap: 2cqh; background: #07090c; }
.r-timer-label { font-size: clamp(8px, 3cqw, 46px); color: var(--dim); }
.r-timer-value { font-size: clamp(20px, 24cqw, 380px); font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; }
.r-timer.is-urgent .r-timer-value { color: var(--cue); }
.r-timer.is-done .r-timer-value { color: var(--live); animation: pulse 1s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: .35; } }

.r-poll {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3cqh; background: var(--poll-bg, #0a0d12); padding: 5cqh 6cqw; text-align: center;
}
/* A poll with a look (poll-look.js) sets these; one without keeps the page's. */
.r-poll.has-look { color: var(--poll-fg); --dim: var(--poll-dim); --accent: var(--poll-accent); --ok: var(--poll-ok); --panel: var(--poll-card); }
/* The join card stays white, for the QR code; on a light screen it needs an edge. */
.r-poll.is-light-look .r-poll-join { box-shadow: 0 0 0 1px rgba(0,0,0,.12), 0 1cqh 3cqh rgba(0,0,0,.08); }
.r-poll-question { font-size: clamp(12px, 6cqw, 92px); font-weight: 700; line-height: 1.15; }
.r-poll-join { display: grid; place-items: center; gap: 1.4cqh; background: #fff; color: #111; border-radius: 2cqh; padding: 3cqh 4cqw; }
.r-poll.is-revealed .r-poll-join { display: none; }
.r-poll-qr svg { width: min(30cqh, 30cqw); height: auto; display: block; }
.r-poll-code { font-family: ui-monospace, Menlo, monospace; font-weight: 700; font-size: clamp(14px, 5cqw, 64px); letter-spacing: 0.12em; }
.r-poll-url {
  font-family: ui-monospace, Menlo, monospace; font-size: clamp(6px, 1.6cqw, 20px);
  color: #333; word-break: break-all; max-width: 80cqw; text-align: center;
}
.r-poll-hint { font-size: clamp(7px, 1.8cqw, 22px); color: #555; }
.r-poll-status { font-size: clamp(8px, 2.2cqw, 30px); color: var(--dim); }
.r-poll-results { width: min(90cqw, 900px); display: flex; flex-direction: column; gap: 1.6cqh; }
.r-poll-bar-row { display: flex; flex-direction: column; gap: .6cqh; }
.r-poll-bar-label { display: flex; justify-content: space-between; font-size: clamp(9px, 2.6cqw, 36px); font-weight: 600; }
.r-poll-bar-track { height: 3.2cqh; background: var(--poll-track, #1b222c); border-radius: 999px; overflow: hidden; }
.r-poll-bar-fill { height: 100%; background: var(--accent); border-radius: 999px; transition: width .3s ease; }
.r-poll-answers { display: flex; flex-direction: column; gap: 1cqh; max-height: 70cqh; overflow: auto; }
.r-poll-answer { font-size: clamp(9px, 2.6cqw, 34px); background: var(--poll-card, #151b23); border-radius: 1.2cqh; padding: 1.2cqh 1.8cqw; text-align: left; }
.r-poll-answer.r-poll-empty { color: var(--dim); text-align: center; }

/* Check-in on screen (Issue #256): white, like a QR code item, so a phone
   across the room can read it. */
.r-att {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2.2cqh; background: #fff; color: #111; padding: 4cqh 6cqw; text-align: center;
}
.r-att-heading { font-size: clamp(10px, 4.2cqw, 64px); font-weight: 700; line-height: 1.15; }
.r-att-qr svg { width: min(46cqh, 46cqw); height: auto; display: block; }
.r-att-code { font-family: ui-monospace, Menlo, monospace; font-weight: 800; font-size: clamp(18px, 9cqw, 150px); letter-spacing: 0.08em; line-height: 1; font-variant-numeric: tabular-nums; }
.r-att-bar { width: min(40cqw, 520px); height: 1cqh; min-height: 3px; background: #e3e7ee; border-radius: 999px; overflow: hidden; }
.r-att-bar span { display: block; height: 100%; width: 0; background: #1f5fcc; border-radius: 999px; }
.r-att-url { font-size: clamp(7px, 2cqw, 30px); color: #444; }
.r-att-count { font-size: clamp(8px, 2.8cqw, 40px); font-weight: 700; color: #13804f; }
.r-att-late { font-size: clamp(8px, 2.4cqw, 34px); font-weight: 700; color: #8a5a00; }
.r-att-late:empty, .r-att-count:empty { display: none; }
.r-att.is-closed .r-att-code { color: #777; font-size: clamp(14px, 6cqw, 96px); }
.r-att.is-closed .r-att-bar, .r-att.is-elsewhere .r-att-bar, .r-att.is-elsewhere .r-att-code { display: none; }

.r-poll-cloud {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1.2cqh 2cqw;
  padding: 2cqh 3cqw;
  max-width: 90cqw;
  max-height: 70cqh;
  overflow: auto;
  text-align: center;
}
.r-poll-cloud-word {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25em;
  font-weight: 700;
  line-height: 1.15;
  padding: 0.2em 0.4em;
  border-radius: 0.5em;
  user-select: none;
  transition: transform 0.15s ease;
}
.r-poll-cloud-word:hover {
  transform: scale(1.06);
}
.r-poll-cloud-count {
  font-size: 0.5em;
  font-weight: 700;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
  background: rgba(255, 255, 255, 0.12);
  padding: 0.1em 0.4em;
  border-radius: 999px;
}
:root[data-theme="light"] .r-poll-cloud-count,
.r-poll.is-light-look .r-poll-cloud-count {
  background: rgba(0, 0, 0, 0.08);
}

.r-camera { background: #000; }
.r-camera-hint { position: absolute; inset: 0; display: grid; place-items: center; color: var(--dim); font-size: clamp(9px, 2.2cqw, 22px); text-align: center; padding: 0 4cqw; }
.r-camera.has-stream .r-camera-hint { display: none; }
.r-camera-frozen {
  position: absolute; top: 3cqh; left: 3cqw; padding: .4em .9em; border-radius: 999px;
  background: rgba(20, 12, 0, .78); color: var(--cue); font-size: clamp(9px, 1.8cqw, 16px);
  font-weight: 600; letter-spacing: .04em; text-transform: uppercase; display: none;
}
.r-camera.has-stream.is-frozen .r-camera-frozen { display: block; }

/* ===================== CONTROLLER ================================= */

body.control {
  display: flex; flex-direction: column; height: 100dvh; overflow: hidden;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left);
  overscroll-behavior: none;
}
body.control.is-frozen { background: #171006; }

#app { display: flex; flex-direction: column; flex: 1; min-height: 0; }

.topbar {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px;
  border-bottom: 1px solid var(--line); font-size: 13px; color: var(--dim); flex-wrap: wrap;
}
/* The controller's bar is one row at every size (Issue #187): what used to
   wrap it now lives in the layout popover and the ⋯ menu, and the status
   text is what gives way, trimming itself rather than pushing a row down. */
body.control .topbar { flex-wrap: nowrap; }
body.control .topbar-status { min-width: 0; }
.topbar-pop-wrap { position: relative; flex: none; }
.layout-current {
  display: inline-flex; align-items: center; gap: 4px; min-height: 30px; padding: 0 8px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px;
}
.layout-current.is-cued { border-color: var(--cue); }
.layout-caret { font-size: 10px; color: var(--dim); }
#topbar-more { min-height: 30px; min-width: 36px; padding: 0 10px; font-size: 18px; line-height: 1; }
.topbar-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  width: max-content; max-width: calc(100vw - 16px);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .35); padding: 8px;
}
.topbar-menu { display: flex; flex-direction: column; gap: 6px; min-width: 220px; }
.topbar-menu > button { width: 100%; justify-content: flex-start; text-align: left; }
.topbar-menu-info { display: flex; flex-direction: column; gap: 6px; padding: 6px 4px 2px; border-top: 1px solid var(--line); margin-top: 2px; }
.topbar-menu .room-info, .topbar-menu .version-tag { white-space: nowrap; }
.topbar .brand { font-weight: 700; color: var(--ink); letter-spacing: .04em; text-decoration: none; }
.topbar .brand:hover { text-decoration: underline; }
.topbar .spacer { flex: 1; }
/* A zero flex-basis and a fixed min-width mean the wrap point depends only on
   a constant, never on what the status text currently says: the group grows
   into leftover room and ellipsizes past it. */
.topbar-status {
  flex: 3 1 0; min-width: 200px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.topbar-status > * + * { margin-left: 10px; }
.version-tag { font-size: 11px; color: var(--dim); opacity: 0.75; white-space: nowrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.version-stamp { font-size: 11px; color: var(--dim); opacity: 0.7; text-align: center; margin: 20px 0 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
#status::before { content: '●'; margin-right: 6px; color: var(--dim); }
#status[data-status='online']::before { color: var(--ok); }
#status[data-status='offline']::before, #status[data-status='connecting']::before { color: var(--cue); }
#status[data-status='error']::before, #status[data-status='mismatch']::before { color: var(--live); }
#display-state.is-bad { color: var(--bad); }
.topbar button { min-height: 34px; padding: 0 10px; font-size: 13px; }

.clock-widget {
  display: inline-flex; align-items: center; gap: 8px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.topbar-clock {
  font-weight: 600; font-size: 13px; color: var(--ink); letter-spacing: .02em;
}
.topbar-pacing {
  position: relative; display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 28px; padding: 2px 10px 4px; font-size: 12px; font-weight: 600;
  color: var(--dim); background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 999px; overflow: hidden; cursor: pointer; user-select: none;
}
.topbar-pacing:hover { background: #232b35; color: var(--ink); }
.topbar-pacing.is-running { color: var(--ink); border-color: var(--accent); }
.topbar-pacing.is-near-end { border-color: var(--cue); color: var(--cue); }
.topbar-pacing.is-overtime { border-color: var(--live); color: #ff9d9d; background: #2a1215; }
.pacing-track {
  position: absolute; bottom: 0; left: 0; right: 0; height: 2.5px;
  background: rgba(255, 255, 255, .08);
}
#topbar-pacing-bar {
  display: block; height: 100%; width: 0%; background: var(--accent); transition: width .3s ease;
}
.topbar-pacing.is-near-end #topbar-pacing-bar { background: var(--cue); }
.topbar-pacing.is-overtime #topbar-pacing-bar { background: var(--live); width: 100% !important; }
/* Who is signed in, on a server that has accounts. Empty and hidden on every
   other deployment, so it costs nothing in the bar it is not needed in. */

/* ===================== GUEST (Simple Mode) =========================
   Issue #77's substitute clicker: the same #app/.topbar furniture body.control
   uses (both rules above are unscoped on purpose), a lot less inside it. */
body.guest {
  display: flex; flex-direction: column; height: 100dvh; overflow: hidden;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  overscroll-behavior: none;
}
.guest-main {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 18px;
  padding: 18px 16px calc(24px + env(safe-area-inset-bottom));
}
#guest-now { font-size: 16px; text-align: center; margin: 0; }
.guest-nav { display: flex; gap: 10px; }
.guest-nav .big-button { flex: 1; width: auto; min-height: 96px; font-size: 22px; }
#guest-blank { min-height: 60px; }
.guest-laser-block { display: flex; flex-direction: column; gap: 10px; align-items: center; }
.guest-laser-block .laser-picker { justify-content: center; }
/* No live mirror to point at (see the comment in guest.html) - a plain,
   fixed-aspect surface stands in for the screen, same fraction-of-a-box math
   as control.js's own laserPoint. Capped by height rather than just width:
   at full device width, 16:9 of it is taller than a phone or a landscape
   tablet has room for underneath Next/Previous and Blank without scrolling
   to reach it - which is exactly wrong for something meant to be immediate. */
.guest-pad {
  position: relative; height: min(38vh, 340px); width: auto; max-width: 100%;
  aspect-ratio: 16 / 9; margin: 0 auto; border-radius: var(--radius);
  border: 2px dashed var(--line); background: var(--panel-2);
  display: flex; align-items: center; justify-content: center; text-align: center;
  cursor: crosshair; touch-action: none;
}
.guest-pad .hint { margin: 0; padding: 0 20px; }
/* A ceiling, never a floor - max-width only ever shrinks to fit a narrower
   viewport, so 56rem stays exactly what a phone or a normal laptop screen
   already got. clamp() only changes anything once the viewport is wide
   enough that 82vw clears 56rem in the first place - a genuinely large
   monitor - letting content already built to use available width (the
   auto-fit form grids, admin-row's own flexed title) actually get some,
   instead of every tab being one column capped at 896px regardless of how
   much screen is sitting unused beside it. Issue #153. */
.admin-wrap { max-width: clamp(56rem, 82vw, 100rem); margin: 18px auto; padding: 0 14px; display: grid; gap: 14px; }
/* The generic .card this inherits from caps max-width at 620px for the
   narrow centered dialogs it's meant for elsewhere (login, setup sheets,
   modals) - admin cards are grid children of the wrapper above, not
   centered dialogs, so that cap only fights the widened .admin-wrap for no
   reason. Issue #153. */
.admin-wrap .card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; max-width: none; }
.admin-wrap h2 { margin: 0 0 6px; font-size: 16px; }
.admin-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.admin-head h2 { flex: 1; }
.admin-head input { min-width: 14rem; }
.admin-form { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); margin-top: 10px; }
.admin-form .field { display: grid; gap: 4px; font-size: 13px; color: var(--dim); }
.admin-actions { display: flex; align-items: center; gap: 12px; margin-top: 12px; flex-wrap: wrap; }
.admin-actions .is-bad { color: var(--bad); }
.admin-row {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 0; border-top: 1px solid var(--line);
}
.admin-title { flex: 1; color: var(--ink); }
.admin-meta { color: var(--dim); font-size: 12px; }
/* Issue #155: a kiosk gone quiet longer than its own heartbeat should ever
   allow, in the same red every other .is-bad status already uses. */
.admin-meta.is-bad { color: var(--bad); }
/* Admin -> Server -> Health: podium-admin doctor's findings, one a row. */
.health-list { list-style: none; margin: 8px 0 12px; padding: 0; }
.health-item {
  display: grid; grid-template-columns: 3.5rem 7.5rem 1fr; gap: 2px 10px; align-items: baseline;
  padding: 7px 0; border-top: 1px solid var(--line); font-size: 13px;
}
.health-mark { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.health-item.is-ok .health-mark { color: var(--ok); }
.health-item.is-warn .health-mark { color: var(--cue); }
.health-item.is-bad .health-mark { color: var(--live); }
.health-detail { color: var(--ink); overflow-wrap: anywhere; }
.health-fix { grid-column: 3; color: var(--dim); font-size: 12px; overflow-wrap: anywhere; }
#health-note.is-bad { color: var(--live); }
@media (max-width: 560px) {
  .health-item { grid-template-columns: 3.5rem 1fr; }
  .health-detail, .health-fix { grid-column: 2; }
}
.admin-del { min-height: 32px; padding: 0 10px; font-size: 12px; }
.admin-del[data-armed='yes'] { background: var(--live-fill); border-color: var(--live-fill); color: #fff; }
.admin-row.is-busy { opacity: 0.55; pointer-events: none; }
.admin-small { min-height: 32px; padding: 0 10px; font-size: 12px; }
/* A file picker dressed as one of the admin page's small buttons - .filebtn
   is declared further down and would otherwise win. */
.filebtn.admin-small { min-height: 32px; padding: 0 10px; font-size: 12px; }
/* A course's default watermark logo (Issue #157). Checkered behind it so a
   transparent PNG reads as transparent, not as a black box. */
.branding-preview {
  max-height: 48px; max-width: 160px; border: 1px solid var(--line); border-radius: 6px;
  background: repeating-conic-gradient(#2a3038 0% 25%, #1c2127 0% 50%) 0 0 / 12px 12px;
}
.admin-name { flex: 1; min-width: 8rem; }
.session-body { padding: 0 0 12px; border-top: 1px solid var(--line); }

/* On a wide screen, an opened course or session sits BESIDE the list rather
   than pushing everything after it down the page (Issue #208): the list stays
   in one column, the open one's details in the other, sticky while the list
   scrolls. A container query on the card, so it follows the space the card
   actually has. Pure CSS: each list already puts the open entry's details
   straight after its row. */
.admin-wrap .card { container-type: inline-size; }
.admin-row.is-open { background: var(--panel-2); }
@container (min-width: 1000px) {
  :is(#sessions, #courses):has(> .session-body) {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); column-gap: 18px; align-items: start;
  }
  :is(#sessions, #courses):has(> .session-body) > :not(.session-body) { grid-column: 1; }
  :is(#sessions, #courses) > .session-body {
    grid-column: 2; grid-row: 1 / span 500; position: sticky; top: 12px;
    border-top: 0; border-left: 1px solid var(--line); padding: 0 0 12px 18px;
    max-height: calc(100vh - 40px); overflow: auto;
  }
}
.timeline { display: grid; gap: 1px; margin: 8px 0 0; }
.timeline-row { display: flex; gap: 12px; align-items: baseline; padding: 3px 0; }
.timeline-at { color: var(--dim); font-size: 12px; font-variant-numeric: tabular-nums; min-width: 5.5rem; }
.timeline-what { flex: 1; }
.timeline-note { color: var(--dim); font-size: 12px; }
/* A caption line (Issue #158): something said over the entry above it, not
   something shown - set in from the entries so the two read apart. */
.timeline-caption .timeline-what { color: var(--dim); font-style: italic; padding-left: 16px; }

/* Searching past sessions (Issue #159): the box, the hits, and a session's
   timeline opened at the moment that matched. */
.session-search { margin: 0 0 12px; }
.ss-bar { display: flex; gap: 8px; flex-wrap: wrap; }
.ss-input { flex: 1 1 260px; min-width: 0; }
.ss-course { flex: 0 0 auto; }
.ss-note:empty { display: none; }
.ss-results { display: flex; flex-direction: column; gap: 10px; }
.ss-session { border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; min-width: 0; }
.ss-head { display: flex; flex-direction: column; min-width: 0; margin-bottom: 4px; }
.ss-hits { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ss-hit {
  display: flex; gap: 10px; align-items: baseline; width: 100%; text-align: left;
  background: none; border: 0; border-radius: 6px; padding: 6px; color: var(--ink); font: inherit; cursor: pointer;
}
.ss-hit:hover, .ss-hit:focus-visible { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.ss-hit .timeline-at { min-width: 3rem; }
.ss-kind { color: var(--dim); font-size: 12px; min-width: 4.5rem; }
.ss-snip { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.ss-who { color: var(--dim); font-size: 12px; }
/* Play from here (Issue #132): beside each hit, and on each moment's time. */
.ss-hit-row { display: flex; gap: 4px; align-items: stretch; }
.ss-hit-row .ss-hit { flex: 1; min-width: 0; }
.ss-play { flex: none; min-width: 36px; min-height: 32px; padding: 0 10px; font-size: 13px; }
.timeline-play {
  background: none; border: 0; padding: 0 4px; border-radius: 4px; cursor: pointer; text-align: left;
  font: inherit; font-size: 12px; color: var(--dim); min-height: 0;
}
.timeline-play:hover, .timeline-play:focus-visible { color: var(--accent); text-decoration: underline; }
.ss-hit.is-static { cursor: default; }
.ss-hit.is-static:hover { background: none; }
.session-search mark {
  background: color-mix(in srgb, var(--cue) 35%, transparent); color: inherit; border-radius: 3px; padding: 0 1px;
}
.ss-viewer { margin-top: 8px; border-top: 1px solid var(--line); padding-top: 8px; }
.ss-nav { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ss-where { flex: 1 1 auto; color: var(--dim); font-size: 13px; }
.ss-timeline { max-height: min(50vh, 420px); overflow: auto; margin-top: 8px; border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; }
.ss-timeline .timeline-row.is-match { background: color-mix(in srgb, var(--cue) 10%, transparent); }
.ss-timeline .timeline-row.is-current { background: color-mix(in srgb, var(--accent) 18%, transparent); box-shadow: inset 3px 0 0 var(--accent); }
@media (max-width: 560px) {
  .ss-hit { flex-wrap: wrap; gap: 4px 10px; }
  .ss-snip { flex-basis: 100%; }
}

.session-badge { display: flex; align-items: center; gap: 8px; }
.session-badge .session-who { color: var(--ink); text-decoration: none; border-bottom: 1px dotted var(--dim); }
.session-badge .session-who:hover { color: var(--accent); }
.topbar .session-badge button { min-height: 28px; padding: 0 9px; font-size: 12px; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

.workspace { display: flex; flex: 1; min-height: 0; }
.workspace.no-preview #preview-pane { display: none; }

.layout-picker { display: flex; flex-wrap: wrap; gap: 4px; }
.layout-btn {
  display: grid; place-items: center; width: 32px; min-height: 30px; padding: 0;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px;
}
.layout-btn.is-on { border-color: var(--accent); background: #16202c; }
/* Frozen and waiting on TAKE, the same colour the preview pane's own cued
   border already uses - a layout change is cued the same way content is. */
.layout-btn.is-cued { border-color: var(--cue); box-shadow: inset 0 0 0 1px var(--cue); }
/* Each icon is a tiny CSS-drawn copy of the actual on-screen grid (see
   #stage.layout-* in the display rules) - what you tap is what you get. */
.layout-icon { display: grid; width: 18px; height: 13px; gap: 2px; }
.layout-icon i { background: var(--dim); border-radius: 1px; }
.layout-btn.is-on .layout-icon i { background: var(--accent); }
.layout-icon-single { grid-template-columns: 1fr; }
.layout-icon-2h { grid-template-columns: 1fr 1fr; }
.layout-icon-2v { grid-template-rows: 1fr 1fr; }
.layout-icon-3 { grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; }
.layout-icon-3 i:first-child { grid-row: 1 / 3; }
.layout-icon-4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
/* Picture-in-picture: one big square, one small one inset over a corner of
   it - drawn with position, not the grid the other icons share, the same
   reason #stage.layout-pip itself does not use a multi-cell grid either. */
.layout-icon-pip { position: relative; display: block; }
.layout-icon-pip i:first-child { position: absolute; inset: 0; }
.layout-icon-pip i:last-child { position: absolute; width: 45%; height: 45%; top: 1px; right: 1px; border: 1px solid var(--panel); }

.panel-picker { display: flex; gap: 4px; }
.panel-btn {
  min-width: 30px; min-height: 30px; padding: 0 8px; font-weight: 700; font-size: 13px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px;
}
.panel-btn.is-on { border-color: var(--accent); color: var(--accent); background: #16202c; }
/* Not part of this layout (Issue #215): still selectable, to set it up
   unseen, but plainly not what the room is looking at. */
.panel-btn.is-offscreen { border-style: dashed; color: var(--dim); background: transparent; }
.panel-btn.is-offscreen.is-on { color: var(--accent); }

#build-note.is-stale, .stale-note { color: var(--warn); }

#arm-resume { display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; }
#arm-resume button { min-height: 32px; padding: 0 12px; font-size: 12px; }

/* The relay readouts. A failing connection is the one problem where the useful
   text is long - a URL, a close code, an attempt count - so let it wrap and
   scroll rather than truncating it into uselessness. */
.relay-target { word-break: break-all; }
.relay-log {
  margin: 8px 0 0; padding: 8px 10px; max-height: 130px; overflow: auto;
  background: var(--field); border: 1px solid var(--line); border-radius: 8px;
  color: var(--dim); font-size: 11.5px; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word; text-align: left;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

#update-banner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: #2a1e12; border-bottom: 1px solid #6b4a22; color: #ffd9a8;
  padding: 10px 14px; font-size: 13px; line-height: 1.5;
}
#update-banner[hidden] { display: none; }
#update-banner button { min-height: 32px; padding: 0 12px; }

/* Data loss risk, not just an inconvenience - same red as relay-help. */
#storage-warning {
  background: #2a1216; border-bottom: 1px solid #6b2b2b; color: #ffc4c4;
  padding: 10px 14px; font-size: 13px; line-height: 1.5;
}
#storage-warning[hidden] { display: none; }

/* A relay that will not come up is a red problem, not an amber one: nothing
   works until it is fixed, unlike a display that has simply not joined yet. */
#relay-help {
  background: #2a1216; border-bottom: 1px solid #6b2b2b; color: #ffc4c4;
  padding: 10px 14px; font-size: 13px; line-height: 1.5;
}
#relay-help p { margin: 6px 0 0; }
#relay-help .relay-log { color: #e5bdbd; border-color: #6b2b2b; }

#link-help {
  background: #2a1e12; border-bottom: 1px solid #6b4a22; color: #ffd9a8;
  padding: 10px 14px; font-size: 13px; line-height: 1.5;
}
#link-help ul { margin: 6px 0 0; padding-left: 18px; }
#link-help li { margin-bottom: 3px; }
#link-help code { font-family: ui-monospace, Menlo, monospace; }
#link-help.is-mismatch { background: #2a1216; border-bottom-color: #6b2b2b; color: #ffc4c4; }

/* Preview rail */
#preview-pane {
  width: 320px; flex: none; border-right: 1px solid var(--line);
  display: flex; flex-direction: column; padding: 12px; gap: 10px;
}
#preview-stage {
  position: relative; aspect-ratio: 16 / 9; background: #000; overflow: hidden;
  border-radius: 10px; border: 2px solid var(--line);
}
#preview-stage > * { position: absolute; inset: 0; }
#preview-pane.is-cued #preview-stage { border-color: var(--cue); }
#preview-label { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--dim); }
#preview-pane.is-cued #preview-label { color: var(--cue); }
#preview-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#take {
  min-height: 62px; font-size: 18px; font-weight: 700; letter-spacing: .05em;
  background: #2b1518; border-color: #57262b; color: #ff9d9d;
}
#take.is-armed { background: var(--live-fill); border-color: var(--live-fill); color: #fff; }

/* Tabs + panels */
/* min-height:0 is what lets .panels scroll instead of shoving the bottom bar
   off the screen when the workspace stacks on a phone. */
.main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.tabs { display: flex; gap: 6px; padding: 10px 12px 0; overflow-x: auto; }
.tab { min-height: 40px; padding: 0 14px; border-radius: 999px; white-space: nowrap; font-size: 14px; }

/* The Settings sheet's own Connection/Presentation split - .tabs sits inside
   .card's own padding here rather than a full-width workspace, so it does not
   want .tabs' left/right padding on top of that. */
.settings-tabs { padding: 0 0 18px; }
.settings-panel .check { margin: 16px 0 6px; }
.settings-panel .check:first-of-type { margin-top: 4px; }
.settings-panel .check input { transform: scale(1.15); }
/* Hidden by default (desktop already has Save/Close in easy reach at the
   bottom of this sheet) - shown on a phone by the media query below. */
.setup-top-actions { display: none; gap: 10px; margin-bottom: 18px; }
.setup-top-actions button { flex: 1; }
.tab.is-on { background: var(--panel-2); border-color: var(--accent); color: var(--ink); }

/* "More", for tabs hidden via Settings -> Presentation -> Controller tabs.
   Positioned relative to the wrap (not .tabs, which scrolls) so the menu
   stays anchored to the button instead of scrolling out from under it. */
.tabs-more-wrap { position: relative; flex: none; }
.tabs-more-menu {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 30;
  display: flex; flex-direction: column; min-width: 10rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 4px; box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
}
.tabs-more-menu button {
  text-align: left; border: 0; background: transparent; border-radius: 8px;
  min-height: 38px; padding: 0 10px; font-size: 14px;
}
.tabs-more-menu button:hover { background: var(--panel-2); }

.panels { flex: 1; min-height: 0; overflow: auto; padding: 12px; -webkit-overflow-scrolling: touch; }
.panel { display: flex; flex-direction: column; gap: 14px; }

.group { margin: 6px 0 0; font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim); }
/* Wide enough that a lecture title survives (Issue #191) - 132px cut nearly
   every deck name to its first two words. auto-fill rather than auto-fit, so
   every group's tiles sit in the same columns rather than a group of two
   stretching across the whole row. */
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 10px; }
.lib-toolbar { position: sticky; top: 0; z-index: 2; background: var(--bg); padding: 2px 0; }
.lib-add { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px; background: var(--panel); }
.tile {
  position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  padding: 12px; min-height: 88px; text-align: left; border-radius: var(--radius);
}
.tile-icon { font-size: 20px; line-height: 1; }
/* The picture on a tile (Issue #192). Its space is kept from the start, so a
   row does not jump as pictures arrive one by one; until one does (or when
   one cannot be made) the tile's own icon stands in, in the same place. */
.tile-thumb {
  display: block; width: 100%; aspect-ratio: 16 / 9; border-radius: 8px; margin-bottom: 4px;
  background: var(--bg) center / cover no-repeat; border: 1px solid var(--line);
}
.tile.has-thumb-slot .tile-icon { position: absolute; top: 18px; left: 18px; opacity: .7; }
.tile-thumb.has-thumb + .tile-icon, .tile.has-thumb-slot:has(.tile-thumb.has-thumb) .tile-icon { display: none; }
/* Two lines before an ellipsis (Issue #191); the whole name is on the
   tile's title, for a hover or a screen reader. */
.tile-title {
  font-weight: 600; font-size: 14px; max-width: 100%; overflow: hidden;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow-wrap: anywhere;
}
.tile-type { font-size: 11px; color: var(--dim); }
.tile-del {
  position: absolute; top: 4px; right: 6px; font-size: 18px; color: var(--dim);
  width: 26px; height: 26px; display: grid; place-items: center; border-radius: 8px;
}
/* Bottom-right rather than top-right, so a custom audio tile can carry this
   and its own delete button (top-right) at once without the two colliding. */
.tile-music {
  position: absolute; bottom: 4px; right: 6px; font-size: 12px; font-weight: 600;
  color: var(--dim); padding: 3px 7px; display: grid; place-items: center; border-radius: 8px;
}
.tile-music:active { background: var(--panel-2); }
/* Issue #226: same corner as .tile-music, which a deck never has. */
.tile-edit {
  position: absolute; bottom: 4px; right: 6px; font-size: 14px;
  color: var(--dim); padding: 2px 7px; display: grid; place-items: center; border-radius: 8px;
}
.tile-edit:hover, .tile-edit:active { background: var(--panel-2); color: var(--accent); }
/* Issue #242: Quick Look, in the same corner - beside ✎ or ♪ when the tile has one. */
.tile-look {
  position: absolute; bottom: 4px; right: 6px; font-size: 14px;
  color: var(--dim); padding: 2px 7px; display: grid; place-items: center; border-radius: 8px;
}
.tile-look.is-beside { right: 40px; }
.tile-look:hover, .tile-look:active { background: var(--panel-2); color: var(--accent); }
/* Issue #226: the pictures and videos used in decks, kept out of the tiles until asked for. */
/* Issue #226: the deck on screen was edited since it went up. */
.deck-edited { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 6px 0; padding: 6px 10px; border: 1px solid var(--cue); border-radius: 10px; color: var(--cue); font-size: 13px; }
.deck-edited[hidden] { display: none; }
.deck-edited button { min-height: 32px; padding: 0 12px; font-size: 13px; }
.lib-deck-media { display: block; margin: 12px auto 4px; background: none; border: 0; color: var(--accent); font-size: 13px; cursor: pointer; }
.lib-deck-media:hover { text-decoration: underline; }
.empty { color: var(--dim); }

/* Where you just were. A single scrolling row rather than a group in the
   Library, because its whole value is being one reachable tap away - buried
   under a lecture plan's twelve items it would be slower than re-picking. */
#recent-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; min-width: 0; }
.recent-label { font-size: 12px; color: var(--dim); white-space: nowrap; }
.recent-strip { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: thin; }
.recent-chip {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  min-height: 40px; padding: 4px 11px; font-size: 13px; white-space: nowrap; flex: none;
}
.recent-chip .where { font-size: 11px; color: var(--dim); }

.stack { display: flex; flex-direction: column; gap: 10px; }
.mixer-channel {
  padding: 10px 12px; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); margin-bottom: 10px;
}
.mixer-channel .mono { color: var(--dim); font-size: 13px; min-width: 3.4em; text-align: right; }
.inline { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.inline > .grow { flex: 1; min-width: 140px; }
.spacer { flex: 1; min-width: 0; }
.check { display: flex; align-items: center; gap: 8px; color: var(--dim); font-size: 14px; }
.check input { width: auto; min-height: auto; }

/* Marp deck panel */
.filebtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap); padding: 0 16px; margin: 0;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius);
  cursor: pointer; font-size: 15px; color: var(--ink);
}
.filebtn:active { transform: translateY(1px); }
.deck-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
#deck-title { font-size: 19px; font-weight: 650; }
.deck-head-right { display: flex; align-items: center; gap: 10px; flex: none; }
#deck-count { font-size: 15px; color: var(--dim); white-space: nowrap; }
#deck-theme.is-warning { color: var(--cue); }
#confidence-split { min-height: 30px; padding: 0 10px; font-size: 13px; white-space: nowrap; }
.deck-nav button { flex: 1; min-height: 58px; font-size: 17px; font-weight: 600; }

/* Now / Next confidence monitor */
.confidence-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.confidence-row[data-split="now"] { grid-template-columns: 3fr 1fr; }
.confidence-row[data-split="next"] { grid-template-columns: 1fr 3fr; }
.confidence-box { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.confidence-label {
  font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
.confidence-preview {
  position: relative; aspect-ratio: 16 / 9; border-radius: var(--radius);
  overflow: hidden; border: 2px solid var(--line); background: #101418;
}
#deck-now-preview { border-color: var(--accent); }
/* Issue #174: the Now tab's large live view of the focused pane. */
.now-preview { margin: 10px auto 4px; border-color: var(--live); max-height: 52vh; }
/* Issue #262: held upright, the screen is tall - the preview may use more of
   it, and a portrait page or photo is read at a useful size. */
@media (orientation: portrait) {
  .now-preview { max-height: 64vh; }
  .ink-controls { gap: 10px; }
}
/* Issue #174: a held slide, read at a useful size. */
.slide-popout-card { width: min(960px, 96vw); max-width: none; }
.slide-popout-card .confidence-preview { margin: 8px 0 10px; }
#slide-popout-notes { max-height: 28vh; overflow: auto; font-size: 15px; line-height: 1.45; }
#slide-popout-notes.is-empty { color: var(--dim); }
#deck-now-preview .mirror-frame.laser-armed { cursor: crosshair; touch-action: none; }
#deck-markup, #deck-laser, #deck-spotlight { flex: 1; font-size: 13px; min-height: 38px; padding: 0 8px; }
#deck-laser.is-on { background: var(--live-fill); border-color: var(--live-fill); color: #fff; }
#deck-spotlight.is-on { background: #d29922; border-color: #d29922; color: #1a1200; }
#deck-now-preview .mirror-frame.spotlight-armed { cursor: crosshair; touch-action: none; }
#pad.is-laser, #pad.is-spotlight { cursor: crosshair; }
.spotlight-preview {
  position: absolute; inset: 0; pointer-events: none; opacity: 0;
  transition: opacity .12s ease; z-index: 4;
  --spotlight-x: 50%;
  --spotlight-y: 50%;
  --spotlight-radius: 40px;
  background: radial-gradient(
    circle var(--spotlight-radius) at var(--spotlight-x) var(--spotlight-y),
    transparent 0%,
    transparent 80%,
    rgba(0, 0, 0, .45) 92%,
    rgba(0, 0, 0, .68) 100%
  );
  border-radius: inherit;
}
.spotlight-preview.is-on { opacity: 1; }
#pad-frame .spotlight-preview { --spotlight-radius: 110px; }
.laser-dot {
  position: absolute; width: 22px; height: 22px; border-radius: 50%;
  --laser: 255, 77, 79;
  background: radial-gradient(circle,
    #fff 0%, rgba(var(--laser), .97) 26%, rgba(var(--laser), .4) 58%, transparent 75%);
  box-shadow: 0 0 0 1.5px rgba(0, 0, 0, .38);
  transform: translate(-50%, -50%); pointer-events: none; opacity: 0; z-index: 5;
}
.laser-dot[data-color='green'] { --laser: 60, 235, 120; }
.laser-dot[data-color='blue'] { --laser: 90, 165, 255; }
.laser-dot.is-on { opacity: 1; }

/* Picking one. Small round swatches next to the Laser button - three of them
   is a row, not a menu, and a menu for three colours would be a tap too many
   in the middle of a sentence. */
.laser-picker { display: flex; gap: 4px; }
.laser-swatch {
  width: 30px; min-height: 30px; padding: 0; border-radius: 50%;
  border: 2px solid var(--line); background: rgb(var(--laser));
}
.laser-swatch:hover { background: rgb(var(--laser)); }
.laser-swatch.is-on { border-color: var(--ink); background: rgb(var(--laser)); }
#deck-laser[data-color='green'] { background: #1d3a28; border-color: #3ceb78; color: #bdf5d3; }
#deck-laser[data-color='blue'] { background: #16263d; border-color: #5aa5ff; color: #cfe3ff; }
#deck-laser.is-on[data-color='green'] { background: #3ceb78; border-color: #3ceb78; color: #06240f; }
#deck-laser.is-on[data-color='blue'] { background: #5aa5ff; border-color: #5aa5ff; color: #061527; }
#deck-next-title { margin: 0; }
.notes-head {
  margin-top: 4px; font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
.notes {
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 16px; font-size: 17px; line-height: 1.55; white-space: pre-wrap;
  min-height: 76px; color: var(--ink);
}
.notes.is-empty { color: var(--dim); font-style: italic; }

/* Presenter notes (Issue #195): their own text size, chosen per device with
   A-/A+, and - when the Slides panel is wide enough - a column of their own
   beside Now/Next instead of below the fold under them. A container query,
   not a viewport one, so split view (two tabs side by side) gets the stacked
   layout its narrower panel actually needs. */
#deck-live { container-type: inline-size; }
.deck-stage { display: flex; flex-direction: column; gap: 14px; }
.deck-stage-main, .deck-stage-notes { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.deck-stage-notes .notes { font-size: var(--notes-size, 17px); }
.notes-head-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.notes-size { display: inline-flex; gap: 4px; }
.notes-size button { min-height: 30px; padding: 0 10px; font-size: 13px; letter-spacing: 0; text-transform: none; }
.notes-tools { display: inline-flex; align-items: center; gap: 8px; }
#notes-toggle { min-height: 30px; padding: 0 12px; font-size: 13px; letter-spacing: 0; text-transform: none; }
/* Folded: the notes and their size buttons are put away, and on a wide panel
   Now/Next take the whole width back (the column layout below is undone). */
.deck-stage.is-notes-folded #deck-notes,
.deck-stage.is-notes-folded .notes-size { display: none; }
/* 1300px, not less: below it, a notes column would take its room from the
   Now/Next previews - on a 1280px laptop, hiding the cue bar to make them
   bigger would have made them smaller. Above it there is room for both. */
@container (min-width: 1300px) {
  .deck-stage { display: grid; grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); align-items: start; }
  .deck-stage-notes { position: sticky; top: 0; }
  .deck-stage-notes .notes { max-height: calc(100vh - 260px); overflow: auto; }
  .deck-stage.is-notes-folded { display: flex; align-items: stretch; }
  .deck-stage.is-notes-folded .deck-stage-notes { position: static; }
}

/* How far through the deck (Issue #196). */
.deck-progress { display: flex; align-items: center; gap: 10px; }
.deck-progress-track { flex: 1; height: 6px; border-radius: 3px; background: var(--panel-2); border: 1px solid var(--line); overflow: hidden; }
#deck-progress-bar { height: 100%; width: 0; background: var(--accent); transition: width .2s; }
#deck-progress-time { margin: 0; white-space: nowrap; }

/* Jump-strip arrows for a strip wider than the panel (Issue #196); on a wide
   panel the chips wrap instead and need none. */
.deck-chips-row { display: flex; align-items: center; gap: 6px; }
.deck-chips-row .deck-chips { flex: 1; min-width: 0; }
.deck-chips-scroll { min-height: 30px; width: 30px; padding: 0; flex: none; }
@container (min-width: 1000px) {
  .deck-chips-row .deck-chips { flex-wrap: wrap; overflow: visible; }
  .deck-chips-scroll { display: none; }
}
.mini-md-list { margin: 6px 0; padding-left: 20px; list-style-type: disc; }
.mini-md-list li { margin-bottom: 4px; }
.notes a { color: var(--accent); text-decoration: underline; }

.deck-chips {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 2px 0 4px 0;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.deck-chips::-webkit-scrollbar {
  height: 4px;
}
.deck-chips::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 4px;
}
.deck-chip {
  flex: 0 0 auto;
  padding: 4px 10px;
  border-radius: 14px;
  font-size: 12px;
  font-weight: 500;
  background: var(--panel-2);
  color: var(--dim);
  border: 1px solid var(--line);
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
  -webkit-user-select: none;
  transition: background .15s, color .15s, border-color .15s;
}
.deck-chip:hover {
  background: var(--panel);
  color: var(--ink);
  border-color: var(--dim);
}
.deck-chip.is-active {
  background: #1f6feb;
  color: #fff;
  border-color: #58a6ff;
  font-weight: 600;
}

#now-title { font-size: 19px; font-weight: 650; }
#now-type { color: var(--dim); font-size: 13px; }
#transport .inline button { min-width: 62px; }
#scrub { width: 100%; }
input[type=range] { accent-color: var(--accent); min-height: 40px; }
.times { display: flex; justify-content: space-between; font-size: 12px; color: var(--dim); font-variant-numeric: tabular-nums; }

/* Ink pad.
   #pad-viewport is the fixed window the user sees; #pad-frame is shaped (via
   aspect-ratio, set from JS) to match the actual content - a deck slide's own
   ratio, or the display's window shape for full-bleed content - so the WHOLE
   pad is drawable surface, edge to edge, with no dead margin to draw into by
   mistake. Zoom scales #pad-frame with a CSS transform only; the canvas's own
   pixel grid never changes, so pointer capture (which reads the canvas's
   on-screen, already-transformed box) stays correct at any zoom level. */
#pad-viewport {
  width: 100%; aspect-ratio: 16 / 9; overflow: hidden; position: relative;
  background: #06090c; border: 1px solid var(--line); border-radius: var(--radius);
  /* The height cap is set from JS (capPadHeight in control.js). */
  min-height: 180px;
}
/* Sized and positioned from JS (fitFrame in control.js): CSS aspect-ratio
   cannot win against the inset an absolutely-positioned element would
   otherwise need, so the "contain"-style fit is computed in pixels instead. */
#pad-frame {
  position: absolute; transform-origin: 0 0; background: #101418;
}
/* The live slide/whiteboard/camera you are drawing on top of. Sits behind the
   ink canvas (which is fully transparent outside your strokes) and can be
   hidden without losing anything - it is a read-only mirror, never the
   surface pointer events land on. */
#pad-mirror {
  position: absolute; inset: 0; pointer-events: none; overflow: hidden;
  background: #101418;
}
#pad-mirror.is-hidden { display: none; }
#pad {
  position: absolute; inset: 0; width: 100%; height: 100%; background: transparent;
  touch-action: none; display: block;
}
/* A deck's real shape not being known yet (see deckAspectPending() in
   control.js) blocks drawing rather than guessing - the mirror's own
   "Loading deck..." status underneath is feedback enough for what is a
   normally sub-second wait. */
#pad.is-pending { pointer-events: none; opacity: .4; }
/* Issue #262: the part of a zoomed page or photo the room can see. The pad is
   always the whole of it; this outline says where the projector is. */
#pad-room-window {
  position: absolute; pointer-events: none; box-sizing: border-box;
  border: 2px dashed var(--live); box-shadow: 0 0 0 9999px rgba(0, 0, 0, .28);
}
/* Issue #262: one compact zoom bar on Now and Ink - fit presets, a slider,
   the level. Pinch, drag, the keys and the wheel do the rest. */
.zoom-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.zoom-bar button { font-size: 13px; min-height: 38px; padding: 0 10px; }
.zoom-slider { flex: 1 1 140px; min-width: 120px; }
.zoom-level { min-width: 4em; text-align: right; color: var(--dim); font-variant-numeric: tabular-nums; }
/* Fingers on a page or photo's preview zoom the projector, not this page; on
   anything else they scroll the tab as usual. */
#now-preview.is-zoomable { touch-action: none; }
#pad-viewport { touch-action: none; }

/* Shared by the Slides tab's Now/Next preview boxes. */
.mirror-viewport {
  position: relative; width: 100%; height: 100%; overflow: hidden; background: #101418;
}
.mirror-frame { position: absolute; background: #101418; }
.ink-tools { display: inline-flex; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); vertical-align: middle; }
.ink-tools .ink-tool-btn {
  border-radius: 0; border: none; border-right: 1px solid var(--line);
  padding: 8px 13px; font-size: 13px; font-weight: 500; min-height: 42px;
  background: var(--panel-2); color: var(--dim);
}
.ink-tools .ink-tool-btn:last-child { border-right: none; }
.ink-tools .ink-tool-btn:hover { color: var(--ink); background: #1a2330; }
.ink-tools .ink-tool-btn.is-on {
  background: var(--accent); color: var(--on-accent); font-weight: 600;
}
#pad.is-eraser { cursor: crosshair; }
.swatch { width: 42px; min-height: 42px; padding: 0; border-radius: 50%; }
.swatch.is-on { outline: 3px solid var(--ink); outline-offset: 2px; }

/* Background colour swatches for the full-screen message editor (Issue #103) -
   visually identical to .swatch/.swatch-picker above but a distinct class:
   the ink tool's own swatch-selection code queries $$('.swatch:not(.swatch-
   picker)') with no scoping, so reusing that class here would make clicking
   one of these ALSO run the ink tool's colour-selection logic. */
.bg-swatch { width: 42px; min-height: 42px; padding: 0; border-radius: 50%; border: 2px solid var(--line); }
.bg-swatch.is-on { outline: 3px solid var(--ink); outline-offset: 2px; }
.bg-swatch-picker {
  position: relative; width: 42px; height: 42px; min-height: 42px; padding: 0;
  border-radius: 50%; display: inline-grid; place-items: center; cursor: pointer;
  background: conic-gradient(#ff4d4f, #ffd166, #45d483, #6ea8fe, #a371f7, #ff4d4f);
  border: 2px solid var(--line); box-sizing: border-box;
}
.bg-swatch-picker-inner {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--custom-bg-color, #0a0d12);
  border: 1.5px solid rgba(0, 0, 0, .45); pointer-events: none;
}
.bg-swatch-picker input[type="color"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer; border: none; padding: 0;
}
.bg-swatch-picker.is-on { outline: 3px solid var(--ink); outline-offset: 2px; }

/* Custom color picker swatch (Issue #53) */
.swatch-picker {
  position: relative; width: 42px; height: 42px; min-height: 42px; padding: 0;
  border-radius: 50%; display: inline-grid; place-items: center; cursor: pointer;
  background: conic-gradient(#ff4d4f, #ffd166, #45d483, #6ea8fe, #a371f7, #ff4d4f);
  border: 2px solid var(--line); box-sizing: border-box;
}
.swatch-picker-inner {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--custom-color, #a371f7);
  border: 1.5px solid rgba(0, 0, 0, .45); pointer-events: none;
}
.swatch-picker input[type="color"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer; border: none; padding: 0;
}
.swatch-picker.is-on { outline: 3px solid var(--ink); outline-offset: 2px; }

/* Ink panel layout preferences (Issue #53) */
.panel[data-panel="ink"].pad-gutter #pad-viewport {
  width: 80%; margin: 6px auto;
}
.panel[data-panel="ink"].controls-top #ink-controls {
  order: 1;
}
.panel[data-panel="ink"].controls-top #pad-viewport {
  order: 2;
}
.panel[data-panel="ink"].controls-top .hint {
  order: 3;
}
.ink-controls {
  display: flex; flex-direction: column; gap: 14px;
}

#cam-local { width: 100%; max-width: 320px; border-radius: var(--radius); background: #000; }
#cam-status { color: var(--dim); font-size: 14px; }

/* How many photos are waiting on the Photos tab. A shot taken by holding a
   button in the top bar lands on a tab you are probably not looking at, so the
   tab says so. */
.tab[data-count]:not([data-count=""])::after {
  content: attr(data-count);
  margin-left: 6px; padding: 0 6px; border-radius: 999px;
  background: var(--accent); color: var(--on-accent); font-size: 11px; font-weight: 700;
}

/* Press and hold (see onLongPress in util.js): the button fills up while you
   hold it, so the gesture is visibly underway rather than either working or
   appearing to do nothing at all. */
.is-holding {
  background-image: linear-gradient(rgba(110, 168, 254, .5), rgba(110, 168, 254, .5));
  background-repeat: no-repeat;
  background-size: 0% 100%;
  animation: holding var(--hold-ms, 700ms) linear forwards;
}
@keyframes holding { to { background-size: 100% 100%; } }
/* Holding a control must not also select its label or raise iOS's callout. */
.panel-btn, .layout-btn { -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }

/* Stills taken from the camera feed: a scrolling row of what you have caught
   so far, big enough to tell one page of a book from the next at arm's length
   on a lectern. */
.shots { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: thin; }
.shot {
  position: relative; flex: 0 0 auto; padding: 0; overflow: hidden;
  width: 132px; aspect-ratio: 4 / 3; min-height: 0; border-radius: var(--radius);
  background: #000;
}
.shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The panel it is currently up in, so four stills in four panels are
   distinguishable from four stills and one panel. */
.shot.is-on { border-color: var(--accent); }
.shot-num {
  position: absolute; left: 4px; bottom: 4px; padding: 1px 6px; border-radius: 6px;
  background: rgba(0, 0, 0, .68); font: 600 11px/1.7 system-ui, sans-serif; color: #fff;
}
.shot-where {
  position: absolute; right: 4px; bottom: 4px; padding: 1px 6px; border-radius: 6px;
  background: var(--accent); font: 700 11px/1.7 system-ui, sans-serif; color: var(--on-accent);
}
/* The two corner actions. Deliberately tap-sized rather than tidy: this gets
   used one-handed, at a lectern, in front of people. */
.shot-del, .shot-save {
  position: absolute; top: 2px; width: 26px; height: 26px;
  display: grid; place-items: center; border-radius: 8px;
  background: rgba(0, 0, 0, .55); color: #fff; font-size: 17px;
}
.shot-del { right: 4px; }
.shot-save { right: 34px; font-size: 15px; }
/* When it was taken: the one thing that tells two photos of the same panel
   apart when there are twenty of them. */
.shot-time {
  position: absolute; top: 4px; left: 4px; padding: 0 4px; border-radius: 5px;
  background: rgba(0, 0, 0, .55); color: #fff; font: 600 9px/1.7 system-ui, sans-serif;
}

/* --- background music -------------------------------------------------------
   A remote for something happening in another room, so it leans on saying what
   is true right now: which track, how far in, and whether it is currently
   ducked under something else. */
#music-quick-bar { display: flex; align-items: center; gap: 10px; min-width: 0; }
.music-quick-chip {
  flex: none; max-width: 46vw; padding: 8px 12px; font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.music-quick-chip.is-on { border-color: var(--accent); background: #16202c; color: var(--ink); }
.music-quick-chip.is-on::before { content: '\266a '; }

.music-now {
  padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel);
}
#music-title { font-size: 17px; font-weight: 600; }
/* A track that will not play says so here, and a dim grey hint is exactly
   what the eye skips over, so the line takes the cue colour instead. */
#music-sub.is-warning { color: var(--cue); }
/* The feedback risk beside "Play through the display's speakers" (Issue
   #147) is worth actually reading, the same reason as above. */
#mic-amplify-warning { color: var(--cue); }
.music-bar {
  height: 6px; margin-top: 10px; border-radius: 999px; overflow: hidden;
  background: #0e1216;
}
#music-elapsed { height: 100%; width: 0; background: var(--accent); transition: width .4s linear; }
#music-scrub { width: 100%; margin: 6px 0 0; cursor: pointer; }
#music-scrub:disabled { opacity: 0.35; cursor: default; }
.music-times { display: flex; justify-content: space-between; margin-top: 5px; font-size: 12px; color: var(--dim); }

/* Prev, play and next belong on one row with the thumb in the middle - the
   shared .big-button is full-width by design, which stacks them. */
.music-transport { display: flex; gap: 10px; align-items: stretch; }
.music-transport button { min-height: 58px; font-size: 17px; font-weight: 600; }
.music-transport .skip { flex: 0 0 72px; font-size: 20px; padding: 0; }
#music-play {
  flex: 1; background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
#music-play:disabled { background: var(--panel-2); border-color: var(--line); color: var(--ink); }

.music-queue { display: flex; flex-direction: column; gap: 4px; max-height: 42vh; overflow: auto; }
.music-row {
  display: grid; grid-template-columns: 26px 1fr auto auto; gap: 10px; align-items: center;
  min-height: 42px; padding: 6px 10px; text-align: left; border-radius: 10px;
}
.music-row.is-on { border-color: var(--accent); background: #16202c; }
.music-row-n { color: var(--dim); font-size: 12px; text-align: center; }
.music-row-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.music-row-artist { color: var(--dim); font-size: 13px; }
.music-row-duration { color: var(--dim); font-size: 13px; font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
#bar-music.is-on, .bottombar .bar-slot.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.bottombar .bar-slot[data-slot-action="laser"].is-on { background: var(--live-fill); border-color: var(--live-fill); color: #fff; }
.bottombar .bar-slot[data-slot-action="spotlight"].is-on { background: #d29922; border-color: #d29922; color: #1a1200; }
.bottombar .bar-slot[data-slot-action="freeze"].is-on { background: var(--cue); border-color: var(--cue); color: #1a1204; }
.bottombar .bar-slot[data-slot-action="blank"].is-on { background: #fff; border-color: #fff; color: #000; }
.bottombar .bar-slot[data-slot-action="take"].is-armed { background: var(--live-fill); border-color: var(--live-fill); color: #fff; }

/* --- automated sets ----------------------------------------------------------
   A saved set (name, and four buttons to start it somewhere) and a build-time
   entry (one step, with its own duration to edit) are different enough shapes
   to want their own rows rather than reusing the music queue's. */
.set-saved-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); margin-bottom: 8px;
}
.set-saved-info { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 140px; }
.set-saved-title { font-weight: 600; font-size: 15px; }
.set-saved-starts { display: flex; gap: 4px; }
.set-start-btn { min-width: 40px; min-height: 40px; padding: 0; font-weight: 700; }

.set-entries { display: flex; flex-direction: column; gap: 4px; max-height: 40vh; overflow: auto; }
.set-row {
  display: grid; grid-template-columns: 22px 1fr auto auto auto auto auto; gap: 8px; align-items: center;
  min-height: 42px; padding: 4px 8px; border-radius: 10px;
}
.set-row-n { color: var(--dim); font-size: 12px; text-align: center; }
.set-row-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
.set-row-secs { width: 56px; min-height: 36px; padding: 0 6px; text-align: center; }
.set-row-move, .set-row-del { min-width: 32px; min-height: 32px; padding: 0; font-size: 14px; }
.set-row-del { color: var(--live); }
/* The running set's jump-list reuses .set-row as a plain tappable button -
   just a number and a title, so the 5-column build layout above does not
   apply to it. */
button.set-row { width: 100%; text-align: left; grid-template-columns: 22px 1fr; cursor: pointer; }
button.set-row.is-on { border-color: var(--accent); background: #16202c; }

.set-now { padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); margin-bottom: 10px; }
#set-now-title { font-weight: 600; margin-bottom: 8px; }
.set-now-list { display: flex; flex-direction: column; gap: 4px; max-height: 30vh; overflow: auto; margin-bottom: 8px; }
.set-now-transport { display: flex; gap: 8px; }
.set-now-transport button { flex: 1; min-height: 46px; }

.poll-option-row { display: flex; gap: 8px; align-items: center; }
.poll-option-row input { flex: 1; }
.poll-option-row button { min-width: 32px; min-height: 32px; padding: 0; font-size: 14px; color: var(--live); }
/* A poll link made in advance (poll-link-ui.js): QR beside the link. */
.poll-link-row { display: flex; gap: 12px; align-items: flex-start; }
.poll-link-qr { flex: none; width: 96px; background: #fff; border-radius: 6px; padding: 4px; }
.poll-link-qr svg { display: block; width: 100%; height: auto; }
.poll-link-text { min-width: 0; flex: 1; }
.poll-link-url { display: block; word-break: break-all; font-size: 13px; }
.poll-link-actions { flex-wrap: wrap; gap: 6px; }
.poll-link .hint { margin: 4px 0; }
@media (max-width: 480px) { .poll-link-row { flex-direction: column; } }
.poll-running-question { font-weight: 650; font-size: 16px; }
#poll-running-code { font-family: ui-monospace, Menlo, monospace; font-weight: 700; letter-spacing: 0.08em; font-size: 15px; }
.poll-bar-row { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.poll-bar-label { display: flex; justify-content: space-between; font-size: 13px; }
.poll-bar-track { height: 10px; background: var(--panel-2); border-radius: 999px; overflow: hidden; }
.poll-bar-fill { height: 100%; background: var(--accent); border-radius: 999px; }
.poll-answer-row {
  display: flex; align-items: center; gap: 8px; background: var(--panel-2);
  border-radius: 8px; padding: 6px 10px; margin-bottom: 6px; font-size: 13px;
}
.poll-answer-row .grow { flex: 1; }
.poll-answer-row.is-hidden { opacity: .5; text-decoration: line-through; }
.poll-answer-hide { min-width: 60px; min-height: 30px; padding: 0 8px; font-size: 12px; }
#poll-action-error { color: var(--live); }

.poll-history-row {
  background: var(--panel-2); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 8px;
}
.poll-history-question { font-weight: 600; font-size: 14px; margin-bottom: 2px; }
.poll-history-row .inline { margin-top: 6px; }
.poll-history-del { color: var(--live); min-width: 32px; }

.toast { transition: opacity .2s ease; opacity: 0; pointer-events: none; }
.toast.is-on { opacity: 1; }

/* The set of countdowns, each showing its own clock: you can see all four at
   a glance, and the controls below drive whichever is selected. */
#timer-chips { gap: 6px; }
.timer-chip {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  min-height: 40px; padding: 5px 11px; font-size: 13px;
}
.timer-chip.is-on { border-color: var(--accent); background: #16202c; color: var(--ink); }
.timer-chip-name { font-weight: 600; }
.timer-chip-time { font-size: 11.5px; color: var(--dim); font-variant-numeric: tabular-nums; }
.timer-chip.is-running .timer-chip-time { color: var(--ok); }
.timer-chip.is-urgent .timer-chip-time { color: var(--cue); }

#timer-readout { font-size: 54px; font-weight: 700; font-variant-numeric: tabular-nums; }
#timer-readout.is-urgent { color: var(--cue); }
.timer-preset { min-width: 62px; }

/* Bottom bar */
.bottombar {
  display: flex; gap: 8px; align-items: center; padding: 10px 12px;
  padding-bottom: calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); background: var(--panel);
}
.bottombar button { font-weight: 600; }
.bottombar .bar-slot {
  flex: 0 1 auto; min-width: 48px; max-width: 110px; height: 42px;
  padding: 0 8px; font-size: 13px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bottombar #freeze.is-on { background: var(--cue); border-color: var(--cue); color: #1a1204; }
.bottombar #blank.is-on { background: #fff; border-color: #fff; color: #000; }
.bottombar .vol { flex: 1 1 200px; max-width: 260px; margin-left: auto; display: flex; align-items: center; gap: 8px; min-width: 0; }
/* A range input's intrinsic min-width would otherwise push it off a phone. */
.bottombar .vol input { flex: 1; min-width: 0; width: 100%; }
#mute { flex: none; width: 44px; height: 42px; padding: 0; }

.bottom-slots-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px; margin-bottom: 8px;
}
.bottom-slot-field { display: flex; flex-direction: column; gap: 4px; }
.bottom-slot-field label { font-size: 11px; color: var(--dim); font-weight: 600; }
.bottom-slot-field select { width: 100%; }

.tab-order-list { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.tab-order-row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px; border: 1px solid var(--line); border-radius: 10px;
}
.tab-order-move { display: flex; gap: 2px; }
.tab-order-move button { min-height: 30px; min-width: 30px; padding: 0; font-size: 12px; }
.tab-order-label { flex: 1; font-size: 14px; }
.tab-order-show { margin: 0; }

/* On a phone the bar has to fit one row: drop the identifiers you only need
   when something is wrong, and let the buttons shrink. */
@media (max-width: 640px) {
  .topbar .brand, #peer-count { display: none; }
  .clock-widget { gap: 6px; }
  .topbar-clock { font-size: 12px; }
  .topbar-pacing { font-size: 11px; padding: 2px 6px 3px; min-height: 24px; }
  .bottombar { gap: 6px; }
  .bottombar button { min-width: 0; padding: 0 4px; }
  .bottombar .bar-slot.mobile-overflow { display: none !important; }
  .bottombar .vol { flex: 1; min-width: 76px; max-width: none; margin-left: 0; gap: 6px; }
  #mute, #bar-play, #bar-music, .bottombar .bar-slot { width: 44px; }
  /* Issue #96: Settings is long enough on a phone that Save/Close at the
     bottom take real scrolling to reach - a copy near the top costs one
     row up here in exchange. Desktop keeps just the ones at the bottom,
     which is already in easy reach on a screen that tall. */
  .setup-top-actions { display: flex; }
}

/* Nothing cued and not frozen: the cue bar folds to a thin "On screen"
   strip across the top of the workspace, at every screen size (Issue #186).
   TAKE, Swap and Clear have nothing to act on, so they go with it. */
.workspace.cue-compact { flex-direction: column; }
.workspace.cue-compact #preview-pane {
  width: auto; flex-direction: row; align-items: center; gap: 10px;
  padding: 6px 12px; border-right: 0; border-bottom: 1px solid var(--line);
}
.workspace.cue-compact #preview-stage { width: 84px; flex: none; border-width: 1px; border-radius: 6px; }
.workspace.cue-compact #preview-pane .meta { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; }
.workspace.cue-compact #take,
.workspace.cue-compact #preview-pane .row,
.workspace.cue-compact #preview-pane details.help { display: none; }

@media (max-width: 900px) {
  .workspace { flex-direction: column; }
  #preview-pane {
    width: auto; border-right: 0; border-bottom: 1px solid var(--line);
    flex-direction: row; align-items: center; padding: 8px 12px;
  }
  #preview-stage { width: 132px; flex: none; }
  #preview-pane .meta { flex: 1; min-width: 0; }
  #take { min-height: 52px; width: 108px; flex: none; }
  #swap, #clear-preview, #preview-pane .hint, #preview-pane details.help { display: none; }
  /* Narrower than the desktop's 170px, but no longer the 112px that cut
     every title short: two columns on a phone, four on an iPad (Issue #191). */
  .tiles { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

/* --- a layout for each screen size (Issue #188) ----------------------------- */

/* The "On screen" strip, on the Slides tab, while it only repeats Now. */
.workspace.strip-repeats #preview-pane { display: none; }

/* Previous/Next in the bottom bar: a phone's Slides tab only (below). */
.bottombar .bar-deck { display: none; }

@media (max-width: 640px) {
  /* Where a thumb rests, in place of the volume slider (mute stays). */
  body[data-tab="slides"] .bottombar .bar-deck {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 1; min-width: 0; font-size: 22px;
  }
  body[data-tab="slides"] .bottombar .vol { flex: none; min-width: 0; }
  body[data-tab="slides"] .bottombar .vol input[type=range] { display: none; }
  body[data-tab="slides"] .deck-nav { display: none; }
  /* Now and Next stay pinned while the notes and the grid scroll under them.
     Sticky only holds inside its own parent, so the wrappers between the pair
     and the rest of the tab are flattened here - #deck-live, which holds the
     notes and the grid too, becomes the box it sticks within.
     -12px: .panels' own padding, so the pair sits flush with its top edge. */
  #deck-live { display: flex; flex-direction: column; gap: 14px; }
  .deck-stage, .deck-stage-main, .deck-stage-notes { display: contents; }
  .confidence-row {
    position: sticky; top: -12px; z-index: 3;
    background: var(--bg); padding: 6px 0 8px; margin-top: -6px;
  }
}

/* Slides beside another tab, on a wide screen (toggled from the tab row). */
body.dual-pane.dual-secondary .panels {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr); gap: 16px; overflow: hidden;
}
body.dual-pane.dual-secondary .panels > .panel { overflow: auto; min-height: 0; }
body.dual-pane.dual-secondary .panel[data-panel="slides"] {
  order: -1; padding-right: 16px; border-right: 1px solid var(--line);
}
/* Slides is open too, in the left column - say so on its tab. */
body.dual-pane.dual-secondary .tab[data-tab="slides"] { border-color: var(--accent); }

/* --- compact density (Issue #209) -------------------------------------------
   Per device, from Settings -> Presentation. Smaller, never below 36px a
   button, and the explanations put away - the controller's only; Settings
   keeps its own, since that is where you would go to find out what something
   does. */
body.compact { --tap: 36px; }
body.compact #app details.help { display: none; }
body.compact .topbar { padding: 4px 10px; gap: 8px; }
body.compact .tabs { gap: 4px; padding: 6px 8px 0; }
body.compact .tab { min-height: 36px; padding: 0 11px; font-size: 13px; }
body.compact .panels { padding: 8px; }
body.compact .panel { gap: 8px; }
body.compact .tiles { gap: 6px; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
body.compact .tile { padding: 8px 10px; min-height: 64px; font-size: 13px; }
body.compact .deck-nav button { min-height: 44px; font-size: 15px; }
body.compact #take { min-height: 46px; }
body.compact .bottombar { gap: 6px; padding: 6px 8px; padding-bottom: calc(6px + env(safe-area-inset-bottom)); }
@media (max-width: 640px) {
  body.compact .confidence-row { top: -8px; }
}

/* ===================== PLANNING ===================================
   The office page. Unlike the controller this is a desk tool: a mouse, a
   keyboard, and a wide screen are all fair assumptions, so it uses a three
   column layout and ordinary-sized controls rather than the controller's
   one-handed 46px targets. It still collapses to one column, because the one
   time you will open it on a phone is the time you forgot something. */

body.plan { min-height: 100%; height: auto; overflow-y: auto; }
body.plan .topbar { position: sticky; top: 0; z-index: 20; background: var(--bg); }
body.plan .tag {
  border: 1px solid var(--line); border-radius: 999px; padding: 2px 10px;
  color: var(--accent); font-size: 12px;
}
body.plan .title-input { width: 260px; min-height: 34px; padding: 6px 10px; font-size: 14px; color: var(--ink); }
body.plan .course-input { width: 130px; min-height: 34px; padding: 6px 10px; font-size: 14px; }
body.plan select.course-input { width: 190px; }
.lectures-group { margin: 18px 0 6px; font-size: 13px; text-transform: none; letter-spacing: 0; color: var(--ink); }
.lecture-row select { min-height: 34px; font-size: 13px; width: auto; }
body.plan .course-create { min-height: 34px; padding: 0 12px; font-size: 13px; }
body.plan .linkish { color: var(--accent); text-decoration: none; font-size: 13px; }
body.plan .linkish:hover { text-decoration: underline; }

#plan-warn {
  background: #2a1e12; border-bottom: 1px solid #6b4a22; color: #ffd9a8;
  padding: 10px 14px; font-size: 13px; line-height: 1.5;
}

.plan-grid {
  display: grid; gap: 0; align-items: start;
  grid-template-columns: 260px minmax(0, 1fr) minmax(0, 26rem);
}
.plan-col { padding: 14px 16px 32px; border-right: 1px solid var(--line); min-width: 0; }
.plan-col:last-child { border-right: 0; }
/* The lecture's own settings, opened like an item (Issue #205). */
.order-settings {
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 10px; align-items: center;
  width: 100%; text-align: left; padding: 8px 12px; margin: 0 0 8px; min-height: 0;
  border: 1px dashed var(--line); background: transparent;
}
.order-settings .order-icon { grid-area: auto; grid-column: 1; grid-row: 1 / span 2; color: var(--ink); }
.order-settings .order-title { grid-area: auto; grid-column: 2; color: var(--ink); }
.order-settings .order-type { grid-area: auto; grid-column: 2; font-size: 12px; color: var(--dim); }
.order-settings.is-on { border-style: solid; border-color: var(--accent); background: var(--panel-2); }
.plan-col h2 { font-size: 15px; margin: 4px 0 4px; }
.plan-col h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); margin: 22px 0 8px; }
.plan-col hr { border: 0; border-top: 1px solid var(--line); margin: 20px 0 14px; }
.col-actions { display: flex; flex-direction: column; gap: 8px; }
.sub-label { display: block; font-size: 12px; color: var(--dim); margin: 10px 0 4px; }
#plan-size.is-warn { color: var(--warn); }
#plan-build.is-stale { color: var(--warn); }

/* Lectures on this machine */
.plan-row {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  width: 100%; text-align: left; padding: 9px 11px; min-height: 0; margin-bottom: 6px;
}
.plan-row.is-on { border-color: var(--accent); background: #16202c; color: var(--ink); }
.plan-row-title { font-weight: 600; font-size: 14px; }
.plan-row-meta { font-size: 11.5px; color: var(--dim); }
/* The planning rail (Issue #204): one primary action, one list. */
.plan-rail-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.plan-rail-head h2 { margin: 0; }
.plan-new-wrap { position: relative; display: inline-flex; }
.plan-new-wrap > button { min-height: 38px; font-size: 14px; }
.plan-new { border-top-right-radius: 0; border-bottom-right-radius: 0; padding: 0 12px; }
.plan-new-more { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left-width: 0; padding: 0 10px; }
.plan-new-wrap > button.is-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.plan-new-wrap > .plan-new-more.is-primary { border-left: 1px solid color-mix(in srgb, var(--on-accent) 25%, transparent); }
.plan-new-menu {
  position: absolute; top: calc(100% + 4px); right: 0; z-index: 30; min-width: 15rem;
  display: flex; flex-direction: column; padding: 4px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
}
.plan-new-menu button { text-align: left; border: 0; background: transparent; min-height: 38px; font-size: 14px; }
.plan-new-menu button:hover { background: var(--panel-2); }
.plan-row-where { color: var(--dim); }
.plan-row-where.is-server { color: var(--ok, #3fb950); }
.plan-row.is-remote { border-style: dashed; background: transparent; }
/* Archiving (Issue #239) */
.plan-row-wrap { position: relative; }
.plan-row-wrap .plan-row { padding-right: 40px; }
.plan-row-archive {
  position: absolute; top: 6px; right: 6px; min-height: 0; width: 30px; height: 30px; padding: 0;
  font-size: 15px; line-height: 1; background: transparent; border-color: transparent; opacity: 0;
}
.plan-row-wrap:hover .plan-row-archive, .plan-row-archive:focus-visible { opacity: 1; border-color: var(--line); background: var(--panel); }
@media (hover: none) { .plan-row-archive { opacity: .75; } }
body.plan button.linkish { background: none; border: 0; padding: 0; min-height: 0; cursor: pointer; }
body.plan button.linkish:hover { text-decoration: underline; }
.plan-archive-bar { display: flex; justify-content: space-between; gap: 8px; margin: 2px 0 4px; font-size: 13px; }
.plan-archive-undo { font-size: 13px; color: var(--dim); margin: 0 0 6px; }
.plan-archived-note { font-size: 13px; color: var(--warn); margin: 0 0 10px; }
.plan-archive-card { width: min(620px, 100%); max-height: calc(100vh - 32px); overflow: auto; }
.plan-archive-card h3 { font-size: 14px; margin: 16px 0 6px; }
.plan-archive-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.plan-archive-head h2 { margin: 0; }
.plan-archive-head button { min-height: 32px; padding: 0 10px; }
#plan-archive-search { width: 100%; margin: 12px 0 8px; }
#plan-tidy-course { width: auto; min-width: 14rem; }
.plan-archive-list { display: flex; flex-direction: column; gap: 4px; max-height: 50vh; overflow: auto; margin: 0 0 10px; }
.plan-archive-item { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; }
.plan-archive-item input[type="checkbox"] { flex: none; }
.plan-archive-item .plan-archive-what { flex: 1; min-width: 0; }
.plan-archive-item .plan-row-title, .plan-archive-item .plan-row-meta { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plan-archive-item button { min-height: 30px; padding: 0 10px; font-size: 13px; }
.plan-archive-list .empty { color: var(--dim); font-size: 13px; margin: 4px 0; }
.plan-sync { font-size: 13px; color: var(--dim); margin: 0 0 10px; }
.plan-sync.is-ok { color: var(--ok, #3fb950); }
.plan-sync.is-warn { color: var(--cue); }
.plan-conflict {
  border: 1px solid var(--cue); border-radius: var(--radius); padding: 10px 12px; margin: 0 0 12px;
  font-size: 13px; background: color-mix(in srgb, var(--cue) 10%, transparent);
}
.plan-conflict p { margin: 0 0 8px; }
.plan-conflict .hint { margin: 8px 0 0; }
.plan-secondary { flex-wrap: wrap; gap: 6px; }
.plan-secondary button { min-height: 36px; font-size: 13px; }

/* The running order */
#order { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.order-row {
  display: flex; align-items: stretch; gap: 8px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 6px 8px 6px 4px;
}
.order-row.is-on { border-color: var(--accent); background: #16202c; }
.order-row.is-dragging { opacity: .4; }
.order-row.is-over { border-color: var(--accent); box-shadow: inset 0 2px 0 var(--accent); }
.order-n {
  flex: none; width: 26px; display: grid; place-items: center;
  color: var(--dim); font-size: 12px; font-variant-numeric: tabular-nums;
  cursor: grab;
}
.order-open {
  flex: 1; min-width: 0; display: grid; gap: 1px 6px; text-align: left;
  grid-template-columns: 24px 1fr auto; grid-template-areas: 'icon title time' 'icon type time' 'note note note';
  background: none; border: 0; padding: 4px 2px; min-height: 0;
}
.order-open:hover { background: none; }
.order-icon { grid-area: icon; font-size: 17px; line-height: 1.2; }
.order-title { grid-area: title; font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.order-type { grid-area: type; font-size: 11px; color: var(--dim); }
.order-time { grid-area: time; font-size: 11px; color: var(--dim); font-variant-numeric: tabular-nums; white-space: nowrap; align-self: center; }
.order-note { grid-area: note; font-size: 11.5px; color: var(--cue); margin-top: 3px; }
.order-tools { flex: none; display: flex; gap: 3px; align-items: center; }
.order-tools button { min-height: 30px; width: 30px; padding: 0; font-size: 13px; }
.order-tools .order-del { color: var(--bad); }

/* Lecture pacing & budgeting (Issue #46) */
.plan-pacing-wrap {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 12px; margin: 8px 0 12px;
}
.plan-pacing-summary { font-size: 13px; color: var(--dim); }
.plan-pacing-summary b { color: var(--ink); }
.pacing-over { color: var(--bad); font-weight: 600; }
.pacing-under { color: var(--ok); }
.pacing-ok { color: var(--ok); font-weight: 600; }
.plan-pacing-track {
  height: 6px; background: var(--panel-2); border-radius: 999px; overflow: hidden; margin-top: 4px;
}
.plan-pacing-bar {
  height: 100%; width: 0%; background: var(--accent); border-radius: 999px;
  transition: width .3s ease, background-color .3s ease;
}
.plan-pacing-bar.is-over { background: var(--live); }
.plan-pacing-bar.is-exact { background: var(--ok); }

/* Add: every type Podium can put on a screen, with its own icon */
.type-picker { display: grid; gap: 6px; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); }
.type-btn {
  display: flex; align-items: center; gap: 8px; text-align: left;
  min-height: 40px; padding: 0 10px; font-size: 13px;
}
.type-icon { font-size: 16px; }

#timers { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.timer-row {
  display: flex; align-items: center; gap: 8px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 4px 4px 4px 11px;
}
.timer-row button { min-height: 32px; width: 32px; padding: 0; }
#timer-new-mins { width: 88px; }

/* What the class sees: the projector's own renderers, in a 16:9 box */
#item-preview-viewport {
  position: relative; width: 100%; aspect-ratio: 16 / 9; max-width: 100%;
  background: #000; border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
}
#item-preview { position: absolute; inset: 0; }
/* Issue #177: this slide's build under the planner's slide stepper. */
.deck-build { margin-top: 6px; }
.deck-build.is-warn .deck-build-text { color: var(--cue); font-weight: 600; }
/* Same "16:9 box, renderer fills it" shape as #item-preview-viewport above,
   for the message editor modal's own live preview (Issue #103). */
#message-preview-box {
  position: relative; width: 100%; aspect-ratio: 16 / 9; max-width: 100%;
  background: #000; border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
}
.thumb {
  display: grid; place-items: center; margin-bottom: 8px;
  aspect-ratio: 16 / 9; background: #0e1216; border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
}
.thumb img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* The running order's number and prep note, on the controller's own tiles */
.tile-order {
  position: absolute; top: 4px; right: 6px;
  font-size: 11px; color: var(--dim); font-variant-numeric: tabular-nums;
}
.tile-note {
  font-size: 11px; color: var(--cue); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
#plan-name { color: var(--accent); }

/* Auto-launch on plan load (Issue #52) */
.autolaunch-box {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 14px; margin-top: 8px;
}
.autolaunch-pane-card {
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px; display: flex; flex-direction: column; gap: 8px;
}
.autolaunch-pane-header {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.pane-badge {
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 11px; padding: 2px 7px; border-radius: 4px;
  background: var(--accent); color: var(--on-accent);
}
.autolaunch-set-box {
  border-top: 1px dashed var(--line); padding-top: 8px; margin-top: 4px;
  display: flex; flex-direction: column; gap: 6px;
}
.autolaunch-set-entry {
  display: flex; align-items: center; gap: 6px;
}
.autolaunch-set-entry select { flex: 1; min-width: 0; }
.autolaunch-set-entry input[type=number] { width: 70px; }

@media (max-width: 1100px) {
  .plan-grid { grid-template-columns: 220px minmax(0, 1fr); }
  #col-item { grid-column: 1 / -1; border-top: 1px solid var(--line); }
}
@media (max-width: 760px) {
  .plan-grid { grid-template-columns: minmax(0, 1fr); }
  .plan-col { border-right: 0; border-top: 1px solid var(--line); }
  body.plan .title-input, body.plan .course-input { width: 100%; }
}

/* Content Management (Issue #54) */
.content-subtabs {
  display: flex;
  gap: 8px;
  border-bottom: 1px solid var(--line);
  margin: 14px 0 16px;
  padding-bottom: 4px;
  overflow-x: auto;
}
.content-subtab {
  min-height: 36px;
  padding: 0 14px;
  border-radius: 6px;
  font-size: 13px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--dim);
  cursor: pointer;
  white-space: nowrap;
}
.content-subtab:hover {
  color: var(--ink);
}
.content-subtab.active {
  background: var(--panel-2);
  border-color: var(--line);
  color: var(--accent);
  font-weight: 600;
}
.content-tab-pane {
  display: none;
}
.content-tab-pane.active {
  display: block;
}

.cat-pills {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.cat-pill {
  min-height: 28px;
  padding: 0 12px;
  border-radius: 999px;
  font-size: 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  color: var(--dim);
  cursor: pointer;
}
.cat-pill:hover {
  color: var(--ink);
}
.cat-pill.active {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  font-weight: 600;
}

.theme-editor-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 10px;
}
@media (max-width: 900px) {
  .theme-editor-split {
    grid-template-columns: 1fr;
  }
}
.theme-code-pane,
.theme-preview-box {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.theme-css-textarea {
  width: 100%;
  height: 380px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 12px;
  line-height: 1.45;
  background: var(--bg);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px;
  resize: vertical;
  white-space: pre;
  tab-size: 2;
  box-sizing: border-box;
}
.theme-preview-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.theme-preview-stage svg,
.theme-preview-stage .marpit {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
}

/* ===================== LANDING =====================================
   index.html, the showcase page. Reuses body.plan's sticky topbar and
   scrollable page (it has the same "read down" shape), and is otherwise its
   own thing - nobody else needs a hero or a pricing table. */

.landing-nav { display: flex; align-items: center; gap: 14px; }
.landing-signin { font-weight: 700; }

.landing-main { max-width: 68rem; margin: 0 auto; padding: 0 20px; }

.landing-hero { padding: 56px 0 40px; text-align: center; }
.landing-eyebrow {
  color: var(--accent); font-size: 13px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; margin: 0 0 14px;
}
.landing-hero h1 { font-size: clamp(26px, 4.4vw, 44px); line-height: 1.15; margin: 0 0 18px; }
.landing-lede { color: var(--dim); font-size: 17px; line-height: 1.55; max-width: 42rem; margin: 0 auto 28px; }
.landing-cta-row { justify-content: center; max-width: 30rem; margin: 0 auto 56px; }
.landing-cta-row .cta-link { text-decoration: none; display: block; flex: 1; }
.landing-cta { width: 100%; }
.landing-cta-secondary {
  width: 100%; min-height: 66px; font-size: 16px; font-weight: 600;
  background: transparent; border-color: var(--accent); color: var(--accent);
}

/* A CSS-only sketch of the two surfaces this whole product is: a display and
   a controller, talking over an encrypted link. Not a screenshot - it never
   goes stale when either page's real UI changes. */
.landing-mock {
  display: flex; align-items: center; justify-content: center;
  gap: 20px; max-width: 46rem; margin: 0 auto; flex-wrap: wrap;
}
.mock-display {
  width: min(360px, 100%); aspect-ratio: 16 / 9; background: #000;
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  display: flex; flex-direction: column;
  box-shadow: 0 18px 50px -22px rgba(0, 0, 0, .7);
}
.mock-display-bar { height: 8px; background: var(--panel-2); flex: none; }
.mock-display-slide {
  position: relative; flex: 1; padding: 14px 18px;
  display: flex; flex-direction: column; gap: 9px; justify-content: center;
}
.mock-slide-line { height: 6px; border-radius: 3px; background: var(--panel-2); }
.mock-ink { position: absolute; inset: 0; }
.mock-ink path { fill: none; stroke: var(--accent); stroke-width: 2.5; stroke-linecap: round; opacity: .85; }

.mock-link { display: flex; flex-direction: column; align-items: center; gap: 7px; min-width: 96px; }
.mock-link-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--ok);
  animation: mock-pulse 2.2s ease-out infinite;
}
.mock-link-label { font-size: 10.5px; color: var(--dim); text-align: center; max-width: 96px; }
@keyframes mock-pulse {
  0% { box-shadow: 0 0 0 0 rgba(69, 212, 131, .55); }
  70% { box-shadow: 0 0 0 11px rgba(69, 212, 131, 0); }
  100% { box-shadow: 0 0 0 0 rgba(69, 212, 131, 0); }
}
@media (prefers-reduced-motion: reduce) { .mock-link-dot { animation: none; } }

.mock-controller {
  width: 156px; background: var(--panel); border: 1px solid var(--line);
  border-radius: 14px; padding: 10px; display: flex; flex-direction: column; gap: 8px;
}
.mock-ctrl-tabs { display: flex; gap: 4px; }
.mock-ctrl-tabs span { flex: 1; height: 5px; border-radius: 3px; background: var(--panel-2); }
.mock-ctrl-tabs span.is-on { background: var(--accent); }
.mock-ctrl-now { height: 54px; border-radius: 8px; background: #000; border: 1px solid var(--line); }
.mock-ctrl-row { display: flex; gap: 6px; }
.mock-ctrl-row span { flex: 1; height: 22px; border-radius: 6px; background: var(--panel-2); }

.landing-section { padding: 40px 0; border-top: 1px solid var(--line); }
.landing-section h2 { font-size: clamp(19px, 2.6vw, 25px); margin: 0 0 24px; text-align: center; }
.landing-grid { display: grid; gap: 16px; }

.landing-scenarios { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.landing-card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; }
.landing-card h3 { margin: 0 0 8px; font-size: 16px; }
.landing-card p { margin: 0; color: var(--dim); font-size: 13.5px; line-height: 1.55; }

.landing-features { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.landing-feature { display: flex; gap: 12px; padding: 4px 0; }
.lf-icon { font-size: 20px; line-height: 1.3; flex: none; width: 26px; text-align: center; }
.landing-feature b { font-size: 14px; }
.landing-feature p { margin: 4px 0 0; color: var(--dim); font-size: 13px; line-height: 1.5; }

.landing-tiers { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.landing-tier { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; text-align: center; }
.landing-tier-recommended { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.landing-tier-price {
  color: var(--accent); font-weight: 700; font-size: 12px;
  letter-spacing: .06em; text-transform: uppercase; margin: 0 0 6px;
}
.landing-tier h3 { margin: 0 0 8px; font-size: 16px; }
.landing-tier p { margin: 0; color: var(--dim); font-size: 13px; line-height: 1.55; }
.landing-tier code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.landing-tiers-note { text-align: center; max-width: 42rem; margin: 20px auto 0; }

.landing-footer { max-width: 68rem; margin: 0 auto; padding: 20px 20px 48px; text-align: center; }
.landing-footer-links { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; margin-bottom: 10px; }
.landing-footer-links a { color: var(--dim); font-size: 13px; text-decoration: none; }
.landing-footer-links a:hover { color: var(--ink); text-decoration: underline; }

@media (max-width: 640px) {
  .landing-hero { padding: 40px 0 32px; }
  .landing-mock { gap: 14px; }
  .mock-link { flex-direction: row; min-width: 0; }
  .mock-link-label { max-width: 140px; text-align: left; }
}


/* ZIP import review screen (Issue #106), on the admin page and the planner. */
.zip-review { display: grid; gap: 10px; margin-top: 8px; }
.zip-review h3, .zip-review h4 { margin: 6px 0 0; }
.zip-items, .zip-needs { display: grid; gap: 6px; }
.zip-row {
  display: grid; grid-template-columns: auto 48px 1fr; gap: 10px; align-items: center;
  padding: 8px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
}
.zip-row.zip-need { grid-template-columns: 48px 1fr; }
.zip-row.is-off { opacity: 0.55; }
.zip-row input[type="checkbox"] { width: 20px; height: 20px; }
.zip-row-main { display: grid; gap: 4px; min-width: 0; }
.zip-row-main code { overflow-wrap: anywhere; font-size: 12px; }
.zip-row-main .hint { margin: 0; }
.zip-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-size: 12px; color: var(--dim); }
.zip-meta select { min-height: 28px; height: 28px; padding: 2px 6px; font-size: 12px; width: auto; }
.zip-thumb { width: 48px; height: 36px; object-fit: cover; border-radius: 4px; background: var(--panel-2); }
.zip-flag { color: var(--cue); font-size: 12px; }
.zip-target { font-size: 12px; overflow-wrap: anywhere; }
.zip-skipped summary { cursor: pointer; color: var(--dim); font-size: 13px; }
.zip-skipped ul, .zip-result { margin: 6px 0 0; padding-left: 18px; font-size: 13px; }
.zip-result .is-bad, .zip-status.is-bad { color: var(--bad); }

/* Issue #173: Settings -> Presentation -> "Start every lecture with". */
.defaults-block .def-group { border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 12px; margin-top: 10px; }
.defaults-block .def-group-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.defaults-block .def-group-head button { min-height: 36px; font-size: 13px; }
.defaults-block .def-row { margin-top: 8px; }
.defaults-block .def-row select, .defaults-block .def-row input[type="text"] { width: 100%; }
.defaults-block .def-row input[type="range"] { width: 100%; }
.def-wm-thumb { max-height: 40px; max-width: 120px; border-radius: 6px; background: repeating-conic-gradient(#ccc 0 25%, #fff 0 50%) 0 0 / 12px 12px; }

/* Issue #175: live streams. The Twitch player fills its panel; a sound-only
   stream keeps playing under a card saying what is on, and the controller's
   small previews show the card alone. */
.r-stream { position: absolute; inset: 0; }
.r-twitch { position: absolute; inset: 0; background: #000; }
.r-twitch-host, .r-twitch-host iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.r-twitch.is-loaded .r-embed-warn { display: none; }
.r-stream-cover { position: absolute; inset: 0; z-index: 2; background: #0e1116; }
.r-stream-cover[hidden] { display: none; }
.r-stream.is-preview { background: #0e1116; }
.r-stream-card {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.4em; padding: 5%; text-align: center; color: #e8ecf1; font-size: clamp(10px, 3.2vmin, 40px);
}
.r-stream-icon { font-size: 2.6em; line-height: 1; }
.r-stream-title { font-size: 1.5em; font-weight: 700; }
.r-stream-sub { color: #aab4c2; }

/* Help text on demand (Issue #189): an explanation is one quiet line until
   asked for, rather than a paragraph standing between a presenter and the
   control it explains. Native <details>, so it opens from the keyboard and
   says "collapsed/expanded" to a screen reader with nothing else to wire. */
details.help { margin: 0; }
details.help > summary {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer; list-style: none;
  color: var(--dim); font-size: 12px; min-height: 24px; user-select: none;
}
details.help > summary::-webkit-details-marker { display: none; }
details.help > summary::before {
  content: 'i'; display: inline-grid; place-items: center; width: 16px; height: 16px;
  border: 1px solid var(--dim); border-radius: 50%; font: italic 700 11px/1 Georgia, serif;
}
details.help[open] > summary { color: var(--ink); }
details.help > .hint { margin-top: 4px; }

/* A note shown once, the first time something is switched on (Issue #189) -
   the live-captions privacy note, the mic feedback warning. */
.confirm-once { border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 12px; background: var(--panel-2); }
.confirm-once .hint { margin: 0 0 8px; }
.confirm-once-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ==========================================================================
   LIGHT / HIGH-CONTRAST THEME (Issue #48, restored for Issue #210)

   Settings -> Presentation has offered "Light" since #48, but this block was
   lost in a merge around the 1.1 release, so choosing it changed almost
   nothing. Restored here, with the colours that carry meaning darkened to
   meet WCAG AA on these pale panels (Issue #156): accent, live, cue and OK
   were 3.0-4.6:1 as first written. test/a11y.test.mjs checks this palette
   as well as the dark one.
   ========================================================================== */

:root[data-theme="light"], body[data-theme="light"] {
  --bg: #f8fafc;
  --panel: #ffffff;
  --panel-2: #f1f5f9;
  --line: #cbd5e1;
  --ink: #0f172a;
  --dim: #475569;
  --accent: #0369a1;
  --live: #b91c1c;
  --cue: #b45309;
  --ok: #15803d;
  --bad: #b42318;
  --warn: #92400e;
  --live-fill: #b91c1c;
  --on-accent: #ffffff;
  --field: #ffffff;
  --code-heading: #7a4f00; --code-sep: #8a5a00; --code-link: #1f5fcc; --code-mono: #a3401a;
  --code-dim: #566273; --code-quote: #4f5b6b; --code-comment: #1b6e46; --code-tag: #b4234a;
  --code-string: #2e6b31; --code-keyword: #6b3fc8;
  color-scheme: light;
}

:root[data-theme="light"] input[type=text],
:root[data-theme="light"] input[type=url],
:root[data-theme="light"] input[type=number],
:root[data-theme="light"] input[type=password],
:root[data-theme="light"] select,
:root[data-theme="light"] textarea {
  background: #ffffff;
  border-color: #cbd5e1;
  color: #0f172a;
}

:root[data-theme="light"] button {
  background: #f1f5f9;
  border-color: #cbd5e1;
  color: #0f172a;
}
:root[data-theme="light"] button:hover {
  background: #e2e8f0;
}
:root[data-theme="light"] button.is-on {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}
:root[data-theme="light"] button.danger-outline,
body.display[data-sheet-theme="light"] .sheet button.danger-outline {
  border-color: #fca5a5;
  color: #b91c1c;
}

:root[data-theme="light"] body.control.is-frozen {
  background: #fef3c7;
}

:root[data-theme="light"] .topbar {
  border-bottom-color: var(--line);
  background: var(--panel);
}

:root[data-theme="light"] .topbar-pacing {
  background: var(--panel-2);
  border-color: var(--line);
  color: var(--dim);
}
:root[data-theme="light"] .topbar-pacing:hover {
  background: #e2e8f0;
  color: var(--ink);
}
:root[data-theme="light"] .topbar-pacing.is-running {
  color: var(--ink);
  border-color: var(--accent);
}
:root[data-theme="light"] .topbar-pacing.is-near-end {
  border-color: var(--cue);
  color: #b45309;
}
:root[data-theme="light"] .topbar-pacing.is-overtime {
  border-color: var(--live);
  color: #991b1b;
  background: #fee2e2;
}
:root[data-theme="light"] .pacing-track {
  background: rgba(0, 0, 0, .08);
}

:root[data-theme="light"] .tab.is-on {
  background: #e2e8f0;
  border-color: var(--accent);
  color: var(--ink);
}

:root[data-theme="light"] .layout-btn.is-on,
:root[data-theme="light"] .panel-btn.is-on,
:root[data-theme="light"] .music-row.is-on,
:root[data-theme="light"] button.set-row.is-on,
:root[data-theme="light"] .order-row.is-on,
:root[data-theme="light"] .order-settings.is-on,
:root[data-theme="light"] .plan-row.is-on,
:root[data-theme="light"] .music-quick-chip.is-on,
:root[data-theme="light"] .timer-chip.is-on {
  border-color: var(--accent);
  background: #e0f2fe;
  color: var(--ink);
}

:root[data-theme="light"] #deck-now-preview,
:root[data-theme="light"] #deck-next-preview,
:root[data-theme="light"] .mirror-frame,
:root[data-theme="light"] .ink-pad-frame {
  background: #f1f5f9;
}
:root[data-theme="light"] #deck-now-preview {
  border-color: var(--accent);
}

:root[data-theme="light"] .bottombar {
  background: var(--panel);
  border-top-color: var(--line);
}
:root[data-theme="light"] #bar-music.is-on,
:root[data-theme="light"] .bottombar .bar-slot.is-on {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}
:root[data-theme="light"] .bottombar #blank.is-on,
:root[data-theme="light"] .bottombar .bar-slot[data-slot-action="blank"].is-on {
  background: #0f172a;
  border-color: #0f172a;
  color: #ffffff;
}
:root[data-theme="light"] .bottombar #freeze.is-on,
:root[data-theme="light"] .bottombar .bar-slot[data-slot-action="freeze"].is-on {
  background: var(--cue);
  border-color: var(--cue);
  color: #ffffff;
}

:root[data-theme="light"] #panic {
  background: #fee2e2;
  border-color: #fca5a5;
  color: #b91c1c;
}

:root[data-theme="light"] #link-help,
:root[data-theme="light"] #help-banner {
  background: #fef3c7;
  border-bottom-color: #fcd34d;
  color: #92400e;
}
:root[data-theme="light"] #link-help.is-mismatch,
:root[data-theme="light"] #relay-help .relay-log {
  background: #fee2e2;
  border-color: #fca5a5;
  color: #991b1b;
}

:root[data-theme="light"] #build-note.is-stale,
:root[data-theme="light"] .stale-note {
  background: #fef3c7;
  border-color: #fcd34d;
  color: #92400e;
}

/* The other warning banners: the same amber, not a dark brown strip across a
   light page. */
:root[data-theme="light"] #update-banner,
:root[data-theme="light"] #plan-warn,
:root[data-theme="light"] #deck-warn,
:root[data-theme="light"] .ql-warn {
  background: #fef3c7;
  border-color: #fcd34d;
  color: #92400e;
}
:root[data-theme="light"] .deck-problems li.is-warning { border-color: #fcd34d; color: #92400e; }

/* TAKE, waiting to be armed: a pale red, as the light theme's other danger
   buttons are. Armed, it is the --live-fill it always was. */
:root[data-theme="light"] #take:not(.is-armed) {
  background: #fee2e2;
  border-color: #fca5a5;
  color: #991b1b;
}

:root[data-theme="light"] .ink-tools .ink-tool-btn:hover {
  background: #e2e8f0;
  color: var(--ink);
}
:root[data-theme="light"] .ink-tools .ink-tool-btn.is-on {
  background: var(--accent);
  color: #ffffff;
}

:root[data-theme="light"] .laser-swatch {
  border-color: #cbd5e1;
}
:root[data-theme="light"] .laser-swatch.is-on {
  border-color: #0f172a;
}

:root[data-theme="light"] .tile-music:active {
  background: #e2e8f0;
}

:root[data-theme="light"] #poll-timer-btns button {
  background: var(--panel-2);
  border-color: var(--line);
  color: var(--ink);
}
:root[data-theme="light"] #poll-timer-btns button:hover {
  background: var(--line);
}

/* The display's own screens in the light theme (Issue #210) - the Go live,
   pairing, setup and standby sheets only. Scoped to .sheet rather than to the
   page, so the stage behind them (what the room sees) never turns pale. */
body.display[data-sheet-theme="light"] .sheet {
  --bg: #f8fafc;
  --panel: #ffffff;
  --panel-2: #f1f5f9;
  --line: #cbd5e1;
  --ink: #0f172a;
  --dim: #475569;
  --accent: #0369a1;
  --live: #b91c1c;
  --cue: #b45309;
  --ok: #15803d;
  --live-fill: #b91c1c;
  --on-accent: #ffffff;
  --field: #ffffff;
  color-scheme: light;
  color: var(--ink);
}
body.display[data-sheet-theme="light"] .sheet .card { background: var(--panel); color: var(--ink); border-color: var(--line); }
:root[data-theme="light"] .warn,
body.display[data-sheet-theme="light"] .sheet .warn { background: #fef3c7; border-color: #fcd34d; color: #92400e; }

/* Tab groups (Issue #197): a thin rule between Present, Room, Sound and
   Setup, and the same groups as headings in Settings' tab list. */
.tab-divider { flex: none; width: 1px; align-self: stretch; margin: 6px 4px; background: var(--line); }
.tab-order-group {
  margin: 10px 0 2px; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
/* Section headings inside a tab that gathers several things - Setup, and the
   microphone at the foot of Mixer. */
.setup-head {
  margin: 10px 0 0; font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
.panel > .setup-head:first-child { margin-top: 0; }

/* --- Deck editor (Issue #226) --------------------------------------------- */
body.deck-editor { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
body.deck-editor .topbar { flex: none; }
.deck-where { max-width: 32ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deck-menu-wrap { position: relative; display: inline-flex; gap: 2px; }
.deck-menu-wrap > button { min-height: 34px; padding: 0 12px; font-size: 13px; }
.deck-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 40; min-width: 260px;
  display: flex; flex-direction: column; gap: 4px; padding: 6px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}
.deck-menu button { text-align: left; min-height: 34px; font-size: 13px; }
.deck-menu hr { border: 0; border-top: 1px solid var(--line); margin: 4px 0; width: 100%; }
.deck-menu-left { left: 0; right: auto; }
.deck-menu-group { display: flex; flex-direction: column; gap: 4px; }
.deck-menu .hint { margin: 2px 4px; max-width: 32ch; font-size: 12px; }
.deck-toolbar .deck-diagram-wrap { gap: 0; }
.deck-mermaid-preview {
  max-height: 14em; overflow: auto; margin: 8px 0; padding: 8px 10px; border-radius: 6px;
  background: var(--bg); border: 1px solid var(--line); font-size: 12px; white-space: pre-wrap;
}
#deck-warn {
  flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  background: #2a1e12; border-bottom: 1px solid #6b4a22; color: #ffd9a8;
  padding: 8px 14px; font-size: 13px; line-height: 1.5;
}
#deck-warn[hidden] { display: none; }
#deck-warn button { min-height: 30px; font-size: 12px; padding: 0 10px; }

.deck-grid {
  flex: 1; min-height: 0; display: grid;
  grid-template-columns: 200px minmax(0, 1fr) minmax(0, 30rem);
}
.deck-col { min-width: 0; min-height: 0; border-right: 1px solid var(--line); overflow-y: auto; padding: 10px 12px 24px; }
.deck-col:last-child { border-right: 0; }
.deck-col h2 { font-size: 14px; margin: 0; }
.deck-col h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); margin: 18px 0 8px; }
.deck-col h3:first-child { margin-top: 2px; }
.deck-col-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.deck-col-head button { min-height: 30px; padding: 0 10px; font-size: 12px; }

.deck-code-col { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.deck-toolbar {
  flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  padding: 6px 8px; border-bottom: 1px solid var(--line); background: var(--bg);
}
.deck-toolbar button { min-height: 30px; padding: 0 9px; font-size: 13px; }
.deck-toolbar button[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.deck-toolbar .check { font-size: 12px; color: var(--dim); }
.deck-code { flex: 1; min-height: 0; overflow: hidden; }
.deck-code .cm-editor { height: 100%; }
.deck-slide-panel { flex: none; border-top: 1px solid var(--line); padding: 8px 12px 10px; background: var(--bg); }
.deck-slide-panel h3 { margin: 0 0 6px; font-size: 12px; text-transform: none; letter-spacing: 0; color: var(--ink); }
.deck-slide-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; font-size: 13px; }
.deck-slide-row label { color: var(--dim); }
.deck-slide-row input[type=text] { width: 150px; min-height: 30px; padding: 4px 8px; font-size: 13px; }
.deck-slide-row input[type=color] { width: 40px; min-height: 30px; padding: 2px; }
.deck-slide-row select, .deck-slide-row button { min-height: 30px; font-size: 13px; width: auto; }
.deck-slide-panel textarea { width: 100%; font-size: 13px; }

.deck-preview-viewport {
  position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000;
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
}
#deck-preview { position: absolute; inset: 0; container-type: size; cursor: text; }
.deck-nav { margin-top: 8px; align-items: center; }
.deck-nav button { min-height: 34px; min-width: 44px; }
.deck-problems { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; font-size: 13px; line-height: 1.45; }
.deck-problems li { padding: 6px 8px; border-radius: 8px; background: var(--panel); border: 1px solid var(--line); }
.deck-problems li.is-warning { border-color: #6b4a22; color: #ffd9a8; }
.deck-problems li.is-ok { color: var(--dim); }
.deck-problems button { min-height: 0; padding: 0 4px; margin-right: 4px; font-size: 12px; background: none; border: 0; color: var(--accent); text-decoration: underline; }
.deck-settings { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px 10px; align-items: center; font-size: 13px; }
.deck-settings label { color: var(--dim); }
.deck-settings select, .deck-settings input { min-height: 32px; font-size: 13px; }

.deck-dialog { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; background: rgba(0, 0, 0, .55); padding: 16px; }
.deck-dialog[hidden] { display: none; }
.deck-dialog-card {
  width: min(460px, 100%); background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px 18px; box-shadow: 0 20px 50px rgba(0, 0, 0, .5);
}
.deck-dialog-card h2 { font-size: 16px; margin: 0 0 12px; }
/* Pictures and videos (Issue #226): where from, the choice, and where it is kept. */
.deck-media-card { width: min(560px, 100%); max-height: calc(100vh - 32px); overflow: auto; }
.deck-media-tabs { display: flex; gap: 4px; margin: 0 0 12px; border-bottom: 1px solid var(--line); }
.deck-media-tabs button {
  min-height: 32px; padding: 0 10px; font-size: 13px; background: none; border: 0;
  border-bottom: 2px solid transparent; border-radius: 0; color: var(--dim);
}
.deck-media-tabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }
.deck-media-tabs button[hidden] { display: none; }
.deck-media-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 6px;
  max-height: 220px; overflow: auto; margin-top: 6px;
}
.deck-media-grid.is-list { grid-template-columns: minmax(0, 1fr); }
.deck-media-tile {
  display: grid; gap: 2px; padding: 4px; text-align: left; font-size: 12px; min-height: 0;
  background: var(--panel-2); border: 2px solid transparent; border-radius: 8px; overflow: hidden;
}
.deck-media-grid.is-list .deck-media-tile { grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 8px; }
.deck-media-tile[aria-selected="true"] { border-color: var(--accent); }
.deck-media-tile img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 4px; background: #fff; }
.deck-media-tile span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deck-media-tile small { color: var(--dim); }
.deck-media-chosen { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; }
.deck-media-chosen[hidden] { display: none; }
.deck-media-chosen img { width: 96px; height: 64px; object-fit: contain; background: #fff; border-radius: 6px; }
.deck-media-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0 12px; }
.deck-video-preview { display: block; width: 100%; max-height: 240px; background: #000; border-radius: 8px; margin: 0 0 10px; }
.deck-video-preview[hidden] { display: none; }
/* A deck's earlier versions (Issue #226). */
.deck-versions-list { list-style: none; margin: 8px 0; padding: 0; display: grid; gap: 6px; }
.deck-versions-list li { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 8px; background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px; font-size: 13px; }
.deck-versions-list button { min-height: 30px; font-size: 13px; padding: 0 10px; width: auto; flex: none; }
/* Templates (Issue #226). */
.deck-templates-card { width: min(760px, 100%); max-height: calc(100vh - 32px); overflow: auto; }
.deck-templates-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.deck-templates-head h2 { margin: 0; }
.deck-templates-head button { min-height: 32px; padding: 0 10px; }
.deck-templates-list { display: grid; gap: 8px; margin: 8px 0 14px; }
.deck-template {
  display: grid; grid-template-columns: 168px minmax(0, 1fr); gap: 12px; align-items: start;
  padding: 8px; background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px;
}
.deck-template.is-hidden { opacity: .6; }
.deck-template-thumb { position: relative; aspect-ratio: 16 / 9; background: #fff; border-radius: 6px; overflow: hidden; }
.deck-template-title { font-weight: 600; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.deck-template-badge { font-size: 11px; font-weight: 600; padding: 1px 6px; border-radius: 6px; background: var(--panel); border: 1px solid var(--line); color: var(--dim); }
.deck-template-badge.is-course { color: var(--accent); }
.deck-template-badge.is-mine { color: var(--ok); }
.deck-template .hint { margin: 2px 0 0; }
.deck-template-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.deck-template-acts button, .deck-template-acts select { min-height: 30px; font-size: 13px; padding: 0 10px; width: auto; }
.deck-template-acts button.armed { background: #6b1a1a; border-color: var(--live); }
.deck-templates-save { border-top: 1px solid var(--line); padding-top: 10px; }
.deck-templates-save h3 { font-size: 14px; margin: 0 0 6px; }
@media (max-width: 560px) { .deck-template { grid-template-columns: minmax(0, 1fr); } }

/* Narrow screens: one column, preview under the code. */
@media (max-width: 960px) {
  body.deck-editor { height: auto; overflow: auto; }
  .deck-grid { grid-template-columns: minmax(0, 1fr); }
  /* One row of small slides, scrolled sideways (Issue #231) - not cells as
     wide as the window, cut off by the column's height, which showed only
     each slide's top-left corner. The row itself is laid out inside the
     strip's shadow root (see buildStrip in deck-editor.js). */
  .deck-strip-col { overflow: hidden; padding-bottom: 8px; }
  .deck-code-col { min-height: 70vh; }
  .deck-col { border-right: 0; border-bottom: 1px solid var(--line); }
}

/* --- Quick Look (Issue #242) ---------------------------------------------- */
body.quicklook { display: flex; flex-direction: column; height: 100vh; overflow: hidden; background: var(--bg); }
body.quicklook .topbar { flex: none; }
.ql-bar { flex-wrap: wrap; row-gap: 6px; }
.ql-title { font-weight: 650; max-width: 40ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ql-from { white-space: nowrap; }
.ql-where { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ql-controls { display: flex; flex-wrap: wrap; gap: 4px; }
.ql-controls button { min-height: 34px; padding: 0 12px; font-size: 13px; }
.ql-controls button[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.ql-warn { flex: none; margin: 0; padding: 8px 14px; background: #2a1e12; border-bottom: 1px solid #6b4a22; color: #ffd9a8; font-size: 13px; }
.ql-main { flex: 1; min-height: 0; display: flex; }
.ql-stage {
  position: relative; flex: 1; min-width: 0; display: grid; place-items: center; overflow: hidden;
  background: #000; container-type: size;
}
/* A slide, letterboxed to its own shape rather than the window's. */
.ql-slide {
  position: relative; width: min(100cqw, calc(100cqh * var(--aspect, 1.7778)));
  aspect-ratio: var(--aspect, 1.7778); background: #fff;
}
.ql-fill { position: absolute; inset: 0; }
/* A document: a finger drag scrolls it (quicklook.js, Issue #257), so the
   browser must not take the drag for itself. Pinching to zoom still works. */
.ql-slide.ql-doc-page { touch-action: pinch-zoom; }
/* A document fills the stage: a page is read down a tall screen, not in the
   projector's 16:9 strip of it (fitWidth in renderDocument). */
.ql-slide.ql-doc-fill { width: 100cqw; height: 100cqh; aspect-ratio: auto; }
.ql-stage.is-zoomed { display: block; overflow: auto; }
.ql-stage.is-zoomed .r-image { width: auto; height: auto; max-width: none; background: transparent; }
.ql-stage .r-image { cursor: zoom-in; }
.ql-stage.is-zoomed .r-image { cursor: zoom-out; }
.ql-media { max-width: 100%; max-height: 100%; background: #000; }
video.ql-media { width: 100%; height: 100%; object-fit: contain; }
audio.ql-media { width: min(640px, 90%); }
.ql-set { align-self: start; justify-self: stretch; padding: 24px; background: var(--bg); width: 100%; height: 100%; overflow: auto; }
.ql-set h2 { margin: 0 0 12px; }
.ql-set ol { margin: 0; padding-left: 1.4em; display: flex; flex-direction: column; gap: 6px; }
.ql-set button { text-align: left; }
.ql-back { position: absolute; top: 10px; left: 10px; z-index: 5; min-height: 32px; font-size: 13px; }
.ql-empty { color: var(--dim); padding: 24px; text-align: center; max-width: 52ch; }
.ql-notes { flex: none; width: min(34%, 420px); border-left: 1px solid var(--line); padding: 14px 16px; overflow: auto; background: var(--panel); }
.ql-notes h2 { margin: 0 0 8px; font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }
.ql-notes-text { white-space: pre-wrap; font-size: 17px; line-height: 1.5; }
.ql-notes-text.is-empty { color: var(--dim); font-size: 14px; }
/* The outline (Issue #253): on the left, the notes on the right. */
.ql-outline { flex: none; width: min(26%, 300px); border-right: 1px solid var(--line); padding: 14px 10px; overflow: auto; background: var(--panel); }
.ql-outline h2 { margin: 0 0 8px 6px; font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }
.ql-outline .ql-headings button { min-height: 32px; font-size: 14px; border-color: transparent; background: transparent; }
.ql-outline .ql-headings button:hover { background: var(--panel-2); }
.ql-outline .ql-headings button[aria-current="true"] { border-color: var(--accent); background: var(--panel-2); color: var(--ink); }
.ql-outline .ql-headings .ql-outline-num { color: var(--dim); font-variant-numeric: tabular-nums; margin-right: 6px; }
@media (max-width: 760px) {
  .ql-main { flex-direction: column; }
  .ql-notes { width: auto; max-height: 38%; border-left: 0; border-top: 1px solid var(--line); }
  .ql-outline { width: auto; max-height: 30%; border-right: 0; border-bottom: 1px solid var(--line); }
}
.ql-overlay {
  position: fixed; inset: 56px 16px 16px; z-index: 50; display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 20px 50px rgba(0, 0, 0, .55);
}
.ql-overlay[hidden] { display: none; }
.ql-overlay-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid var(--line); }
.ql-overlay-head h2 { margin: 0; font-size: 15px; }
.ql-overlay-head button { min-height: 32px; padding: 0 10px; }
.ql-grid-cells { flex: 1; overflow: auto; padding: 14px; display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; align-content: start; }
.ql-cell { position: relative; padding: 0; border: 2px solid var(--line); border-radius: 6px; background: #fff; overflow: hidden; min-height: 0; cursor: pointer; }
.ql-cell.is-on { border-color: var(--accent); }
.ql-cell .ql-cell-num { position: absolute; right: 4px; bottom: 4px; z-index: 2; font-size: 11px; padding: 1px 6px; border-radius: 8px; background: rgba(0, 0, 0, .7); color: #fff; }
.ql-cell img, .ql-cell canvas { display: block; width: 100%; height: auto; }
.ql-cell-deck { display: block; width: 100%; grid-column: 1 / -1; }
.ql-checks { inset: 56px 16px auto auto; width: min(520px, calc(100% - 32px)); max-height: calc(100vh - 72px); }
.ql-checks-list { list-style: none; margin: 0; padding: 10px 14px; overflow: auto; display: flex; flex-direction: column; gap: 6px; font-size: 14px; }
.ql-checks-list li { padding: 6px 8px; border-radius: 6px; background: var(--bg); }
.ql-checks-list li.is-warning { border-left: 3px solid #e3a33b; }
.ql-checks-list li.is-info { border-left: 3px solid var(--dim); }
.ql-checks-list li.is-ok { color: var(--dim); }
.ql-checks-list button { background: none; border: 0; padding: 0; min-height: 0; color: var(--accent); cursor: pointer; font: inherit; }
.ql-private { flex: none; margin: 0; padding: 4px 14px; font-size: 12px; color: var(--dim); border-top: 1px solid var(--line); }
body.quicklook:fullscreen .ql-private, body.quicklook:fullscreen .ql-warn { display: none; }

/* --- Choose from the server (Issue #241) ----------------------------------- */
.fb-card { width: min(720px, 100%); max-height: calc(100vh - 32px); display: flex; flex-direction: column; }
.fb-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.fb-head h2 { margin: 0; }
.fb-head button { min-height: 32px; padding: 0 10px; }
.fb-filters { display: flex; gap: 8px; margin: 12px 0 8px; }
.fb-filters input { flex: 1; min-width: 0; }
.fb-filters select { width: auto; }
.fb-list { flex: 1; min-height: 120px; overflow: auto; display: flex; flex-direction: column; gap: 4px; }
.fb-list h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); margin: 12px 0 2px; }
.fb-list h3:first-child { margin-top: 0; }
.fb-row { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; }
.fb-row.is-preselected { border-color: var(--accent); }
.fb-icon { flex: none; width: 1.4em; text-align: center; }
.fb-what { flex: 1; min-width: 0; }
.fb-title, .fb-meta { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fb-title { font-weight: 600; font-size: 14px; }
.fb-meta { font-size: 12px; color: var(--dim); }
.fb-row button { flex: none; min-height: 30px; padding: 0 10px; font-size: 13px; }
.fb-empty { color: var(--dim); font-size: 13px; margin: 8px 0; }
.fb-foot { margin-top: 10px; }
.plan-add-from-server { margin-top: 8px; width: 100%; }

/* My Files (Issue #243) ------------------------------------------------------ */
.me-profile h2 { font-size: 20px; }
.me-courses { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }
.me-courses-label { font-size: 12px; color: var(--dim); text-transform: uppercase; letter-spacing: .06em; }
.me-course { padding: 2px 9px; border: 1px solid var(--line); border-radius: 999px; font-size: 13px; }
.me-course.is-archived { opacity: .55; }
.me-filters, .me-upload, .me-bulk { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 10px; }
.me-filters input[type=search] { flex: 1 1 16rem; min-width: 0; }
.me-filters select, .me-upload select { width: auto; }
.me-check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--dim); }
.me-upload { padding: 8px 10px; border: 1px dashed var(--line); border-radius: 6px; font-size: 13px; }
.me-panel.is-drop .me-upload { border-color: var(--accent); background: var(--panel-2); }
.me-bulk { padding: 6px 10px; background: var(--panel-2); border-radius: 6px; font-size: 13px; }
/* Admin › Storage (server/space.js). */
.space-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 8px; }
.space-table th, .space-table td { text-align: left; padding: 6px 8px; border-top: 1px solid var(--line); vertical-align: middle; }
.space-table th { color: var(--dim); font-weight: 600; font-size: 12px; }
.space-table td.num, .space-table th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.space-table tr.is-spare td { color: var(--cue); }
.space-table .space-sub { display: block; color: var(--dim); font-size: 12px; }
.space-bar { display: flex; height: 10px; border-radius: 5px; overflow: hidden; background: var(--panel-2); margin: 6px 0 4px; }
.space-bar span { display: block; height: 100%; }
.space-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 6px; vertical-align: -1px; }
.space-confirm input[type=text] { width: 100%; }
@media (max-width: 640px) { .space-courses .space-wide { display: none; } }
/* Select all and Back up (backup-ui.js): one line above the list. */
.me-selectbar { display: flex; align-items: center; gap: 8px; margin: 0 0 8px 8px; }
.me-selectbar .spacer { flex: 1; }
.backup-dialog fieldset { border: 0; padding: 0; margin: 0 0 10px; display: grid; gap: 8px; }
.backup-dialog label { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.backup-dialog label select { width: auto; flex: 1; min-width: 0; }
.backup-skipped ul { margin: 4px 0 0; padding-left: 18px; font-size: 13px; color: var(--dim); max-height: 12em; overflow: auto; }
.me-list { display: flex; flex-direction: column; gap: 4px; }
.me-row { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; }
.me-row.is-archived { opacity: .7; }
.me-nocheck { width: 13px; flex: none; }
.me-chip { flex: none; padding: 1px 8px; border-radius: 999px; font-size: 12px; border: 1px solid var(--line); white-space: nowrap; cursor: help; }
.me-chip.is-owner { border-color: var(--accent); color: var(--accent); }
.me-chip.is-edit { border-color: #6aa36f; color: #8fd095; }
.me-chip.is-view { color: var(--dim); }
.me-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; flex: none; max-width: 50%; }
.me-actions .is-bad { color: var(--bad); }
.me-link { display: inline-flex; align-items: center; border: 1px solid var(--line); border-radius: 6px; color: var(--ink); text-decoration: none; }
.me-dialog { width: min(440px, 100%); }
.me-dialog select { width: 100%; }
.me-password { display: grid; gap: 10px; }
.me-password label { display: grid; gap: 4px; font-size: 13px; color: var(--dim); }
.hint.is-bad { color: var(--bad); }
@media (max-width: 640px) {
  .me-row { flex-wrap: wrap; }
  .me-actions { max-width: none; width: 100%; justify-content: flex-start; }
}
:root[data-theme="light"] .me-chip.is-edit, body[data-theme="light"] .me-chip.is-edit { border-color: #2f7a36; color: #1f6326; }
:root[data-theme="light"] .hint.is-bad, body[data-theme="light"] .hint.is-bad,
:root[data-theme="light"] .me-actions .is-bad, body[data-theme="light"] .me-actions .is-bad { color: #b42318; }

/* Markdown documents on the controller's Now tab (Issue #240) ---------------- */
.doc-tools { display: grid; gap: 8px; }

/* A web page in the live browser (Now tab): an address bar, the page itself
   to tap and drag, and the keys a touch screen has no other way to send. */
.web-tools { display: grid; gap: 8px; margin-top: 8px; }
.web-address, .web-type { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.web-address input, .web-type input { flex: 1 1 12em; min-width: 0; }
.web-address > button:not([type="submit"]) { min-width: 44px; }
.web-screen {
  position: relative; width: 100%; max-height: 60vh; aspect-ratio: 16 / 9; margin: 0 auto;
  border: 2px solid var(--live); border-radius: var(--radius); overflow: hidden;
  background: #101418; touch-action: none; cursor: pointer; outline-offset: 2px;
}
.web-screen canvas { display: block; width: 100%; height: 100%; object-fit: contain; }
.web-screen:not(.has-frame) canvas { visibility: hidden; }
.web-screen:focus-visible { outline: 3px solid var(--accent); }
/* Frozen, or a cue: the room is not seeing this yet. */
.web-screen.is-ahead { border-color: var(--accent); }
.web-screen-note {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); max-width: 85%;
  padding: 8px 12px; border-radius: 8px; color: #dfe6ee; background: rgba(8, 11, 15, .82); text-align: center;
  pointer-events: none;
}
.web-screen-note:empty { display: none; }
.web-row { flex-wrap: wrap; }
.web-video-row { display: flex; gap: 8px 12px; align-items: center; flex-wrap: wrap; margin: 8px 0; }
.web-video-row .hint { flex: 1 1 14rem; }
.web-zoom { display: inline-flex; gap: 4px; align-items: center; margin-left: auto; }
.web-browse { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 6px; }

.doc-scrub-label { display: grid; gap: 4px; font-size: 13px; color: var(--dim); }
.doc-headings { display: flex; flex-direction: column; gap: 2px; max-height: 14rem; overflow: auto; }
.doc-heading { text-align: left; min-height: 32px; padding: 0 10px; font-size: 14px; border-radius: 6px; }
.doc-heading-2 { padding-left: 22px; }
.doc-heading-3 { padding-left: 34px; font-size: 13px; }
.doc-heading-4 { padding-left: 46px; font-size: 13px; }
.doc-heading.is-on { border-color: var(--accent); color: var(--ink); background: var(--panel-2); }
/* A document's headings in Quick Look (Issue #240), now its outline (Issue #253) */
.ql-headings { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.ql-headings button { width: 100%; text-align: left; min-height: 34px; padding: 0 12px; }
.ql-headings .ql-heading-2 button { padding-left: 26px; }
.ql-headings .ql-heading-3 button, .ql-headings .ql-heading-4 button { padding-left: 40px; font-size: 14px; }

/* Guest View: reading a document at your own pace (Issue #240) --------------- */
.doc-reader-toggle {
  position: fixed; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom)); z-index: 40;
  min-height: 40px; padding: 0 14px; border-radius: 999px; font-size: 14px;
  background: rgba(15, 18, 22, .88); color: #fff; border: 1px solid rgba(255, 255, 255, .25);
}
.doc-reader { position: fixed; inset: 0; z-index: 45; display: flex; flex-direction: column; background: #ffffff; color: #1d232b; }
.doc-reader.is-dark { background: #14181d; color: #e8ecf1; }
.doc-reader[hidden] { display: none; }
.doc-reader-bar {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px; padding-top: calc(8px + env(safe-area-inset-top));
  background: #0b0d10; color: #fff; font-size: 14px;
}
.doc-reader-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.doc-reader-back { min-height: 36px; padding: 0 12px; font-size: 14px; }
.doc-reader-scroll { position: relative; flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.doc-reader-marker {
  position: absolute; left: 0; right: 0; height: 0; border-top: 3px solid #e0a800; pointer-events: none;
}
.doc-reader-marker span {
  position: absolute; right: 8px; top: -1px; padding: 1px 8px; border-radius: 0 0 6px 6px;
  background: #e0a800; color: #1d232b; font-size: 12px; font-weight: 600;
}

/* The deck editor's document mode (Issue #240) ----------------------------- */
body.is-doc .deck-only { display: none !important; }
body:not(.is-doc) .doc-only { display: none !important; }

/* Light or dark on every page (theme.js) -------------------------------------- */
.theme-toggle { min-width: 36px; min-height: 32px; padding: 0 8px; font-size: 16px; line-height: 1; flex: none; }
.me-appearance { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 14px; }
.me-appearance label { font-size: 14px; color: var(--dim); }
.me-appearance select { width: auto; min-width: 12rem; }

/* Word and RTF files (Issue #258): document or PDF ------------------------------ */
.word-choice { width: min(520px, 100%); }
.word-choice-option { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: start; padding: 10px 12px; margin: 0 0 8px; border: 1px solid var(--line); border-radius: 10px; cursor: pointer; }
.word-choice-option input { margin-top: 3px; }
.word-choice-option > span { display: grid; gap: 4px; }
.word-choice-option:has(input:checked) { border-color: var(--accent); background: var(--panel-2); }
.word-choice-option.is-off { cursor: default; opacity: .75; }

/* A course's roster on My Files (Issue #256) ------------------------------------ */
.roster-import { width: min(560px, 100%); }
.roster-diff { margin: 8px 0; padding-left: 1.2em; display: grid; gap: 4px; font-size: 14px; }
.roster-diff .is-bad strong { color: var(--live); }
.roster-form label { display: grid; gap: 4px; }

/* The controller's Attendance tab (Issue #256). */
.att-list { display: flex; flex-direction: column; gap: 6px; }
.att-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 10px;
}
.att-row.is-flagged { border-color: var(--cue); }
.att-who { display: flex; flex-direction: column; min-width: 0; }
.att-name { font-weight: 600; overflow-wrap: anywhere; }
.att-meta { font-size: 12px; color: var(--dim); }
.att-flag { font-size: 12px; color: var(--cue); font-weight: 600; }
.att-status { flex: none; width: auto; min-width: 110px; }
.att-row.is-unmarked .att-status { color: var(--dim); }

/* Attendance after class, on My Files (Issue #256, phase 3). */
.attn-views { display: inline-flex; gap: 4px; border: 0; margin: 0; padding: 0; }
.attn-range { display: inline-flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.attn-range label { display: inline-flex; gap: 6px; align-items: center; font-size: 13px; color: var(--dim); }
.attn-flag { font-size: 12px; color: var(--cue); font-weight: 600; }
.att-flag.is-dismissed { color: var(--dim); font-weight: 400; }
.attn-review-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.attn-review-head h3 { margin: 0; font-size: 16px; }
.attn-history ul { margin: 6px 0 0; padding-left: 18px; font-size: 13px; color: var(--dim); }
.attn-grid-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; }
.attn-grid { border-collapse: collapse; font-size: 13px; width: max-content; min-width: 100%; }
.attn-grid th, .attn-grid td { padding: 6px 8px; border-bottom: 1px solid var(--line); text-align: center; white-space: nowrap; }
.attn-grid tbody th { text-align: left; font-weight: 600; position: sticky; left: 0; background: var(--panel); }
.attn-grid thead th { font-weight: 600; color: var(--dim); }
.attn-grid tr.is-guest th { font-weight: 400; font-style: italic; }
.attn-cell { font-weight: 700; font-family: ui-monospace, Menlo, monospace; }
.attn-cell.is-present { color: var(--ok); }
.attn-cell.is-late { color: var(--cue); }
.attn-cell.is-absent { color: var(--bad); }
.attn-cell.is-excused { color: var(--dim); }
:root[data-theme="light"] .attn-cell.is-absent, body[data-theme="light"] .attn-cell.is-absent { color: #b42318; }
.attn-total { color: var(--dim); font-variant-numeric: tabular-nums; }

/* Questions with check-in and the parking lot (Issue #256, phase 4). */
.att-question { border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; }
.att-question-prompt { font-weight: 600; margin-bottom: 6px; }
.att-bars { display: flex; flex-direction: column; gap: 4px; }
.att-bar { display: grid; grid-template-columns: minmax(0, 1fr) 40%; gap: 8px; align-items: center; font-size: 13px; }
.att-bar-track { height: 8px; background: var(--line); border-radius: 999px; overflow: hidden; }
.att-bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.att-answers { margin: 0; padding-left: 18px; font-size: 13px; max-height: 180px; overflow: auto; }
.att-actions { display: flex; gap: 6px; flex: none; flex-wrap: wrap; justify-content: flex-end; }
.att-parked.is-answered { opacity: .6; }
.r-att.is-exit .r-att-heading { color: #1f5fcc; }

/* Replay (Issue #132): a recorded lecture played back - the stage (what was
   on screen) and the caption under it, the transcript beside, and the
   playback bar across the bottom. */
body.replay { display: flex; flex-direction: column; min-height: 100vh; background: var(--bg); }
.rp-bar { flex-wrap: wrap; row-gap: 6px; }
.rp-bar .brand { color: inherit; text-decoration: none; }
.rp-title { font-weight: 650; max-width: 40ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rp-meta { white-space: nowrap; }
.rp-main {
  flex: 1; min-height: 0; display: grid; gap: 12px; padding: 12px 16px 16px;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
  grid-template-rows: minmax(0, 1fr) auto auto;
  grid-template-areas: "stage side" "controls controls" "note note";
  height: calc(100vh - 56px);
}
.rp-main[hidden] { display: none; }
.rp-stage-wrap { grid-area: stage; display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.rp-stage {
  flex: 1; min-height: 200px; position: relative; display: flex; align-items: center; justify-content: center;
  background: #000; border-radius: var(--radius); overflow: hidden;
}
.rp-stage img { max-width: 100%; max-height: 100%; object-fit: contain; }
/* Screen video (phase 3): fills the stage, and the still pictures step aside. */
.rp-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000; }
.rp-video[hidden] { display: none; }
.rp-stage.is-video #rp-picture, .rp-stage.is-video .rp-card { visibility: hidden; }
.rp-card { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 24px; text-align: center; color: #e8ecf1; }
.rp-card[hidden] { display: none; }
.rp-scene-title { font-size: clamp(20px, 3vw, 34px); font-weight: 650; }
.rp-scene-note { color: #97a2b0; font-size: 14px; }
.rp-caption {
  margin: 0; min-height: 2.8em; padding: 8px 12px; border-radius: 8px; font-size: 17px; line-height: 1.4;
  background: var(--panel); border: 1px solid var(--line);
}
.rp-caption.is-empty { color: var(--dim); }
.rp-side { grid-area: side; display: flex; flex-direction: column; min-height: 0; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
.rp-side-head { margin: 0; padding: 10px 12px; font-size: 14px; border-bottom: 1px solid var(--line); }
.rp-transcript { list-style: none; margin: 0; padding: 4px; overflow: auto; flex: 1; min-height: 0; }
.rp-transcript .rp-line button {
  display: flex; gap: 10px; align-items: baseline; width: 100%; text-align: left; padding: 6px 8px; border: 0; border-radius: 6px;
  background: none; color: var(--ink); font: inherit; font-size: 14px; cursor: pointer;
}
.rp-transcript .rp-line button:hover, .rp-transcript .rp-line button:focus-visible { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.rp-transcript .rp-line.is-current button { background: color-mix(in srgb, var(--accent) 20%, transparent); box-shadow: inset 3px 0 0 var(--accent); }
.rp-transcript .rp-line-scene button { color: var(--dim); font-size: 12px; font-weight: 600; text-transform: none; }
.rp-at { color: var(--dim); font-size: 12px; font-variant-numeric: tabular-nums; min-width: 3.5rem; }
.rp-none { padding: 8px; }
.rp-controls { grid-area: controls; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rp-play { min-width: 7.5em; font-weight: 650; }
.rp-time { font-variant-numeric: tabular-nums; color: var(--dim); min-width: 9em; text-align: center; }
.rp-scrub { position: relative; flex: 1 1 240px; min-width: 160px; display: flex; align-items: center; }
.rp-scrub input { width: 100%; position: relative; z-index: 1; background: transparent; }
.rp-marks { position: absolute; left: 8px; right: 8px; top: 50%; height: 14px; transform: translateY(-50%); pointer-events: none; }
.rp-mark { position: absolute; top: 0; width: 2px; height: 100%; border-radius: 1px; background: var(--dim); opacity: .55; }
.rp-mark-poll { background: var(--cue); opacity: .9; width: 3px; }
.rp-mark-photo { background: var(--accent); opacity: .9; width: 3px; }
.rp-mics { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.rp-mic { min-height: 32px; padding: 0 10px; font-size: 13px; }
.rp-mic[aria-pressed="false"] { opacity: .6; text-decoration: line-through; }
/* A poll's result and a photo over the stage, at their moments (phase 2). */
.rp-poll {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: min(560px, 88%); max-height: 86%; overflow: auto;
  display: flex; flex-direction: column; gap: 8px; padding: 16px 18px; border-radius: var(--radius);
  background: #0e1216; color: #e8ecf1; box-shadow: 0 8px 30px rgba(0, 0, 0, .5); z-index: 1;
}
.rp-poll[hidden], .rp-photo[hidden] { display: none; }
.rp-poll-tag { color: #ffc94d; font-size: 12px; font-weight: 650; text-transform: uppercase; letter-spacing: .04em; }
.rp-poll-q { font-size: 18px; }
.rp-poll-rows, .rp-poll-answers { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.rp-poll-rows li { display: grid; grid-template-columns: minmax(0, 1fr) 40% auto; gap: 10px; align-items: center; }
.rp-poll-label { overflow-wrap: anywhere; }
.rp-poll-bar { height: 10px; border-radius: 5px; background: rgba(255, 255, 255, .12); overflow: hidden; }
.rp-poll-bar span { display: block; height: 100%; background: #6ea8fe; }
.rp-poll-count { color: #97a2b0; font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rp-poll-answers li { padding: 4px 8px; border-radius: 6px; background: rgba(255, 255, 255, .08); }
.rp-photo {
  position: absolute; right: 12px; bottom: 12px; margin: 0; width: min(34%, 260px); display: flex; flex-direction: column; gap: 4px;
  padding: 6px; border-radius: 8px; background: rgba(14, 18, 22, .9); color: #e8ecf1; font-size: 12px;
}
.rp-photo img { width: 100%; border-radius: 4px; display: block; }
.rp-speed { display: inline-flex; gap: 6px; align-items: center; color: var(--dim); font-size: 13px; }
.rp-audio-note { grid-area: note; margin: 0; }
@media (max-width: 760px) {
  .rp-main {
    height: auto; grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto auto;
    grid-template-areas: "stage" "controls" "side" "note";
  }
  .rp-stage { min-height: 0; aspect-ratio: 16 / 9; flex: none; }
  .rp-side { max-height: 50vh; }
  .rp-time { min-width: 0; }
  /* The controls stay in reach while the transcript scrolls. */
  .rp-controls { position: sticky; bottom: 0; z-index: 2; padding: 8px 0; background: var(--bg); border-top: 1px solid var(--line); }
  .rp-photo { width: 40%; right: 8px; bottom: 8px; }
  .rp-poll { padding: 10px 12px; }
  .rp-poll-q { font-size: 15px; }
}

/* Recording the screen (Issue #132, phase 3): the choice on the Go live
   screen, and the badge the room sees while it runs. */
.arm-record-screen { display: block; margin: 10px auto 0; }
.arm-record-screen[hidden] { display: none; }
.arm-window { display: block; margin: 10px auto 0; }
.screen-rec {
  position: fixed; left: 12px; bottom: 12px; z-index: 60; max-width: min(520px, 80vw);
  padding: 4px 10px; border-radius: 999px; font: 600 12px/1.4 system-ui, sans-serif;
  background: rgba(0, 0, 0, .65); color: #e8ecf1; pointer-events: none;
}
.screen-rec.is-live { color: var(--bad); }
.screen-rec[hidden] { display: none; }

/* The deck editor's link dialog: the QR code it will add. */
.deck-link-preview { display: flex; justify-content: center; }
.deck-link-preview:empty { display: none; }
.deck-link-preview svg { width: 140px; height: auto; background: #fff; }

/* Choosing how a poll looks (poll-look-ui.js). */
.poll-look { display: grid; gap: 6px; }
.poll-look-row { display: flex; align-items: center; gap: 8px; }
.poll-look-row[hidden] { display: none; }
.poll-look-row label { min-width: 7.5em; margin: 0; font-size: 13px; }
.poll-look-row select { flex: 1; }
.poll-look-swatch { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; align-items: center; padding: 8px 12px; border-radius: 8px; border: 1px solid var(--line, rgba(127,127,127,.3)); }
.poll-look-swatch[hidden] { display: none; }
.poll-look-swatch-text { grid-row: span 7; font-weight: 700; font-size: 22px; }
.poll-look-swatch-bar { display: block; height: 7px; border-radius: 999px; }

/* Admin › Content › Marp Themes: Marp's own, Podium's, and those added here. */
.themes-group { margin: 16px 0 4px; font-size: 13px; text-transform: uppercase; letter-spacing: .05em; color: var(--dim); }
.admin-row.is-muted .admin-title { opacity: .6; }

/* A theme's cheat sheet (theme-sheet-ui.js): a sample slide per class, its colours, fonts and directives. */
.theme-sheet-card-outer { width: min(1100px, 100%); max-height: calc(100vh - 32px); overflow: auto; }
.theme-sheet-head { display: flex; align-items: center; gap: 12px; position: sticky; top: -16px; background: var(--panel); padding: 4px 0 8px; z-index: 1; }
.theme-sheet-head h2 { flex: 1; margin: 0; }
.theme-sheet-close { min-width: 36px; font-size: 20px; line-height: 1; }
.theme-sheet h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .05em; color: var(--dim); margin: 18px 0 8px; }
.theme-sheet-about { margin: 0 0 6px; }
.theme-sheet-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
.theme-sheet-card { margin: 0; display: grid; gap: 6px; align-content: start; }
.theme-sheet-slide { border: 1px solid var(--line); border-radius: 6px; overflow: hidden; background: #fff; }
.theme-sheet-card figcaption { display: grid; gap: 3px; font-size: 13px; }
.theme-sheet-card code { font-size: 12px; word-break: break-all; }
.theme-sheet-use { justify-self: start; min-height: 30px; font-size: 13px; }
.theme-sheet-colours { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.theme-sheet-colour { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
.theme-sheet-chip { width: 18px; height: 18px; border-radius: 4px; border: 1px solid var(--line); flex: none; }
.theme-sheet-fonts { margin: 0; padding-left: 18px; }
.theme-sheet-directives { border-collapse: collapse; width: 100%; font-size: 13px; }
.theme-sheet-directives th, .theme-sheet-directives td { text-align: left; vertical-align: top; padding: 5px 8px; border-top: 1px solid var(--line); }
.theme-sheet-directives code { font-size: 12px; white-space: nowrap; }
@media (max-width: 640px) { .theme-sheet-directives code { white-space: normal; word-break: break-all; } }
.deck-theme-row { gap: 6px; }
.deck-theme-row select { flex: 1; min-width: 0; }
/* Admin's theme quick settings. */
.theme-quick { margin: 0 0 12px; border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 12px; }
.theme-quick summary { cursor: pointer; font-weight: 600; }
.theme-quick-grid { display: grid; grid-template-columns: max-content 56px max-content 56px; gap: 8px 12px; align-items: center; margin-top: 8px; }
.theme-quick-grid input[type="color"] { width: 56px; height: 32px; padding: 2px; }
.theme-quick-font { grid-column: span 3; display: flex; gap: 8px; flex-wrap: wrap; }
.theme-quick-font select, .theme-quick-font input { flex: 1; min-width: 140px; }
@media (max-width: 640px) { .theme-quick-grid { grid-template-columns: max-content 56px; } .theme-quick-font { grid-column: span 1; } }
