:root {
  --bg: #14110f;
  --bg-2: #1a1613;
  --card: #1d1916;
  --card-2: #241f1b;
  --line: #322b25;
  --text: #efe6da;
  --text-dim: #a99a89;
  --accent: #e8b87a;
  --accent-dim: #8a6c47;
  --danger: #d98b6a;
  --radius: 12px;
  --radius-sm: 8px;
  /* The chrome is one translucent layer over the picture; every panel reads from
     the same two values so they stay the same material. */
  --glass: rgba(20, 17, 15, 0.82);
  --glass-solid: rgba(20, 17, 15, 0.94);
  /* The chrome dissolves over a second in both directions. The drift is the small
     step toward its own edge that the way out takes; the way back does not animate
     it — it arrives where it belongs (see the hidden rules at the foot of the dock
     block). */
  --chrome-fade: 1s ease;
  --chrome-drift: 12px;
  /* One square icon button. It is a token because #play is round and has to stay
     square even where the narrow layout lets its neighbours shrink. */
  --btn: 38px;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* A faint grain overlay — the lo-fi look, done in CSS rather than an image.
   It stays above every layer, the chrome included, which is what keeps the
   translucent panels looking like they belong to the picture. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 999;
  opacity: 0.035;
  background: repeating-linear-gradient(0deg, #fff 0 1px, transparent 1px 3px);
}

/* #app stays a plain block: the file:// guard hides it with [hidden], and an author
   `display` rule here would beat the user agent's [hidden] { display: none }. */
#app {
  position: relative;
}

/* --------------------------------------------------------------------- scene */

/* The scene is the page now. The bitmap stays 384x216 and CSS scales it — aspect
   ratios other than 16:9 are cropped rather than letterboxed, so the frame is always
   full. Pixels land on fractional screen pixels at most window sizes; that is the
   cost of filling the screen with a fixed-resolution drawing. */
#city {
  position: fixed;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  background: #05060d;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  /* The picture is a button now (ui.js starts the music on a click): the UA tap
     flash on a full-screen canvas is the whole screen blinking, and without
     `manipulation` a second tap — the one that calls the bar back — is read as a
     double tap and zooms the page instead. */
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* The seal's links: the picture draws the character and his QR code (src/scene/seal.js)
   and these two elements only decide what the pointer hits, so they are transparent and
   empty. Each is scaled by the same factor as the canvas and moved every frame by the
   renderer. z-index 1 keeps them under the chrome — the dock's buttons stay on top. */
#seal-body,
#seal-qr {
  position: fixed;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  z-index: 1;
  cursor: pointer;
}

#seal-body[hidden],
#seal-qr[hidden] {
  display: none;
}

#seal-body:focus-visible,
#seal-qr:focus-visible {
  outline: 2px dashed var(--accent);
  outline-offset: 4px;
}

/* -------------------------------------------------------------------- topbar */

.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 16px 28px;
  padding-top: calc(12px + env(safe-area-inset-top));
}

/* The scrim is a layer of its own because the header's box is not: the title stays
   when the rest of the chrome goes, and a background cannot leave an element that
   has to stay. `z-index: -1` keeps it behind the title — the header is a stacking
   context (fixed plus a z-index), so the scrim lands between the picture and the
   text. Absolute, so it is not a flex item and the row is laid out as it was. */
.topbar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to bottom,
    rgba(10, 8, 7, 0.78),
    rgba(10, 8, 7, 0.42) 55%,
    transparent
  );
  transition: opacity var(--chrome-fade);
}

.brand h1 {
  margin: 0;
  font-size: 19px;
  font-weight: 650;
  letter-spacing: 0.01em;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.7);
}

/* The subtitle goes with the scrim; only the h1 stays. It dissolves where it stands
   — a drift would walk it up under the title it belongs to. */
.brand-sub {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-dim);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.7);
  transition: opacity var(--chrome-fade);
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: 6px;
  transition: opacity var(--chrome-fade);
}

#meter {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  display: block;
}

/* ------------------------------------------------------------------ controls */

button {
  font: inherit;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

/* An icon button: the glyph alone, with the name in the tooltip. Sized to the same
   square everywhere so the header row and the dock row line up. */
.icon-btn {
  position: relative; /* the tooltip's containing block */
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--btn);
  height: var(--btn);
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-dim);
}

.icon-btn .icon {
  display: block;
  /* Without this the glyph is the topmost element at the button's centre, and
     elementFromPoint — which the scene probe uses to prove a control is reachable —
     answers with the SVG instead of the button. */
  pointer-events: none;
}

.icon-btn:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.08);
}

.icon-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.icon-btn[aria-pressed="true"],
.icon-btn[aria-expanded="true"] {
  color: #241a10;
  background: var(--accent);
  border-color: var(--accent);
}

/* The transport control is the one button with a permanent fill, in both states.
   The explicit `color` is the point: the glyphs are `currentColor`, so a rule that
   lightens the ink without also lightening the fill paints the pause bars amber on
   amber. That is what turned a playing button into a plain square once already —
   `.icon-btn.accent` sat below `.icon-btn[aria-pressed="true"]` at equal
   specificity and won the colour, so the two bars were drawn and invisible. */
#play {
  background: var(--accent);
  border-color: var(--accent);
  color: #241a10;
}

#play:hover {
  background: #f0c68e;
  border-color: #f0c68e;
  color: #241a10;
}

/* ------------------------------------------------------------------ tooltips */

/* Replaces the native title: the browser's own tooltip needs a second of hovering,
   cannot be styled, and would sit on top of ours. */
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  max-width: min(70vw, 200px);
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: rgba(12, 10, 9, 0.95);
  color: var(--text);
  font-size: 12px;
  line-height: 1.35;
  text-align: center;
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.14s ease;
}

[data-tip]:hover::after,
[data-tip]:focus-visible::after {
  opacity: 1;
}

/* The header sits at the top edge, so its tooltips open downwards; anchored to the
   right so they cannot spill off the side. */
.topbar [data-tip]::after {
  top: calc(100% + 9px);
  bottom: auto;
  left: auto;
  right: 0;
  transform: none;
}

/* ---------------------------------------------------------------------- dock */

/* The dock's own box, and nothing else: the pointer is not what calls it back —
   movement anywhere on the screen does — so there is no invisible strip over the
   picture for it to swallow. The bar dissolves where it stands, and an opacity
   does not change layout, so boxes around it never reflow on the way out or back
   in — and this box is the one the renderer and the probes read while the bar is
   away, so it must not be the layer the fade rides on. */
#dock-zone {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  display: flex;
  align-items: flex-end;
}

/* The fade rides this element's own opacity, which is the one arrangement a
   backdrop-filter survives: an opacity (or filter, or mask) on an *ancestor* makes
   it a backdrop root, and the blur would be sampling nothing. Never move the fade
   up to #dock-zone. */
.dock {
  width: 100%;
  display: grid;
  gap: 9px;
  padding: 9px 14px;
  padding-bottom: calc(9px + env(safe-area-inset-bottom));
  background: var(--glass);
  border-top: 1px solid rgba(232, 184, 122, 0.18);
  backdrop-filter: blur(10px);
  transition: opacity var(--chrome-fade);
}

.dock-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px 6px;
}

/* The two mixers sit in the row where the user's list puts them, framed off from the
   buttons because they are continuous controls rather than openers. */
.dock-vol {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 6px;
  padding: 0 12px;
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
}

.dock-vol .slider-row {
  width: 104px;
  gap: 1px;
}

.dock-vol .slider-label {
  font-size: 11px;
}

/* Hiding: one attribute on #app drives both layers, so they can never disagree.
   A transition runs with the value of the state it is moving *to*, and only these
   rules list the transform: the way out fades and drifts toward its own edge, and
   the way back is a plain fade — the offset is dropped in the first frame, while
   the layer is still invisible, so the chrome arrives where it belongs.
   The pointer-events cut rides the attribute and not the fade: a click has to be
   able to reach the picture the moment the bar is on its way out, and the band
   probe in dev/run-scene.mjs holds the phone section to it. The bar is therefore
   unclickable for the second it takes to dissolve. */
#app[data-chrome="hidden"] .dock {
  opacity: 0;
  transform: translateY(var(--chrome-drift));
  transition: opacity var(--chrome-fade), transform var(--chrome-fade);
}

#app[data-chrome="hidden"] .topbar::before,
#app[data-chrome="hidden"] .brand-sub {
  opacity: 0;
}

#app[data-chrome="hidden"] .topbar-right {
  opacity: 0;
  transform: translateY(calc(-1 * var(--chrome-drift)));
  transition: opacity var(--chrome-fade), transform var(--chrome-fade);
  pointer-events: none;
}

/* The header keeps its box while it is away: it is where the title's own hit box
   lives. The title keeps that box in turn — it is still on screen, and a click that
   fell through the label onto the picture would start the music under a word that
   only names the page. Everything else in the header is out of the pointer's way,
   and the bar's zone is out of the way entirely, which is what lets a tap land on
   the picture where the bar rests. */
#app[data-chrome="hidden"] .topbar {
  pointer-events: none;
}

#app[data-chrome="hidden"] .brand {
  pointer-events: auto;
}

#app[data-chrome="hidden"] #dock-zone {
  pointer-events: none;
}

/* -------------------------------------------------------------- now playing */

/* The track line is a strip over the controls, not a rule across the window: on a
   wide screen a full-width progress bar reads as a divider. */
.now-playing {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
  min-width: 0;
  padding: 0 2px;
  font-size: 12px;
}

.np-top {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex: none;
  min-width: 0;
}

.np-style {
  font-size: 13px;
  font-weight: 650;
  white-space: nowrap;
}

.np-meta {
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.np-bar {
  flex: 1 1 auto;
  min-width: 40px;
  height: 3px;
  margin: 0;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.09);
  overflow: hidden;
}

.np-fill {
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.3s linear;
}

.np-next {
  flex: none;
  color: var(--text-dim);
  white-space: nowrap;
}

/* ------------------------------------------------------------------ popovers */

/* Positioned from the owning button's rect by src/ui/dock.js. `transform` is banned
   in here: on an ancestor it turns position:fixed into position:absolute. */
#popovers {
  position: fixed;
  inset: 0;
  z-index: 31;
  pointer-events: none;
}

.popover {
  position: fixed;
  left: var(--pop-left, 16px);
  bottom: var(--pop-bottom, 120px);
  display: grid;
  gap: 10px;
  width: max-content;
  max-width: min(92vw, 560px);
  /* --pop-max is the room actually left between the button and the header; the
     fixed cap is the comfort limit for the panels that never come close to it. */
  max-height: min(var(--pop-max, 520px), 520px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--glass-solid);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(10px);
  pointer-events: auto;
}

/* Without this the author display above beats the user agent's [hidden] rule and
   every panel shows at once. */
.popover[hidden] {
  display: none;
}

.popover h2 {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.hint {
  margin: 0;
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.45;
}

/* ------------------------------------------------------------------- pills */

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

.pill {
  padding: 7px 13px;
  font-size: 13px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card-2);
}

.pill:hover {
  background: var(--card);
  border-color: var(--accent-dim);
}

.pill[aria-pressed="true"],
.pill[aria-checked="true"] {
  background: var(--accent);
  color: #241a10;
  border-color: var(--accent);
  font-weight: 600;
}

.segmented {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.seg {
  padding: 7px 12px;
  font-size: 13px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card-2);
}

.seg:hover {
  background: var(--card);
  border-color: var(--accent-dim);
}

.seg[aria-checked="true"] {
  background: var(--accent);
  color: #241a10;
  border-color: var(--accent);
  font-weight: 600;
}

/* ----------------------------------------------------------------- sliders */

.sliders {
  display: grid;
  gap: 9px;
}

.slider-row {
  display: grid;
  gap: 4px;
}

.slider-label {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 13px;
  color: var(--text-dim);
  white-space: nowrap;
}

.readout {
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 18px;
  background: transparent;
  margin: 0;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 3px;
  background: var(--line);
}

input[type="range"]::-moz-range-track {
  height: 4px;
  border-radius: 3px;
  background: var(--line);
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -5px;
  border-radius: 50%;
  background: var(--accent);
  border: none;
  cursor: pointer;
}

input[type="range"]::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: none;
  cursor: pointer;
}

input[type="range"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

/* -------------------------------------------------------------------- seed */

.seed-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.seed-row input[type="text"] {
  flex: 1 1 200px;
  min-width: 0;
  font: inherit;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  color: var(--text);
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 11px;
}

.seed-row input[type="text"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.seed-row button {
  padding: 8px 13px;
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--card-2);
}

.seed-row button:hover {
  background: var(--card);
  border-color: var(--accent-dim);
}

/* ------------------------------------------------------------------- stats */

/* A deliberate overlay, not chrome: it stays put when the mouse takes the controls
   away, because a reading you have to keep re-summoning is not a reading. */
#stats-mount {
  position: fixed;
  top: calc(58px + env(safe-area-inset-top));
  right: 16px;
  z-index: 32;
  width: min(92vw, 560px);
  max-height: min(72vh, 560px);
  overflow-y: auto;
  pointer-events: none;
}

#stats-mount > * {
  pointer-events: auto;
}

.stats {
  background: rgba(13, 11, 10, 0.96);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 12px;
}

.stats h3 {
  margin: 0 0 10px;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  font-weight: 600;
}

.stats-body {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 2px 18px;
}

.stats-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 1px 0;
}

.stats-key {
  color: var(--text-dim);
}

.stats-val {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.stats-current {
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid var(--line);
  color: var(--text-dim);
}

/* -------------------------------------------------------------------- stall */

.stall {
  position: fixed;
  inset: 0;
  z-index: 40; /* the last word: when the audio has died, this is the only clickable thing */
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(8, 8, 14, 0.72);
  backdrop-filter: blur(2px);
}

.stall[hidden] {
  display: none;
}

.stall-card {
  max-width: 380px;
  background: var(--card);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  padding: 18px 20px;
  text-align: center;
  line-height: 1.5;
}

.stall-card strong {
  display: block;
  margin-bottom: 6px;
  font-size: 15px;
}

.stall-card p {
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--text-dim);
}

.stall-card button {
  padding: 9px 16px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: #241a10;
  font-weight: 600;
}

kbd {
  font-family: ui-monospace, monospace;
  font-size: 11px;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
}

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

/* --------------------------------------------------------- file:// guard */

#filewarning {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
}

#filewarning[hidden] {
  display: none;
}

.fw-card {
  max-width: 520px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 24px;
  line-height: 1.55;
}

.fw-card h2 {
  margin-top: 0;
}

.fw-card pre {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  overflow-x: auto;
  font-size: 13px;
}

.fw-card a {
  color: var(--accent);
}

/* ------------------------------------------------- probe: scene and nothing else */

/* `dev/run-scene.mjs` screenshots are for looking at the picture. The canvas now
   covers the viewport, so the chrome has to be taken out of the frame by hand —
   the title with it: a permanent label is still chrome, and these shots are of the
   scene and nothing else. */
body.hide-chrome .topbar,
body.hide-chrome #dock-zone,
body.hide-chrome #popovers {
  visibility: hidden;
}

/* ------------------------------------------------------------------ narrow */

/* Two rows: the buttons first, the two mixers on their own line. The buttons wrap
   again on their own if nine of them still do not fit. */
@media (max-width: 720px) {
  .topbar {
    padding: 10px 12px 26px;
    padding-top: calc(10px + env(safe-area-inset-top));
  }

  .brand h1 {
    font-size: 16px;
  }

  .brand-sub {
    display: none;
  }

  #meter {
    width: 92px;
    height: 22px;
  }

  .dock {
    padding: 8px 10px;
    padding-bottom: calc(8px + env(safe-area-inset-bottom));
  }

  .dock-bar {
    gap: 4px;
  }

  /* The nine buttons share one row by growing and shrinking into it rather than
     wrapping, which is what leaves a last row holding a single stray button. The
     mixers are pushed to a line of their own, so the row the bar breaks at does not
     depend on where the mixers sit in the markup. */
  .dock-bar > .icon-btn {
    order: 1;
    flex: 1 1 0;
    min-width: 0;
    max-width: 56px;
  }

  /* Except the transport control, which keeps its full square while the rest of the
     row shrinks around it: it is the one button that has to be findable by feel, and
     a fixed square is also what its corner radius is drawn for. */
  .dock-bar > #play {
    flex: 0 0 auto;
    width: var(--btn);
    height: var(--btn);
  }

  .dock-vol {
    order: 2;
    flex: 1 1 100%;
    justify-content: space-around;
    margin: 0;
    padding: 7px 0 0;
    border: none;
    border-top: 1px solid var(--line);
  }

  .dock-vol .slider-row {
    width: min(42%, 190px);
  }

  /* The track line is the only thing on screen that changes for minutes at a time.
     The 3 px bar it gives up was stretched across the whole phone and read as a rule
     under the text rather than as progress, so on a narrow screen the information
     takes the room instead: the key, the tempo and the section. "Up next" still goes:
     two lines of track data on a phone push the controls off the bottom. */
  .np-next,
  .np-bar {
    display: none;
  }

  .np-top {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* The longest of these is at the edge of a 320 px screen, so it truncates rather
     than wrapping the strip into two lines, which would move the buttons. */
  .np-style {
    flex: none;
  }

  .np-meta {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* A sheet rather than a balloon: on a phone the panel spans the width and the
     anchor left over from the desktop layout is simply ignored. */
  .popover {
    left: 8px;
    right: 8px;
    width: auto;
    max-width: none;
    max-height: min(var(--pop-max, 55dvh), 55dvh);
  }

  #stats-mount {
    left: 8px;
    right: 8px;
    width: auto;
  }
}

/* Nine of these in one row on a small phone leaves the glyphs all but touching. The
   bitmaps are drawn on a 12-wide grid and stay readable at 20 px, and the finger
   target is the button, not the ink. */
@media (max-width: 420px) {
  .dock-bar .icon {
    width: 20px;
    height: 20px;
  }
}

/* No hover means no tooltips, which is why the buttons get thumb-sized here. The dock
   itself behaves as it does on a desk: a tap is the movement that calls it back, and
   the idle countdown takes it away again (src/ui/dock.js). */
@media (hover: none) {
  :root {
    --btn: 42px;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Pseudo-elements are not matched by `*`: the header's scrim lives on
     `.topbar::before`, and without the two extra selectors it would have kept its
     transition while everything else snapped. */
  *,
  *::before,
  *::after {
    transition: none !important;
  }
}
