/* ==========================================================================
   Corporate Weapon
   --------------------------------------------------------------------------
   One rule governs this whole interface, and it is the fix for the playtest:

     COLOUR = WHERE YOU ARE.  BLACK = WHAT YOU DO.

   The browser window IS the work laptop. Above, an app window fills the
   screen in the colours of the kind of tool it is — mail blue, chat violet,
   the aubergine channel sidebar, network blue, service-desk slate — with
   that kind of tool's structure: folder rails, message lists, week grids,
   margins, field tables. You should know which app you are in before you
   read a word. (Every one of those hexes sits deliberately a few units off
   the corresponding real product's registered colour, and every wordmark is
   the fictional company's own: see the note above .app--teams.)

   Below, under a hard hi-vis edge, sits the one thing that is never part of
   the fiction: the decision zone. Black ground, amber accents, the stakes,
   the question, and six lettered options. It never moves and it can never be
   mistaken for the conversation.

   Type: the display face is the break-room printer sign (Impact/DIN
   Condensed) and it is reserved for the game's own voice — the wordmark, the
   question, the verdict. Apps use the system UI stack, which on Windows is
   literally Teams' and Outlook's own face. Mono is for machine data: clocks,
   addresses, routing, ticket fields.
   ========================================================================== */

:root {
  /* the machine */
  --ink:       #0f1319;  /* the decision zone, the OS, the locked screen */
  --ink-2:     #1a2130;  /* a raised control on ink */
  --ink-3:     #131926;  /* the dock's own ground */
  --ink-line:  #2b3446;  /* hairline on ink */
  --on-ink:    #eef1f6;  /* body text on ink */
  --on-ink-2:  #a9b6cb;  /* secondary text on ink (7.3:1 on --ink) */

  /* the desk */
  --desk:      #dfe4ec;  /* behind an app window */
  --paper:     #ffffff;
  --line:      #d8dde6;
  --line-soft: #eceff4;
  --text:      #171c25;
  --muted:     #5c6675;  /* 6.1:1 on white */

  /* the game's own voice */
  --hivis:     #ffbe0b;
  --hivis-dk:  #c98f00;
  --presence:  #3ec37e;
  --danger:    #b3261e;
  --focus:     #1b4fd0;

  --display: Impact, Haettenschweiler, "DIN Condensed", "Roboto Condensed",
    "Arial Narrow", "Helvetica Neue Condensed", sans-serif;
  --ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Mono", Consolas, "Courier New", monospace;

  --r-sm: 0.3125rem;
  --r-md: 0.5625rem;
  --r-pill: 62.4375rem;
  --os-h: 2.625rem;
}

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

/* Everything below is set in rem, so this one dial scales the whole
   interface. Past the laptop width the game was composed on, the root
   grows with the viewport -- capped at 1.375x, and held back on short
   windows -- so a big monitor sees the same ratios as a small one. */
html { font-size: clamp(16px, min(1.1111vw, 1.93vh), 22px); }

body {
  margin: 0;
  font-family: var(--ui);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--text);
  background: var(--ink);
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p { margin: 0; }
button, input { font: inherit; color: inherit; }
.hidden { display: none !important; }

.sr-only {
  position: absolute;
  width: 0.0625rem; height: 0.0625rem;
  padding: 0; margin: -0.0625rem;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

:focus-visible { outline: 0.1875rem solid var(--focus); outline-offset: 0.125rem; }

/* --------------------------------------------------------------------------
   The machine: the app is the viewport, at every size
   -------------------------------------------------------------------------- */

#app {
  position: relative;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ink);
}

.screen {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ink);
  color: var(--on-ink);
  animation: screen-in 160ms ease-out both;
}

.screen:focus { outline: none; }

/* The away-from-the-desk screens: one centred column that scrolls if the
   viewport is short. */
.screen--dark {
  --focus: var(--hivis);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.pad {
  width: 100%;
  max-width: 40rem;
  margin: auto;
  padding: 2rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1.125rem;
}

/* See showScreen() in js/main.js: the incoming screen ignores pointer events
   for a beat so a double-tap's second click cannot land on whatever moved
   under the finger — "Clock in" sits directly over the option grid. */
#app.is-switching .screen { pointer-events: none; }

@keyframes screen-in {
  from { opacity: 0; transform: translateY(0.1875rem); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   Shared bits
   -------------------------------------------------------------------------- */

.eyebrow {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--on-ink-2);
  padding-bottom: 0.875rem;
  border-bottom: 0.0625rem solid var(--ink-line);
}

.btn-primary {
  display: block;
  width: 100%;
  min-height: 3.25rem;
  padding: 0.875rem 1.25rem;
  border: 0;
  border-radius: var(--r-sm);
  background: var(--hivis);
  color: #221a00;
  font-weight: 700;
  font-size: 1.0625rem;
  text-align: center;
  cursor: pointer;
  touch-action: manipulation;
  box-shadow: 0 0.1875rem 0 var(--hivis-dk);
  transition: transform 90ms ease, box-shadow 90ms ease, background-color 120ms ease;
}

.btn-primary:hover { background: #ffc93b; }
.btn-primary:active { transform: translateY(0.1875rem); box-shadow: 0 0 0 var(--hivis-dk); }

.avatar {
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  background: var(--avatar-color, #6a7383);
  color: #fff;
  font-size: 0.78125rem;
  font-weight: 700;
  text-transform: uppercase;
  user-select: none;
}

.avatar--sm { width: 1.5rem; height: 1.5rem; font-size: 0.65625rem; }

/* --------------------------------------------------------------------------
   Landing — the lock screen
   -------------------------------------------------------------------------- */

#screen-landing .pad { gap: 0; max-width: 35rem; }

#screen-landing h1 {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: clamp(2.4rem, 11vw, 4.4rem);
  line-height: 0.84;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: #f6f8fc;
  margin: 1.625rem 0 0;
}

#screen-landing h1 .wm-accent { color: var(--hivis); }

.lock-status { display: flex; flex-direction: column; gap: 0.25rem; }

.lock-time {
  font-family: var(--mono);
  font-size: 2.625rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: #f2f5f9;
}

.lock-date,
.notif-app {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--on-ink-2);
}

.lock-notifs {
  list-style: none;
  margin: 1.125rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.lock-notifs li {
  display: flex;
  align-items: baseline;
  gap: 0.5625rem;
  padding: 0.5625rem 0.75rem;
  border-radius: var(--r-sm);
  background: var(--ink-2);
  border: 0.0625rem solid var(--ink-line);
  font-size: 0.8125rem;
  color: #cfd6e2;
}

.notif-app { flex: 0 0 auto; font-size: 0.625rem; }

.tagline {
  margin-top: 1.25rem;
  padding-top: 1.125rem;
  border-top: 0.0625rem solid var(--ink-line);
  font-size: 1.0625rem;
  font-weight: 500;
  color: #dfe4ec;
}

/* What this is, how long it takes, and what you get — brief §5a. It sits
   directly under the tagline because it has to be above the fold: the old
   landing screen never said any of it, and a playtester asked. */
.purpose {
  margin-top: 0.875rem;
  font-size: 1.03125rem;
  font-weight: 700;
  line-height: 1.35;
  color: #ffffff;
  max-width: 34ch;
  /* One sentence in two lines rather than a line and an orphaned word: the
     first thing the page says about what you are about to do should not end
     with "life." alone on the second line. */
  text-wrap: balance;
}

.purpose-body {
  margin-top: 0.4375rem;
  font-size: 0.90625rem;
  line-height: 1.55;
  color: #cfd6e2;
  max-width: 50ch;
}

/* Who you are, said once, before the first thing lands on you. */
.who-you-are {
  margin-top: 0.8125rem;
  padding-left: 0.75rem;
  border-left: 0.1875rem solid var(--hivis);
  font-size: 0.84375rem;
  line-height: 1.5;
  color: #b6bfcd;
  max-width: 46ch;
}

#screen-landing .btn-primary { margin-top: 1.25rem; }

/* A quiet secondary action: replaying the example is a want, not the path. */
.link-btn {
  align-self: flex-start;
  margin-top: 0.6875rem;
  padding: 0.25rem 0;
  border: 0;
  background: none;
  color: var(--on-ink-2);
  font-family: var(--mono);
  font-size: 0.71875rem;
  letter-spacing: 0.04em;
  text-decoration: underline;
  text-underline-offset: 0.1875rem;
  cursor: pointer;
  touch-action: manipulation;
}

.link-btn:hover { color: var(--hivis); }
.link-btn[hidden] { display: none; }

/* On a laptop the lock screen behaves like one: the machine's status down one
   side, the sign-in down the other, both filling the glass. */
@media (min-width: 900px) {
  #screen-landing .pad {
    max-width: 67.5rem;
    display: grid;
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    column-gap: 4.5rem;
    align-items: center;
    padding: 2.5rem;
  }

  #screen-landing h1 { font-size: clamp(3.6rem, 6.4vw, 5.8rem); margin-top: 1.375rem; }
  .lock-time { font-size: 4rem; }
  .lock-notifs { margin-top: 1.625rem; }
  .landing-inner { gap: 0; }
}

.fine-print {
  margin-top: 1.125rem;
  font-family: var(--mono);
  font-size: 0.71875rem;
  line-height: 1.7;
  color: var(--on-ink-2);
  max-width: 46ch;
}

/* The policy and terms links: present on the landing and results screens
   because an ad network requires a disclosed policy and a player deserves one,
   quiet because neither is the reason anybody came. */
.legal-links {
  margin-top: 0.875rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--on-ink-2);
}

.legal-links a { color: var(--on-ink-2); text-decoration: underline; text-underline-offset: 0.1875rem; }
.legal-links a:hover, .legal-links a:focus-visible { color: var(--hivis); }

#screen-results .legal-links { margin: 1.625rem 0 0.25rem; }

/* With scripting off the landing screen still paints, so the one thing that
   will not work says so where the player is about to press it. */
.noscript-note {
  margin-top: 1rem;
  padding: 0.75rem 0.875rem;
  max-width: 52ch;
  border: 0.0625rem solid var(--hivis-dk);
  border-left-width: 0.25rem;
  border-radius: var(--r-md);
  background: var(--ink-2);
  color: var(--on-ink);
  font-size: 0.84375rem;
  line-height: 1.55;
}

.noscript-note b { color: var(--hivis); }

/* A phone on its side has 24.375rem of height for a wordmark, the purpose, the
   identity line and a button. The purpose is the one thing brief §5a puts
   above the fold, so the lock-screen dressing pays for it: the notification
   stack goes, the wordmark halves, and the two halves sit side by side. */
@media (max-height: 560px) and (min-width: 700px) {
  #screen-landing .pad {
    max-width: 67.5rem;
    display: grid;
    grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
    column-gap: 2.75rem;
    align-items: center;
    padding: 1rem 1.75rem;
  }

  #screen-landing h1 { font-size: clamp(2rem, 5vw, 3rem); margin-top: 0.625rem; }
  .lock-time { font-size: 2.5rem; }
  .lock-notifs { display: none; }
  .landing-inner { gap: 0; }
  .tagline { margin-top: 0.625rem; padding-top: 0.625rem; font-size: 0.875rem; }
  .purpose { margin-top: 0.5625rem; font-size: 0.9375rem; max-width: 52ch; }
  .purpose-body { margin-top: 0.3125rem; font-size: 0.78125rem; line-height: 1.45; max-width: 66ch; }
  /* Your standing is stated on the OS bar on every screen of the game itself,
     so this is the paragraph that goes when the height runs out — not the
     purpose, which is the one thing §5a puts above the fold. */
  .who-you-are { display: none; }
  #screen-landing .btn-primary { margin-top: 0.75rem; }
  .link-btn { margin-top: 0.4375rem; }
  .fine-print { display: none; }
}

/* Very short and narrow at once: the identity line and the fine print go, in
   that order, before the purpose ever does. */
@media (max-height: 560px) and (max-width: 699px) {
  #screen-landing .pad { padding: 0.875rem 1.125rem; }
  .lock-notifs, .who-you-are, .fine-print { display: none; }
  #screen-landing h1 { font-size: 2rem; margin-top: 0.5rem; }
  .lock-time { font-size: 1.875rem; }
  .tagline { margin-top: 0.5625rem; padding-top: 0.5625rem; font-size: 0.875rem; }
  .purpose { margin-top: 0.5rem; font-size: 0.90625rem; }
  .purpose-body { margin-top: 0.3125rem; font-size: 0.78125rem; line-height: 1.45; }
  #screen-landing .btn-primary { margin-top: 0.75rem; }
}

/* --------------------------------------------------------------------------
   The worked example's coach marks (brief §5a)
   --------------------------------------------------------------------------
   The example is the real game with four labels on it, so the labels float
   over the real screen rather than replacing it with a diagram. The layer
   itself never eats a click — only the callout inside it does — because the
   thing being explained is still live underneath.
   -------------------------------------------------------------------------- */
#coach {
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  /* The scrim's colour, one place. Dark enough that the lit region is
     obviously the subject, light enough that the app is still legible
     underneath — this is a tour of a screen, not a modal over it. */
  --coach-dim: rgba(9, 12, 17, 0.62);
}

#coach.hidden { display: none; }

/* The spotlight: a ring around the thing the step is talking about, and the
   rest of the screen dimmed behind it.

   Both come from ONE element and its border box. The dimming is an outer
   box-shadow with a viewport-sized spread, which is clipped by the element's
   own (rounded) border box — so the hole in the scrim is the lit region, it
   has the product's own corner radius for free, and there is exactly one box
   to position instead of four scrim panels to keep in agreement.

   IT IS NEVER A CLICK TARGET. It now covers the whole screen, which is the
   shape of the launch blocker fixed in f658088: #coach sets
   `pointer-events: none` and this inherits it, and it is said again here so
   that the element covering everything can never be the one that swallows a
   press. A box-shadow is not hit-tested and the element's own ground is
   transparent, so the app underneath stays as live as it looks. */
.coach-ring {
  position: fixed;
  pointer-events: none;
  border: 0.125rem solid var(--hivis);
  border-radius: var(--r-md);
  opacity: 0;
  box-shadow:
    0 0 0 0.25rem rgba(255, 190, 11, 0.16),
    0 0 1.375rem rgba(255, 190, 11, 0.28),
    0 0 0 100vmax var(--coach-dim);
  transition:
    top 220ms ease, left 220ms ease, width 220ms ease, height 220ms ease,
    opacity 200ms ease;
}

/* Raised on the frame after the first placement, so the scrim fades up rather
   than snapping in over the conversation the player is already reading. */
.coach-ring.is-lit { opacity: 1; }

.coach-mark {
  position: fixed;
  width: min(21.25rem, calc(100vw - 1rem));
  padding: 0.875rem 1rem 0.8125rem;
  border: 0.0625rem solid var(--hivis-dk);
  border-radius: var(--r-md);
  background: var(--ink-2);
  box-shadow: 0 1.125rem 2.5rem -0.75rem rgba(0, 0, 0, 0.75);
  pointer-events: auto;
}

.coach-head { display: flex; align-items: baseline; gap: 0.5625rem; }

.coach-step {
  font-family: var(--mono);
  font-size: 0.65625rem;
  letter-spacing: 0.1em;
  color: #241b00;
  background: var(--hivis);
  border-radius: var(--r-sm);
  padding: 0.0625rem 0.375rem;
}

.coach-label {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--hivis);
}

.coach-text {
  margin-top: 0.5rem;
  font-size: 0.90625rem;
  line-height: 1.5;
  color: #eaeff7;
}

.coach-row {
  margin-top: 0.75rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.375rem 0.875rem;
}

.coach-next { min-height: 2.5rem; padding: 0 1.125rem; font-size: 0.90625rem; }
.coach-skip { align-self: center; margin-top: 0; }

/* The last beat gets the whole screen: it is not pointing at anything, it is
   telling the player the day is about to start for real — so unlike the four
   steps, this layer DOES swallow clicks, and nothing behind it is live.

   The selector is `#coach.coach--outro`, not `.coach--outro`, and that is the
   whole fix for the bug that trapped every first-time pointer player: the id
   rule above (specificity 1-0-0) sets `pointer-events: none` on this same
   element, so a bare class rule (0-1-0) loses and the computed value stayed
   `none` — taking the panel and its only button down with it by inheritance.
   Keyboard players were fine (the button is focused on open), which is exactly
   why four playtests and every automated check missed it: `element.click()`
   ignores `pointer-events` entirely. Anything added to #coach that a player
   must be able to press needs to beat that id rule or say `auto` for itself. */
#coach.coach--outro { background: rgba(9, 12, 17, 0.82); pointer-events: auto; }

.coach-panel {
  pointer-events: auto; /* same contract as .coach-mark: the callout is live */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(28.75rem, calc(100vw - 2rem));
  padding: 1.5rem 1.5rem 1.375rem;
  border: 0.0625rem solid var(--hivis-dk);
  border-radius: var(--r-md);
  background: var(--ink-2);
  box-shadow: 0 1.625rem 3.75rem -1.125rem rgba(0, 0, 0, 0.85);
  text-align: left;
}

.coach-panel-eyebrow {
  font-family: var(--mono);
  font-size: 0.65625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hivis);
}

.coach-panel-title {
  margin-top: 0.5625rem;
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: clamp(1.5rem, 6vw, 2.1rem);
  line-height: 1.05;
  text-transform: uppercase;
  color: #ffffff;
}

.coach-panel-text {
  margin-top: 0.625rem;
  font-size: 0.90625rem;
  line-height: 1.55;
  color: #cfd6e2;
}

.coach-panel .btn-primary { margin-top: 1.125rem; }

/* A short screen cannot hold a 21.25rem callout under a full-height app window:
   the callout shrinks its own furniture rather than falling off the edge. */
@media (max-height: 560px) {
  .coach-mark { width: min(20rem, calc(100vw - 1rem)); padding: 0.625rem 0.8125rem; }
  .coach-text { margin-top: 0.375rem; font-size: 0.8125rem; line-height: 1.4; }
  .coach-row { margin-top: 0.5625rem; }
  .coach-next { min-height: 2.125rem; font-size: 0.8125rem; }
  .coach-panel { padding: 1rem 1.125rem; }
  .coach-panel-text { font-size: 0.8125rem; }
}

@media (prefers-reduced-motion: reduce) {
  .coach-ring { transition: none; }
}

/* --------------------------------------------------------------------------
   The OS bar — the only chrome that belongs to the machine, not to an app
   -------------------------------------------------------------------------- */

#screen-game { background: var(--ink); }

#os-bar {
  position: relative;
  flex: 0 0 auto;
  height: var(--os-h);
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 0 0.5rem 0 0.875rem;
  background: var(--ink);
  color: var(--on-ink);
  border-bottom: 0.0625rem solid #000;
}

/* The clock is not decoration. Several scenarios turn on how much of the
   morning is left — a model due at 11am when it is 9:02, a rota that pages
   tonight — and the owner played one and said the time "is important context
   here but is not displayed to the user that clearly, it is hiding in a
   corner." It was 0.8125rem of mono in the far corner of the bar. It is now the
   loudest thing on the OS bar after the progress read-out: bigger, heavier,
   and boxed in its own hi-vis chip so the eye lands on it before the company
   name. The briefings state the time in words as well (see
   docs/superpowers/briefing-style.md §7) — this is the other half of that. */
#clock {
  font-family: var(--mono);
  font-size: 0.9375rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--ink);
  background: var(--hivis);
  border-radius: var(--r-sm);
  padding: 0.1875rem 0.5625rem;
}

#company {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--on-ink-2);
  white-space: nowrap;
}

#company::before {
  content: "";
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: var(--r-pill);
  background: var(--presence);
}

/* You, once, on the machine itself — the anchor everyone else is measured
   against. It used to be a box inside every app's participants strip; it is one
   fact about the player, so it is stated one time, on the OS bar, and every
   relation on every screen below reads relative to it. */
#whoami {
  display: flex;
  align-items: baseline;
  gap: 0.3125rem;
  padding: 0.125rem 0.5625rem;
  border-radius: var(--r-pill);
  background: rgba(255, 190, 11, 0.11);
  border: 0.0625rem solid rgba(255, 190, 11, 0.28);
  font-family: var(--mono);
  font-size: 0.65625rem;
  letter-spacing: 0.04em;
  color: #e3c98a;
  white-space: nowrap;
}

#whoami b { color: var(--hivis); letter-spacing: 0.1em; text-transform: uppercase; }

@media (max-width: 819px) { .whoami-more { display: none; } }

#run-progress {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
}

#run-count {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-ink-2);
  white-space: nowrap;
}

/* The workday, filling one tenth per situation. */
#run-bar {
  position: relative;
  width: clamp(4.375rem, 14vw, 9.375rem);
  height: 0.375rem;
  border-radius: var(--r-pill);
  background: #232c3a;
  overflow: hidden;
}

#run-bar::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--day-progress, 0) * 100%);
  background: var(--hivis);
  transition: width 600ms cubic-bezier(0.4, 0, 0.2, 1);
}

#sound-toggle {
  flex: 0 0 auto;
  width: 2.375rem;
  height: 2.375rem;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--on-ink-2);
  cursor: pointer;
  touch-action: manipulation;
}

#sound-toggle:hover { background: rgba(255, 255, 255, 0.09); color: #e7ebf2; }

#sound-toggle svg {
  width: 1.125rem;
  height: 1.125rem;
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
}

#sound-toggle .spk { stroke-linejoin: round; }
#sound-toggle .wave,
#sound-toggle .slash { fill: none; }
#sound-toggle[aria-pressed="true"] .slash,
#sound-toggle[aria-pressed="false"] .wave { display: none; }
#sound-toggle[aria-pressed="true"] { color: var(--hivis); }

/* A playtester on a ~125rem-wide laptop screen missed the LinkedIn top nav
   entirely and called the whole strip above it "too small taskbar at the
   top". Below this width the OS bar is read at arm's length on a phone held
   close; above it, the same 2.625rem strip and 11-0.8125rem type sit a keyboard's
   length away and read as a hairline. --os-h is the one variable both this
   bar and every app-bar below it are built from, so raising it here (and
   scaling the type that shares this row) is the fix for every skin at once,
   not just the one the player named. ~28% on the bar, ~20-25% on its type —
   enough to read as a different bar, not a stretched one. */
@media (min-width: 1100px) {
  :root { --os-h: 3.375rem; }

  #os-bar { gap: 1.125rem; padding: 0 0.625rem 0 1.125rem; }
  #clock { font-size: 1.1875rem; padding: 0.25rem 0.75rem; }
  #company { gap: 0.5rem; font-size: 0.84375rem; }
  #whoami { padding: 0.1875rem 0.6875rem; font-size: 0.78125rem; }
  #run-count { font-size: 0.84375rem; }
  #run-bar { height: 0.4375rem; }
  #sound-toggle { width: 2.625rem; height: 2.625rem; }
  #sound-toggle svg { width: 1.25rem; height: 1.25rem; }
}

/* --------------------------------------------------------------------------
   The stage: ONE app window, and nothing else

   It used to be a two-row grid — the app, then a black answer dock under it.
   The dock's row was `auto`, so at a short viewport the dock's own content
   sized the row and the app row (`minmax(0, 1fr)`) was crushed to a sliver
   under it: measured on the shipped build, an 844×390 laptop-lid viewport left
   the Teams window 5.0625rem tall with the reply zone's box overlapping it by 0.5rem,
   and `elementFromPoint` in the middle of the conversation returned `.ask`.
   The playtest verdict was exactly that: "IT ALSO COVERS THE TEXT ABOVE WHEN
   THERE IS AN ANIMATION OF SOMEONE TYPING."

   There is now one region. The app fills it, the reply zone is a child of the
   app's own pane, and the conversation's scrollport is whatever is left after
   the reply zone has taken its height — reserved, never overlaid.
   -------------------------------------------------------------------------- */

#stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  overflow: hidden;
  background: var(--desk);
}

/* --------------------------------------------------------------------------
   The app window — shared skeleton, one identity per product
   -------------------------------------------------------------------------- */

.app {
  --brand: #35414f;
  --brand-fg: #ffffff;
  --rail-bg: #f4f6fa;
  --rail-fg: #3d4757;
  --rail-active: rgba(17, 106, 185, 0.12);
  --accent: var(--brand);
  /* The composer ground: a shade off the pane, the way every one of these
     products separates "what you are reading" from "what you are writing". */
  --reply-bg: #f6f7fa;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--paper);
  /* The screen around it is dark and sets a near-white text colour. An app
     window is paper: it must never inherit that. */
  color: var(--text);
}

.app-bar {
  flex: 0 0 auto;
  min-height: 2.5rem;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0 0.75rem;
  background: var(--brand);
  color: var(--brand-fg);
}

.app-mark {
  flex: 0 0 auto;
  min-width: 1.375rem;
  height: 1.375rem;
  padding: 0 0.3125rem;
  display: grid;
  place-items: center;
  border-radius: 0.25rem;
  background: rgba(255, 255, 255, 0.9);
  color: var(--brand);
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1;
}

.app-brand {
  font-size: 0.84375rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* Where you are inside the app — the folder, the channel, the tab. On a phone
   this is the whole navigation, so it is never hidden. */
.app-where {
  padding: 0.1875rem 0.5625rem;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.18);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 42vw;
}

/* --------------------------------------------------------------------------
   FURNITURE, NOT OFFERS

   A playtester clicked the invite's Accept button instead of the answer dock.
   That trap is not one control: this build is full of convincing chrome. The
   rule now is simple — a control either does the thing it is shaped like, or
   it stops being shaped like a control.

   Anything with a clean 1:1 answer behind it is WIRED (see the RSVP bar).
   Everything else — search, rails, channel lists, toolbars, the Reply row —
   is furniture: no well, no border, no hover, no pointer. It still says what
   app you are in, which is its whole job.
   -------------------------------------------------------------------------- */

/* Search is a label, not a field: no well to click into. */
.app-search {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 26.25rem;
  padding: 0.25rem 0;
  font-size: 0.78125rem;
  color: rgba(255, 255, 255, 0.66);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-search::before { content: "⌕ "; opacity: 0.8; }

/* Nothing inside an app window is clickable unless it is a real control, and
   a real control is a <button>. Stated rather than inherited, so it is
   checkable: no element in an app window computes cursor:pointer. */
.app :not(button) { cursor: default; }

.app-meta {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.71875rem;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, 0.92);
  white-space: nowrap;
}

.app-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  overflow: hidden;
}

.app-rail,
.app-list { display: none; }

.app-pane {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--paper);
}

/* The one scroller inside an app window. */
.scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  padding: 0.75rem 0.875rem 1.125rem;
  display: flex;
  flex-direction: column;
  gap: 0.5625rem;
}

.scroll:focus { outline: none; }

/* The scroller is a flex column, so everything in it keeps its own height and
   scrolls — a week grid or a ticket field table must never be squashed to fit
   the pane. */
.scroll > * { flex: 0 0 auto; }

/* --------------------------------------------------------------------------
   THE CONVERSATION SITS AGAINST THE QUESTION — AND NOTHING IS EMPTY

   The playtest verdict was "the bottom is too jarring and at the same time, i
   didnt even see the bottom until afterwards". A three-message scenario stacked
   from the top of a full-height pane left ~31.25rem of white between the last
   thing the player read and the question. Bottom-anchoring alone just moved
   that white above the messages ("THIS FRAME NOW COVERS EVERYTHING").

   So the slack is not left anywhere: the conversation-start block (see
   introBlock in kit.js) is the one child of the scroller allowed to grow, and
   it takes whatever height is going. Messages end up flush above the question,
   the top of the pane holds real chrome instead of nothing, and a long
   transcript squeezes the block back to its natural size and scrolls normally.
   -------------------------------------------------------------------------- */
.scroll > .convo-intro { flex: 1 1 auto; }

/* Its content hangs from the BOTTOM of the block, not the middle. Centred, a
   three-message scenario put ~8.125rem of air above the card and ~8.125rem below it:
   two gaps and a floating island. Hung, there is one band — empty space under
   the pane header, which is what the top of a short conversation looks like in
   every one of these products — and the card sits directly on the divider that
   introduces the messages.

   The tint is on the CARD, not on the block that grows. It used to be on the
   block, which meant the slack — 160–16.875rem of it at 1440×1050 once the
   participants strip came out and gave the pane its height back — rendered as
   a tinted panel with a hole in the top of it: a designed region that was
   mostly empty, which is the one thing an empty region must not look like.
   Tinting only the card leaves the slack as plain unfilled conversation
   ground, which is exactly what Teams, Slack and LinkedIn show above a short
   history, and the panel itself now carries no internal air at all. */
.convo-intro {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  margin: -0.75rem -0.875rem 0;
  padding: 0.625rem 0.875rem 0.875rem;
  border-bottom: 0.0625rem solid var(--line);
}

.intro-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1875rem;
  text-align: center;
  /* Bleeds back through .convo-intro's side padding, so the card spans the
     pane the way the block used to. */
  margin: 0 -0.875rem;
  padding: 0.75rem 1.375rem;
  background: linear-gradient(to bottom, rgba(21, 26, 35, 0.055), rgba(21, 26, 35, 0.015));
}

.intro-inner .avatar {
  width: 2.875rem;
  height: 2.875rem;
  font-size: 1.0625rem;
  margin-bottom: 0.25rem;
}

/* The app's own glyph on a tile: the accent colour on a pale wash of itself.

   The tile takes --mark-tint, which defaults to the skin's --rail-active
   because in most skins that IS a pale wash. In the channel skin it is not —
   there the rail sits on aubergine and its active row is solid accent blue, so
   the default resolved to the same colour as the ink and the "#" rendered as
   an empty blue square (measured 1.00:1) on every viewport of all_company and
   ceo_channel. A skin whose --rail-active is solid must therefore say what
   this tile is instead; the invariant is that the tile and the glyph are never
   the same colour. */
.intro-mark {
  display: grid;
  place-items: center;
  width: 2.875rem;
  height: 2.875rem;
  margin-bottom: 0.25rem;
  border-radius: var(--r-md);
  background: var(--mark-tint, var(--rail-active));
  color: var(--accent);
  font-size: 1.375rem;
  font-weight: 700;
}

.intro-title {
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

.intro-line {
  max-width: 52ch;
  font-size: 0.78125rem;
  line-height: 1.45;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* The email thread's own version: not a greeting, the history you will never
   read. It still grows, so the reading pane is never half empty. */
.mail-thread > .convo-intro .intro-inner { align-items: flex-start; text-align: left; }

/* The bleed has to match each scroller's own top padding — and it is only
   correct where the scrollport's top neighbour is a flat, full-bleed pane
   header with its own bottom border, as it is in the channel skin. The desk is
   the one skin whose scrollport sits under a bordered, rounded card, so the
   bleed does not survive the move: it pulled the log's first line up onto the
   card's border, where it was sliced through the middle. `.tk-scroll` has no
   top padding for a bleed to match now (see its rule), so its intro takes the
   base -0.75rem bleed off again rather than inheriting it. */
.chan-log > .convo-intro { margin-top: -0.875rem; }
.tk-scroll > .convo-intro { margin-top: 0; }

/* Navigation collapsed to a strip: medium and narrow screens keep the app's
   folders/channels visible even without room for the rail. */
.app-tabs {
  flex: 0 0 auto;
  display: flex;
  gap: 0.25rem;
  padding: 0.375rem 0.625rem;
  border-bottom: 0.0625rem solid var(--line);
  background: var(--rail-bg);
  overflow-x: auto;
  scrollbar-width: none;
}

.app-tabs::-webkit-scrollbar { display: none; }

.app-tab {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  padding: 0.25rem 0.625rem;
  border-radius: var(--r-pill);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

.app-tab.is-active {
  background: var(--accent);
  color: #fff;
}

.app-tab-badge {
  font-family: var(--mono);
  font-size: 0.625rem;
  opacity: 0.85;
}

/* --- the pane header ------------------------------------------------------ */

.pane-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.625rem 0.875rem;
  border-bottom: 0.0625rem solid var(--line);
  background: var(--paper);
}

.head-meta { flex: 1 1 auto; min-width: 0; }

.head-title {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.96875rem;
  font-weight: 700;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.head-sub {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.75rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.head-count {
  flex: 0 0 auto;
  text-align: right;
  font-family: var(--mono);
  line-height: 1.15;
}

.head-count b {
  display: block;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.head-count small {
  display: block;
  font-size: 0.5625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.presence-dot {
  flex: 0 0 auto;
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: var(--r-pill);
  background: var(--presence);
}

.presence-dot--off { background: #b9c0cc; }

.pane-head.is-removed { filter: grayscale(1); opacity: 0.45; }

/* The same laptop-scale tier as the OS bar above (~68.75rem and up): the app
   bar and the pane header underneath it are the rest of the "taskbar" the
   playtester called too small, so they grow with it rather than staying
   phone-scale under a now-larger OS bar. */
@media (min-width: 1100px) {
  .app-bar { min-height: 3.125rem; gap: 0.8125rem; padding: 0 1rem; }
  .app-mark { min-width: 1.6875rem; height: 1.6875rem; padding: 0 0.375rem; font-size: 0.9375rem; }
  .app-brand { font-size: 1.0625rem; }
  .app-where { padding: 0.25rem 0.6875rem; font-size: 0.9375rem; }
  .app-search { max-width: 32.5rem; font-size: 0.9375rem; }
  .app-meta { font-size: 0.875rem; }

  .pane-head { padding: 0.8125rem 1.125rem; gap: 0.8125rem; }
  .head-title { font-size: 1.1875rem; }
  .head-sub { font-size: 0.875rem; }
  .head-count b { font-size: 1.0625rem; }
  .head-count small { font-size: 0.6875rem; }
}

/* --- what this person is to you -------------------------------------------

   THERE IS NO PARTICIPANTS STRIP. Two of them were built and two were rejected
   — "isn't very clear", then "horrible to show relationships: not minimalistic,
   confusing and all over the place" — because a band of chips, groups, rank
   glyphs and quote blocks is five kinds of furniture answering one question.

   So the relationships are distributed to the places the people already are:
   the conversation header ("Priya · Available · Your manager"), the byline on
   every message, and the routing line under an option ("↳ Greg · same level as
   you"). Those are structural fact and they are on screen BEFORE the choice,
   because judging the situation is the whole task.

   The `context` line is the other thing entirely — "You cannot say no to her",
   "none of the hope left", "May have a point" — a verdict on the player's
   position, and shown while they were choosing it was the app leading the
   witness (brief §2e). It now arrives after the answer, in the conversation,
   as the game's aside on what just happened. */
/* An annotation, not a message and not the narrator's caption: dashed rule,
   muted type, no ground of its own. */
.aside-line {
  align-self: stretch;
  margin: 0.125rem 0;
  padding: 0.3125rem 0 0.3125rem 0.6875rem;
  border-left: 0.125rem dashed var(--line);
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.aside-who {
  color: var(--text);
  font-weight: 700;
}

.aside-who::after { content: " — "; font-weight: 400; color: var(--muted); }

/* --- rails and lists (wide screens) --------------------------------------- */

.app-rail {
  flex: 0 0 13.125rem;
  flex-direction: column;
  gap: 0.125rem;
  padding: 0.625rem 0.5rem;
  overflow-y: auto;
  background: var(--rail-bg);
  color: var(--rail-fg);
  border-right: 0.0625rem solid var(--line);
}

.rail-title {
  padding: 0.25rem 0.5rem 0.5rem;
  font-size: 0.8125rem;
  font-weight: 700;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail-label {
  padding: 0.625rem 0.5rem 0.25rem;
  font-family: var(--mono);
  font-size: 0.59375rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.72;
}

.rail-list { list-style: none; margin: 0; padding: 0; }

.rail-item {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  padding: 0.375rem 0.5625rem;
  border-radius: var(--r-sm);
  font-size: 0.8125rem;
  line-height: 1.3;
}

.rail-item.is-active {
  background: var(--rail-active);
  font-weight: 700;
}

.rail-icon { flex: 0 0 0.875rem; text-align: center; opacity: 0.8; font-size: 0.75rem; }
.rail-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.rail-badge {
  font-family: var(--mono);
  font-size: 0.65625rem;
  font-variant-numeric: tabular-nums;
  opacity: 0.8;
}

.app-list {
  flex: 0 0 18rem;
  flex-direction: column;
  overflow-y: auto;
  background: var(--paper);
  border-right: 0.0625rem solid var(--line);
}

.list-title {
  position: sticky;
  top: 0;
  padding: 0.5625rem 0.75rem;
  background: var(--paper);
  border-bottom: 0.0625rem solid var(--line);
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.list-row {
  padding: 0.5625rem 0.75rem 0.625rem 0.875rem;
  border-bottom: 0.0625rem solid var(--line-soft);
  border-left: 0.1875rem solid transparent;
}

.list-row.is-active {
  border-left-color: var(--accent);
  background: var(--rail-active);
}

.list-top {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.list-who {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.8125rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.list-when {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 0.65625rem;
  color: var(--muted);
}

.list-subject,
.list-snippet {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.78125rem;
}

.list-subject { color: var(--text); }
.list-snippet { color: var(--muted); font-size: 0.75rem; }
.list-row.is-unread .list-subject { font-weight: 700; }
.list-row.is-unread .list-who::before {
  content: "";
  display: inline-block;
  width: 0.375rem;
  height: 0.375rem;
  margin-right: 0.375rem;
  border-radius: var(--r-pill);
  background: var(--accent);
}

@media (min-width: 900px) {
  .app-rail { display: flex; }
  .app-tabs { display: none; }
}

@media (min-width: 1180px) {
  .app-list { display: flex; }
}

/* LinkedIn's left column IS the conversation list — it is the app, not a
   nav rail, so it appears as soon as there is room for a second column. */
@media (min-width: 860px) {
  .app--linkedin .app-rail { display: flex; }
  .app--linkedin .app-tabs { display: none; }
}

/* --------------------------------------------------------------------------
   Shared surface furniture
   -------------------------------------------------------------------------- */

.system-line {
  align-self: center;
  max-width: 100%;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  overflow-wrap: anywhere;
}

.status-line {
  align-self: stretch;
  padding: 0.1875rem 0 0.1875rem 0.625rem;
  border-left: 0.125rem solid var(--line);
  font-family: var(--mono);
  font-size: 0.71875rem;
  line-height: 1.5;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* The narrator's last word, inside the app it is commenting on. The game's
   own voice, so it wears the game's own colours — ink and hi-vis — and it is
   the one piece of authored prose that is allowed to interrupt the fiction. */
.caption-line {
  align-self: stretch;
  margin: 0.125rem 0;
  padding: 0.5625rem 0.75rem 0.625rem;
  border-left: 0.1875rem solid var(--hivis);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--ink);
  color: #f4f6fa;
  font-size: 0.90625rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

/* ==========================================================================
   ANOTHER WINDOW — the half of an answer that happened somewhere else
   --------------------------------------------------------------------------
   A playtester in a DM with Priya chose `DM Greg: "was there a Falcon
   migration this morning, or is that new"` and watched Greg answer it inside
   Priya's chat, between her messages, under a header reading 2 PEOPLE and a
   stakes line reading JUST YOU AND PRIYA. The reaction was right and the
   drawing was wrong: 35% of the pool's answers go somewhere that is not the
   surface they are chosen on, and all of them were being drawn as if they had
   stayed.

   So they are drawn where they went. The desk metaphor is already the whole
   layout — an app window on `--desk`, under an OS bar — and a second window on
   that desk is the one affordance every one of these skins shares, which is
   what makes this consistent across ten formats without ten inventions. It has
   the desk's own ground, a title bar naming the conversation it belongs to,
   and window controls: nothing about it can be read as a message in the
   conversation underneath.

   It is IN FLOW, at the foot of the surface, not floating in a corner. A toast
   pinned over the app pane is clipped at 24.375rem and missed by anyone who has
   scrolled, and being missed is the one thing this element cannot afford.
   ========================================================================== */

/* What the surface you are on says about you having gone: the same mono
   system-line voice it uses for a delivery receipt or a resolution. */
.away-jump {
  align-self: stretch;
  margin: 0.25rem 0 0;
  padding: 0.1875rem 0 0.1875rem 0.625rem;
  border-left: 0.125rem solid var(--line);
  font-family: var(--mono);
  font-size: 0.71875rem;
  line-height: 1.5;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.away-jump::before { content: "⧉ "; color: var(--accent, var(--muted)); }

.away {
  align-self: center;
  width: min(100%, 35rem);
  margin: 0.125rem 0 0.25rem;
  border: 0.0625rem solid #aeb8c7;
  border-radius: var(--r-md);
  background: var(--desk);
  box-shadow: 0 0.875rem 1.875rem rgba(15, 19, 25, 0.22), 0 0.0625rem 0 rgba(15, 19, 25, 0.08);
  overflow: hidden;
}

.away-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.375rem 0.625rem;
  border-bottom: 0.0625rem solid #c3ccd9;
  background: linear-gradient(#f4f6fa, #e3e8f0);
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--muted);
}

.away-mark { color: var(--text); }

.away-eyebrow { text-transform: uppercase; letter-spacing: 0.09em; }

.away-title {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text);
  font-weight: 700;
  overflow-wrap: anywhere;
}

.away-title::before { content: "· "; color: var(--muted); font-weight: 400; }

/* Pushed right whether the bar fits on one line or wraps to two: window
   controls that end up under the title, left-aligned, stop reading as window
   controls at all. */
.away-controls { margin-left: auto; letter-spacing: 0.14em; color: #8a94a3; }

.away-body {
  display: flex;
  flex-direction: column;
  gap: 0.5625rem;
  padding: 0.6875rem 0.75rem 0.75rem;
  background: var(--paper);
}

.away-line { display: flex; align-items: center; gap: 0.4375rem; flex-wrap: wrap; }
.away-who { font-size: 0.84375rem; }
.away-rel { font-family: var(--mono); font-size: 0.65625rem; color: var(--muted); }

.away-text {
  margin-top: 0.125rem;
  font-size: 0.90625rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

/* Yours, in the window you typed it in — marked the way every surface marks
   the thing you sent, with the game's own hi-vis rather than a bubble. */
.away-msg--out { border-left: 0.1875rem solid var(--hivis-dk); padding-left: 0.5625rem; }
.away-msg--out .away-who { color: var(--hivis-dk); }

.away-msg + .away-msg { border-top: 0.0625rem solid var(--line-soft); padding-top: 0.5625rem; }

.away-note {
  font-family: var(--mono);
  font-size: 0.71875rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.away-body > .you-did { margin: 0; }

/* An option that sends nothing still did something. */
.you-did {
  align-self: stretch;
  padding: 0.4375rem 0.6875rem;
  border-left: 0.1875rem solid var(--hivis-dk);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: #fff8e4;
  font-size: 0.84375rem;
  font-style: italic;
  color: #4a4130;
  overflow-wrap: anywhere;
}

.chat-divider {
  align-self: stretch;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.chat-divider::before,
.chat-divider::after {
  content: "";
  flex: 1 1 auto;
  height: 0.0625rem;
  background: var(--line);
}


.reaction {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1875rem 0.5625rem;
  border-radius: var(--r-pill);
  background: var(--paper);
  border: 0.0625rem solid var(--line);
  box-shadow: 0 0.0625rem 0.125rem rgba(21, 26, 35, 0.1);
  font-size: 0.75rem;
  color: var(--muted);
}

.reaction--out { align-self: flex-end; }
.reaction-emoji { font-size: 0.9375rem; line-height: 1; }

.reaction.seen {
  padding: 0.125rem 0.125rem 0;
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  font-family: var(--mono);
  font-size: 0.6875rem;
}

.typing {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.625rem 0.8125rem;
  background: var(--paper);
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-md);
  border-top-left-radius: 0.1875rem;
}

.typing span {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--r-pill);
  background: #a7aebc;
  animation: typing-dot 1.2s infinite ease-in-out;
}

.typing span:nth-child(2) { animation-delay: 0.15s; }
.typing span:nth-child(3) { animation-delay: 0.3s; }

@keyframes typing-dot {
  0%, 60%, 100% { opacity: 0.35; transform: translateY(0); }
  30%           { opacity: 1;    transform: translateY(-0.1875rem); }
}

.msg-in { animation: msg-in 150ms ease-out both; }

@keyframes msg-in {
  from { opacity: 0; transform: translateY(0.375rem); }
  to   { opacity: 1; transform: none; }
}

/* the room goes cold */
.scroll.is-removed > *,
.doc-thread.is-removed > *,
.invite-card.is-removed > *,
.tk-thread.is-removed > *,
.doc-page.is-removed {
  filter: grayscale(1);
  opacity: 0.4;
}

.scroll.is-removed > .status-line:last-child,
.scroll.is-removed > .system-line:last-child,
.doc-thread.is-removed > .status-line:last-child,
.invite-card.is-removed > .status-line:last-child,
.tk-thread.is-removed > .status-line:last-child {
  filter: none;
  opacity: 1;
  color: var(--danger);
  border-left-color: var(--danger);
}

/* ==========================================================================
   THE TEN APP SKINS — and a note on their colours
   --------------------------------------------------------------------------
   Each skin has to be recognisable in half a second: that is the whole reason
   the formats look different at all, and it was rebuilt three times to get
   there. What it must NOT do is present a fake product as somebody's real one.
   So the layouts stay, the wordmarks are the fictional company's own (see
   js/render/*: "Synergy Connect", "Synergy Mail", "Synergy Docs", alongside
   the "Synergy Dynamics" workspace and "Synergy Service Desk" that were
   already there), and the palettes below sit a few units off the real
   products' registered hexes — far enough that none of these is that colour,
   close enough that no player could tell you which way it moved. Contrast was
   re-measured after the shift with the computed-contrast harness, not by eye.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The chat app — teams_dm, group_chat, dead_chat
   -------------------------------------------------------------------------- */

.app--teams {
  --brand: #5c5ec2;
  --accent: #5c5ec2;
  --rail-bg: #2f2f4a;
  --rail-fg: #e6e6f2;
  --rail-active: rgba(255, 255, 255, 0.16);
}

.app--teams .app-rail {
  flex: 0 0 5.75rem;
  align-items: stretch;
  border-right: 0;
  padding: 0.5rem 0.375rem;
}

.app--teams .rail-item {
  flex-direction: column;
  gap: 0.1875rem;
  padding: 0.5rem 0.125rem;
  font-size: 0.65625rem;
  text-align: center;
}

.app--teams .rail-icon { font-size: 1.0625rem; opacity: 0.9; }
.app--teams .rail-name { flex: 0 0 auto; }
.app--teams .rail-badge {
  position: absolute;
  margin: -0.875rem 0 0 1.375rem;
  padding: 0 0.25rem;
  border-radius: var(--r-pill);
  background: #c4314b;
  color: #fff;
  font-size: 0.5625rem;
}

.app--teams .app-list { background: #f5f5fb; }
.app--teams .list-row.is-active { background: #e5e5f6; }

.head-teams { border-bottom: 0.0625rem solid var(--line); }

.chat-stack { flex: 0 0 auto; display: flex; padding-left: 0.3125rem; }
.chat-stack .avatar { margin-left: -0.3125rem; box-shadow: 0 0 0 0.125rem var(--paper); }

.chat-log { background: #fafafd; }

.bubble-row {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  width: 100%;
}

.bubble-row.in  { justify-content: flex-start; }
.bubble-row.out { justify-content: flex-end; }

.bubble {
  position: relative;
  max-width: min(74ch, 82%);
  padding: 0.5625rem 0.8125rem;
  border-radius: var(--r-md);
  font-size: 0.90625rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.bubble-in {
  background: var(--paper);
  border: 0.0625rem solid var(--line);
  border-top-left-radius: 0.1875rem;
  color: var(--text);
}

.bubble-out {
  background: #e8e9fa;
  border: 0.0625rem solid #cdcef1;
  border-top-right-radius: 0.1875rem;
  color: #23244a;
}

.bubble-from {
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  margin-bottom: 0.1875rem;
  flex-wrap: wrap;
}

.bubble-from b { font-size: 0.78125rem; color: var(--from-color, var(--text)); }

.bubble-rel {
  font-size: 0.65625rem;
  font-family: var(--mono);
  letter-spacing: 0.02em;
  color: var(--muted);
}

.avatar-gap { flex: 0 0 auto; width: 1.5rem; }
.bubble-row.cont .bubble-in { border-top-left-radius: var(--r-md); }

/* the room is dormant, and it should look dormant before a word is read */
.app.is-dormant .head-teams .head-title { color: var(--muted); }
.app.is-dormant .chat-stack { filter: grayscale(1); opacity: 0.65; }
.app.is-dormant .chat-log { background: #eeeef2; }
.app.is-dormant .list-row.is-active { filter: grayscale(0.55); }
.app.is-dormant .app-bar { background: #6f7192; }
.app.is-dormant .app-tab.is-active { background: #6f7192; }

.chat-divider--plain {
  justify-content: center;
  gap: 0;
  margin: 0.125rem 0 0.375rem;
}

.chat-divider--plain::before,
.chat-divider--plain::after { content: none; }

.app.is-dormant .chat-divider--plain {
  align-self: center;
  padding: 0.1875rem 0.75rem;
  border: 0.0625rem dashed #b4b7c4;
  border-radius: var(--r-pill);
  background: var(--paper);
  font-weight: 700;
  color: #5c6675;
}

/* --------------------------------------------------------------------------
   The channels — all_company, ceo_channel (Slack-ish)
   -------------------------------------------------------------------------- */

.app--slack {
  --brand: #411244;
  --accent: #1367a6;
  --rail-bg: #411244;
  --rail-fg: #e4d5e4;
  --rail-active: #1367a6;
  /* This skin's --rail-active is solid, not a wash — see .intro-mark. */
  --mark-tint: rgba(19, 103, 166, 0.14);
}

.app--slack .app-rail { border-right: 0; }
.app--slack .rail-title { border-bottom: 0.0625rem solid rgba(255, 255, 255, 0.16); color: #fff; }
.app--slack .rail-item.is-active { color: #fff; }
.app--slack .rail-item { opacity: 0.94; }

.head-slack .hash { color: var(--muted); font-weight: 400; }

.chan-log { gap: 0.75rem; padding-top: 0.875rem; }

.chan-msg {
  display: flex;
  gap: 0.5625rem;
  align-items: flex-start;
}

.chan-msg .avatar { border-radius: 0.25rem; }

.chan-body { flex: 1 1 auto; min-width: 0; }

.chan-line {
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  flex-wrap: wrap;
}

.chan-who { font-size: 0.84375rem; color: var(--from-color, var(--text)); }

.chan-badge {
  padding: 0 0.3125rem;
  border-radius: 0.1875rem;
  background: #611f69;
  color: #fff;
  font-family: var(--mono);
  font-size: 0.5625rem;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.chan-rel,
.chan-time {
  font-family: var(--mono);
  font-size: 0.65625rem;
  color: var(--muted);
}

.chan-time::before { content: "· "; }

.chan-text {
  font-size: 0.90625rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.chan-msg--out .chan-text {
  padding-left: 0.5625rem;
  border-left: 0.1875rem solid var(--hivis);
}

/* --------------------------------------------------------------------------
   Outlook — email_thread
   -------------------------------------------------------------------------- */

.app--outlook,
.app--calendar {
  --brand: #116ab9;
  --accent: #116ab9;
  --rail-bg: #f3f6fb;
  --rail-fg: #23303f;
  --rail-active: rgba(17, 106, 185, 0.13);
}

.mail-actions { display: flex; gap: 0.75rem; }

/* Reply / Reply all / Forward. Bordered, these were the most button-like
   things on an email screen while the six real choices sat below the fold —
   and they cannot be wired, because "Reply all" is only half an answer: which
   of the six messages would it send? So they read as Outlook's borderless
   command row, which is what Outlook actually draws. */
.mail-action {
  padding: 0.25rem 0;
  font-size: 0.71875rem;
  color: var(--muted);
  white-space: nowrap;
}

.mail-thread { gap: 0.75rem; background: #f7f9fc; }

/* The collapsed 47: a divider, not a disclosure you can open. */
.mail-older {
  align-self: stretch;
  padding: 0.25rem 0 0.5rem;
  border-bottom: 0.0625rem dashed var(--line);
  font-size: 0.78125rem;
  color: var(--muted);
}

.email {
  background: var(--paper);
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-sm);
  box-shadow: 0 0.0625rem 0.125rem rgba(21, 26, 35, 0.06);
  padding: 0.6875rem 0.8125rem;
}

.email--out { border-left: 0.1875rem solid var(--hivis-dk); background: #fffdf6; }

.email-head {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding-bottom: 0.5rem;
  border-bottom: 0.0625rem solid var(--line-soft);
}

.email-who { flex: 1 1 auto; min-width: 0; }

.email-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.1875rem 0.5rem;
}

.email-from { font-size: 0.875rem; }

.email-rel {
  padding: 0.0625rem 0.375rem;
  border-radius: var(--r-pill);
  background: var(--line-soft);
  font-size: 0.65625rem;
  color: #3f4a5c;
}

.email-addr,
.email-to,
.email-time {
  display: block;
  font-family: var(--mono);
  font-size: 0.65625rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.email-time { flex: 0 0 auto; padding-top: 0.125rem; text-align: right; }
.email-body { padding-top: 0.5625rem; font-size: 0.90625rem; line-height: 1.5; overflow-wrap: anywhere; }

/* --------------------------------------------------------------------------
   Outlook Calendar — calendar_invite
   -------------------------------------------------------------------------- */

/* The week beside the invitation on a wide screen, above it on a narrow one.
   The grid never scrolls: it is the answer to "where does this land". */
.cal-wrap {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  background: #f7f9fc;
}

/* The grid takes the height it is given rather than hanging from the top of an
   empty pane: a week view that fills the window is what Outlook does, and the
   invitation beside it then sits against the question instead of above a void. */
.cal-wrap > .cal { flex: 1 1 auto; min-height: 8.25rem; margin: 0.75rem 0.875rem 0; }
.cal-scroll { background: #f7f9fc; gap: 0.875rem; }

@media (min-width: 1100px) {
  .cal-wrap { flex-direction: row; align-items: stretch; }
  .cal-wrap > .cal { flex: 1 1 58%; min-width: 0; align-self: stretch; margin: 0.875rem 0 0.875rem 0.875rem; }
  .cal-wrap > .cal-scroll { flex: 1 1 42%; }
}

.cal {
  display: flex;
  flex-direction: column;
  min-height: 0;
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  overflow: hidden;
}

.cal-head {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: 3rem repeat(5, minmax(0, 1fr));
  border-bottom: 0.0625rem solid var(--line);
  background: #eef3f9;
}

.cal-day {
  padding: 0.3125rem 0.25rem;
  text-align: center;
  border-left: 0.0625rem solid var(--line);
  font-size: 0.6875rem;
  color: var(--muted);
}

.cal-day small { display: block; font-size: 0.59375rem; letter-spacing: 0.08em; text-transform: uppercase; }
.cal-day b { font-size: 0.8125rem; color: var(--text); }
.cal-day.is-today b { color: var(--brand); }

/* Twelve half-hour rows, 07:00–13:00. They hold 1.3125rem each and share whatever
   height is left over, so the same grid reads correctly in a 12.5rem strip and
   in a 31.25rem pane. The hour rules are sized as a fraction of the body rather
   than in pixels for the same reason: six hour bands, whatever the height. */
.cal-body {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: 3rem repeat(5, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  background-image: linear-gradient(to bottom, var(--line-soft) 0 0.0625rem, transparent 0.0625rem);
  background-size: 100% calc(100% / var(--cal-hours, 6));
}

.cal-time {
  grid-column: 1;
  padding-right: 0.375rem;
  font-family: var(--mono);
  font-size: 0.59375rem;
  color: var(--muted);
  text-align: right;
  line-height: 1;
}

.cal-block {
  margin: 0.0625rem 0.125rem;
  padding: 0.125rem 0.3125rem;
  border-radius: 0.1875rem;
  border-left: 0.1875rem solid #7c93ab;
  background: #e7edf4;
  font-size: 0.65625rem;
  line-height: 1.15;
  overflow: hidden;
  color: #33404f;
}

.cal-block-time { display: block; font-family: var(--mono); font-size: 0.53125rem; opacity: 0.8; }
.cal-block-label { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cal-block.is-ghost { opacity: 0.45; text-decoration: line-through; }

/* the thing that has just landed in your week */
.cal-block.is-invite {
  border-left-color: var(--hivis-dk);
  background: var(--hivis);
  color: #241b00;
  font-weight: 700;
  box-shadow: 0 0 0 0.125rem rgba(255, 190, 11, 0.35);
  animation: invite-pulse 2.4s ease-in-out 3;
}

/* The invite is the one block allowed to wrap its title — but only twice. A
   45-character meeting name in a half-hour slot ran past the block and was cut
   mid-letter by the grid, which read as a rendering fault rather than as a long
   meeting name. */
.cal-block.is-invite .cal-block-label {
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@keyframes invite-pulse {
  0%, 100% { box-shadow: 0 0 0 0.125rem rgba(255, 190, 11, 0.35); }
  50%      { box-shadow: 0 0 0 0.375rem rgba(255, 190, 11, 0.22); }
}

.invite-card {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding: 0.875rem;
  border: 0.0625rem solid var(--line);
  border-top: 0.1875rem solid var(--brand);
  border-radius: var(--r-sm);
  background: var(--paper);
}

.invite-top { display: flex; align-items: center; gap: 0.625rem; }
.invite-org { min-width: 0; }
.invite-org b { display: block; font-size: 1rem; }

.invite-orgline {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  flex-wrap: wrap;
  font-size: 0.78125rem;
  color: var(--muted);
}

.who-rel {
  padding: 0.0625rem 0.375rem;
  border-radius: var(--r-pill);
  background: var(--line-soft);
  font-size: 0.65625rem;
  color: #3f4a5c;
}

.invite-when {
  font-size: 1.0625rem;
  font-weight: 700;
  padding-bottom: 0.25rem;
  border-bottom: 0.0625rem solid var(--line-soft);
}

.invite-field {
  display: flex;
  gap: 0.625rem;
  padding-top: 0.4375rem;
  border-top: 0.0625rem solid var(--line-soft);
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}

.invite-label {
  flex: 0 0 5.25rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 0.1875rem;
}

.invite-value { flex: 1 1 auto; min-width: 0; }

.rsvp {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  padding-top: 0.625rem;
  border-top: 0.0625rem solid var(--line);
}

/* These ARE controls now — each one presses the dock option it is the artefact
   of — so they are allowed to look like controls, and they are the only thing
   in an app window that is. */
.rsvp-chip {
  padding: 0.375rem 0.875rem;
  border: 0.0625rem solid #96a3b5;
  border-radius: var(--r-sm);
  background: var(--paper);
  font-size: 0.78125rem;
  font-weight: 600;
  color: #33404f;
  cursor: pointer;
  touch-action: manipulation;
  transition: background-color 110ms ease, border-color 110ms ease, transform 80ms ease;
}

button.rsvp-chip:hover:not(:disabled) { border-color: var(--brand); background: #eaf2fb; color: var(--brand); }
button.rsvp-chip:active:not(:disabled) { transform: translateY(0.0625rem); }

/* Not yet your turn (the invite is still arriving), or already spent. */
.rsvp-chip:disabled { cursor: default; opacity: 0.5; }

/* No answer maps onto it: state, not an offer. */
.rsvp-chip--state {
  padding: 0.375rem 0;
  border: 0;
  background: none;
  font-weight: 400;
  color: var(--muted);
  cursor: default;
}

.rsvp-chip.is-picked,
.rsvp-chip.is-picked:disabled {
  border-color: var(--brand);
  background: var(--brand);
  color: #fff;
  font-weight: 700;
  opacity: 1;
}

.rsvp-note {
  flex: 1 1 100%;
  font-family: var(--mono);
  font-size: 0.65625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   Docs — doc_comments
   -------------------------------------------------------------------------- */

.app--docs {
  --brand: #ffffff;
  --brand-fg: #1f2b3a;
  --accent: #1971e2;
  --rail-bg: #f7f9fc;
}

.app--docs .app-bar { border-bottom: 0.0625rem solid var(--line); }
.app--docs .app-mark { background: var(--accent); color: #fff; }
.app--docs .app-where { background: var(--line-soft); color: var(--text); }
.app--docs .app-search { background: var(--line-soft); color: var(--muted); }
.app--docs .app-meta { color: var(--muted); }

.doc-toolbar {
  flex: 0 0 auto;
  display: flex;
  gap: 0.25rem;
  padding: 0.3125rem 0.75rem;
  border-bottom: 0.0625rem solid var(--line);
  background: #f7f9fc;
  overflow-x: auto;
  scrollbar-width: none;
}

.doc-toolbar::-webkit-scrollbar { display: none; }

/* Docs' toolbar is borderless in the real product too. */
.doc-tool {
  flex: 0 0 auto;
  padding: 0.1875rem 0.4375rem;
  font-size: 0.71875rem;
  color: var(--muted);
  white-space: nowrap;
}

.doc-scroll { background: #e9edf3; padding: 1rem 0.875rem 1.25rem; }

/* A page of a document fills the window it is open in — it does not float in
   the top third of one. The page stretches, the margin stretches beside it,
   and the last comment therefore sits against the question. */
/* 1 0 auto, not 1 1 auto: the layout grows to fill the pane but is never
   squeezed below its own content, which is what lets the page stay stuck to
   the top of the scroller on a phone. */
.doc-scroll > .doc-layout { flex: 1 0 auto; }

.doc-layout {
  display: flex;
  gap: 1rem;
  align-items: stretch;
}

.doc-page {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1.375rem 1.5rem 1.625rem;
  background: var(--paper);
  border: 0.0625rem solid var(--line);
  box-shadow: 0 0.0625rem 0.25rem rgba(21, 26, 35, 0.12);
}

.doc-h { font-size: 0.9375rem; font-weight: 700; }

.doc-lines { display: flex; flex-direction: column; gap: 0.5rem; }
.doc-line { height: 0.4375rem; border-radius: 0.125rem; background: #e9ecf2; }

/* the passage everyone is arguing about, anchored to the margin */
.doc-para {
  position: relative;
  padding: 0.25rem 0.3125rem;
  background: rgba(255, 190, 11, 0.34);
  box-shadow: inset 0 -0.125rem 0 rgba(201, 143, 0, 0.6);
  font-size: 0.90625rem;
  line-height: 1.6;
  overflow-wrap: anywhere;
}

.doc-margin { flex: 0 0 18.75rem; display: none; }

/* The margin is as tall as the page; the thread hangs from the bottom of it,
   so the newest comment is the one nearest the question. */
.doc-thread { display: flex; flex-direction: column; gap: 0.5625rem; margin-top: auto; }

@media (min-width: 900px) {
  .doc-margin { display: flex; flex-direction: column; }
  /* the connector: the highlight, then the thread beside it */
  .doc-para::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 100%;
    width: 1rem;
    height: 0.125rem;
    background: var(--hivis-dk);
  }
}

@media (max-width: 899px) {
  .doc-layout { flex-direction: column; }
  .doc-margin { display: flex; flex-direction: column; flex: 1 1 auto; width: 100%; }
}

.comment {
  background: var(--paper);
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-sm);
  padding: 0.5625rem 0.6875rem;
  box-shadow: 0 0.0625rem 0.125rem rgba(21, 26, 35, 0.08);
}

.comment--out { border-left: 0.1875rem solid var(--hivis-dk); background: #fffdf6; }
.comment--reply { margin-left: 1.125rem; }

.comment-head {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  padding-bottom: 0.3125rem;
}

.comment-who { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; gap: 0.375rem; flex-wrap: wrap; }
.comment-who b { font-size: 0.78125rem; color: var(--from-color, var(--text)); }

.comment-rel,
.comment-time {
  font-family: var(--mono);
  font-size: 0.625rem;
  color: var(--muted);
}

.comment-body { font-size: 0.84375rem; line-height: 1.5; overflow-wrap: anywhere; }

/* "Reply · Resolve" in link blue was an offer with nothing behind it: no
   option in the pool resolves a comment, so there is no clean answer to wire
   it to. Grey, it reads as what the card says about itself. */
.comment-actions {
  padding-top: 0.375rem;
  font-size: 0.6875rem;
  color: var(--muted);
}

.comment.is-resolved { opacity: 0.5; }
.comment.is-resolved .comment-body { text-decoration: line-through; }

/* --------------------------------------------------------------------------
   Service desk — it_ticket
   -------------------------------------------------------------------------- */

.app--desk {
  --brand: #2c3a4a;
  --accent: #486a8f;
  --rail-bg: #eef1f5;
  --rail-fg: #2c3a4a;
  --rail-active: rgba(72, 106, 143, 0.16);
}

.tk-no {
  font-family: var(--mono);
  font-size: 0.8125rem;
  padding: 0.0625rem 0.4375rem;
  border-radius: var(--r-sm);
  background: var(--brand);
  color: #fff;
}

.status-chip {
  flex: 0 0 auto;
  padding: 0.25rem 0.5625rem;
  border: 0.0625rem solid #e3c3c3;
  border-radius: var(--r-sm);
  background: #fbeded;
  font-family: var(--mono);
  line-height: 1.2;
  text-align: right;
}

.status-chip b { display: block; font-size: 0.75rem; color: #97282f; }

.status-chip small {
  display: block;
  font-size: 0.53125rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8c6266;
}

/* THE PINNED CARD AND THE SCROLLPORT MUST NOT SHARE AN EDGE.
   ----------------------------------------------------------
   Until this rule, `.tk-fields` had `margin: 0.75rem 0.875rem 0` — no bottom margin —
   so the field card's own bottom border sat at exactly the y where `.tk-scroll`
   starts clipping, and `.tk-scroll { padding-top: 0.625rem }` plus
   `.tk-scroll > .convo-intro { margin-top: -0.625rem }` cancelled each other so the
   log's first line began flush on that same line. Whatever text row happened to
   straddle it was sliced horizontally through the middle of its glyphs and left
   sitting on the card's border. A playtester read that as text bleeding out
   from under the field table; it is not a stacking fault (a pixel diff with the
   scroller hidden showed 0 differing pixels inside the card at five viewports)
   and nothing paints over anything. The two boxes simply touched.

   So: a real 0.625rem gap under the card, no top padding for the bleed to cancel,
   and an inset shadow on the scrollport so a clipped row reads as content
   scrolled under a pinned header — which is what it is, and what every real
   product does with one. */
.tk-scroll { background: #f4f6f9; padding-top: 0; }

.app--desk .app-pane > .tk-fields { flex: 0 0 auto; margin: 0.75rem 0.875rem 0.625rem; }
.app--desk .app-pane > .tk-scroll { box-shadow: inset 0 0.4375rem 0.375rem -0.4375rem rgba(21, 26, 35, 0.22); }

.tk-fields {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0;
  margin: 0;
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  overflow: hidden;
}

/* Six fields stacked one per row is 18.75rem of ticket metadata sitting on top of
   the activity log — the log had 1.8125rem left at 1280×800, which is a ticket with
   no ticket in it. Every service desk lays its fields out in columns for
   exactly this reason. Two pairs per row on a wide pane, three on a very wide
   one: same six fields, a third of the height. */
@media (min-width: 900px) {
  .tk-fields { grid-template-columns: repeat(2, max-content minmax(0, 1fr)); }
  .tk-fields dt:last-of-type,
  .tk-fields dd:last-of-type { border-bottom: 0.0625rem solid var(--line-soft); }
  .tk-fields > :nth-last-child(-n + 4) { border-bottom: 0; }
}

@media (min-width: 1400px) {
  .tk-fields { grid-template-columns: repeat(3, max-content minmax(0, 1fr)); }
  .tk-fields > :nth-last-child(-n + 6) { border-bottom: 0; }
}

.tk-fields dt {
  padding: 0.375rem 0.625rem;
  border-bottom: 0.0625rem solid var(--line-soft);
  background: #f7f9fc;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.tk-fields dd {
  margin: 0;
  padding: 0.375rem 0.625rem;
  border-bottom: 0.0625rem solid var(--line-soft);
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}

.tk-fields dt:last-of-type,
.tk-fields dd:last-of-type { border-bottom: 0; }

.tk-fields dd.is-bad { color: #97282f; font-weight: 700; }

.tk-activity-label {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 0.125rem;
}

.tk-thread { display: flex; flex-direction: column; }

.tk-update {
  position: relative;
  padding: 0.0625rem 0 0.875rem 1.125rem;
  border-left: 0.125rem solid #cfd6e0;
}

.tk-update::before {
  content: "";
  position: absolute;
  left: -0.375rem;
  top: 0.3125rem;
  width: 0.5625rem;
  height: 0.5625rem;
  border-radius: var(--r-pill);
  background: #b9c0cc;
  box-shadow: 0 0 0 0.1875rem #f4f6f9;
}

.tk-update--out { border-left-color: var(--hivis-dk); }
.tk-update--out::before { background: var(--hivis-dk); }

.tk-line { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.tk-who { font-size: 0.8125rem; color: var(--from-color, var(--text)); }

.tk-rel,
.tk-time { font-family: var(--mono); font-size: 0.65625rem; color: var(--muted); }
.tk-time { margin-left: auto; }

.tk-body { font-size: 0.84375rem; line-height: 1.5; overflow-wrap: anywhere; }
.tk-thread > .status-line,
.tk-thread > .you-did { margin: 0.125rem 0 0.625rem; }

/* --------------------------------------------------------------------------
   LinkedIn — linkedin_dm
   -------------------------------------------------------------------------- */

.app--linkedin {
  --brand: #ffffff;
  --brand-fg: #1f2b3a;
  --accent: #0c68c6;
  --rail-bg: #ffffff;
}

.app--linkedin .app-bar { border-bottom: 0.0625rem solid var(--line); }
.app--linkedin .app-mark { background: var(--accent); color: #fff; font-size: 0.75rem; letter-spacing: -0.02em; }
.app--linkedin .app-where { background: var(--line-soft); color: var(--text); }
.app--linkedin .app-search { background: #e8f0fb; color: #3c4a5c; max-width: 15rem; }
.app--linkedin .app-pane { background: #f4f2ee; }
.app--linkedin .pane-head { border-bottom: 0.0625rem solid var(--line); }

.li-nav { margin-left: auto; display: flex; gap: 0.125rem; }

.li-navitem {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.0625rem;
  padding: 0.125rem 0.5625rem;
  font-size: 0.625rem;
  color: var(--muted);
  border-bottom: 0.125rem solid transparent;
}

.li-navitem.is-active { color: var(--text); border-bottom-color: var(--text); }
.li-navicon { font-size: 0.875rem; line-height: 1; }

@media (max-width: 1023px) { .li-navlabel { display: none; } }
@media (max-width: 719px) { .li-nav { display: none; } }

/* The nav the playtester named outright: at 90rem wide the labels were
   already visible (the tier above turns them on at 63.9375rem) and still went
   unnoticed — a 0.625rem label under a 0.875rem glyph reads as a hairline, not a
   navigation bar. This is the one piece of chrome that gets more than the
   OS bar's ~25%: it is furniture pretending to be Home/My Network/Jobs/
   Messaging/Notifications/Me, so it has to read as a nav bar on its own,
   not just scale in step with everything around it. */
@media (min-width: 1100px) {
  .li-nav { gap: 0.375rem; }
  .li-navitem { gap: 0.25rem; padding: 0.375rem 0.875rem; font-size: 0.8125rem; border-bottom-width: 0.1875rem; }
  .li-navicon { font-size: 1.25rem; }
}

.li-convos { flex: 0 0 16.75rem; padding: 0; background: var(--paper); }
.li-convos .rail-title { padding: 0.625rem 0.75rem; border-bottom: 0.0625rem solid var(--line); }

.li-convo {
  padding: 0.5625rem 0.75rem;
  border-bottom: 0.0625rem solid var(--line-soft);
  border-left: 0.1875rem solid transparent;
}

.li-convo.is-active { border-left-color: var(--accent); background: #eaf3fc; }
.li-convo-top { display: flex; align-items: baseline; gap: 0.5rem; }
.li-convo-top b { flex: 1 1 auto; min-width: 0; font-size: 0.8125rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.li-convo-when { font-family: var(--mono); font-size: 0.625rem; color: var(--muted); }

.li-convo-snippet {
  font-size: 0.75rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.li-face { position: relative; flex: 0 0 auto; padding: 0.1875rem; border: 0.125rem solid #01754f; border-radius: var(--r-pill); }

/* A badge on a profile, not a green button in the corner of the header. */
.li-otw {
  flex: 0 0 auto;
  padding: 0.1875rem 0.5rem;
  border-radius: var(--r-sm);
  background: #e4f1eb;
  color: #01543a;
  font-family: var(--mono);
  font-size: 0.59375rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.li-degree {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 400;
  color: var(--muted);
}

.li-degree::before { content: "· "; }

.li-thread { background: var(--paper); gap: 0; }

/* LinkedIn messaging is not bubbles: both sides are flat rows, left aligned,
   name in bold above the text. That difference is the whole skin. */
.li-msg { padding: 0.625rem 0; border-bottom: 0.0625rem solid var(--line-soft); }

.li-line { display: flex; align-items: center; gap: 0.4375rem; flex-wrap: wrap; }
.li-who { font-size: 0.84375rem; }

.li-rel,
.li-time { font-family: var(--mono); font-size: 0.65625rem; color: var(--muted); }
.li-time { margin-left: auto; }

.li-body { padding: 0.3125rem 0 0 1.9375rem; font-size: 0.875rem; line-height: 1.5; overflow-wrap: anywhere; }
.li-msg--out { background: #f8fbff; }
.li-msg--out .li-who { color: var(--accent); }
.li-typing { margin: 0.5rem 0 0.5rem 1.9375rem; }

/* ==========================================================================
   THE REPLY ZONE — the app's own composer, with your six replies in it

   There is no banner. There is no dock. Three rounds of playtest ended with
   "I HATE THE BANNER AT THE BOTTOM. IT ALSO COVERS THE TEXT ABOVE WHEN THERE
   IS AN ANIMATION OF SOMEONE TYPING", and the concept went with the verdict.

   Every product being simulated already has a reply affordance, and every one
   of them already offers canned replies over it — Teams' suggested replies,
   Gmail's smart replies, a service desk's canned responses, Outlook's inline
   reply pane. So the six options ARE that feature. The zone is the last child
   of `.app-pane`, it inherits the product's own `--accent` because it is
   inside `.app--<product>`, and it is drawn in that product's vocabulary.

   TWO STRUCTURAL PROMISES, both measurable:

   1. IN FLOW, NEVER OVER. `.app-pane` is a flex column: `.scroll` is
      `flex: 1 1 auto; min-height: 0`, this is `flex: 0 1 auto`. Every pixel
      this block takes is a pixel the conversation's scrollport never had —
      it cannot be a pixel the conversation is hidden under. Nothing here is
      fixed or absolute, and nothing paints outside its own box.
   2. THE CONVERSATION KEEPS AT LEAST A THIRD OF THE PANE. `max-height` caps
      the zone as a fraction of the pane, so however long the six options run,
      the scroller above them cannot be squeezed away. In the one viewport
      where six options plus a prompt genuinely do not fit (a phone on its
      side), the suggestion list scrolls inside the zone — the conversation
      does not pay for it.
   ========================================================================== */

.reply {
  /* 0 0 auto, not 0 1 auto: the zone takes exactly the height its content
     needs and the scroller above absorbs the rest. Allowed to shrink, it
     shares the squeeze with the conversation and then paints its own children
     over each other, because a flex item shrinking does not scroll. */
  flex: 0 0 auto;
  min-height: 0;
  /* The conversation's floor. Past this the OPTION GRID scrolls — never the
     conversation, and never by covering it. The question, the stakes and the
     summary stay put while it does, because a scrolled-away question is the
     thing three rounds of playtest were about. */
  max-height: 62%;
  display: flex;
  flex-direction: column;
  gap: 0.4375rem;
  padding: 0.625rem 0.875rem 0.75rem;
  overflow: hidden;
  background: var(--reply-bg);
  border-top: 0.0625rem solid var(--line);
  color: var(--text);
}

/* --- the question ---------------------------------------------------------

   THE QUESTION IS AN EXAM QUESTION, ON ITS OWN PAPER.

   Round 3: "the bottom banner, how do u reply is too small - tbh idk why there
   is even a banner." Both faults at once — undersized AND parked in a strip
   with no reason to exist. That was fixed by making it the largest type on the
   surface, set in the application's own accent.

   Round 4 found what that cost. In the app's accent it shared a colour with the
   option keys, the action prefixes and the routing lines, so it read as more of
   the same furniture: the player saw a screenful of blue and no single thing
   saying "this is what you are being asked".

   So the question stops pretending to be the application. It sits on its own
   card — paper ground against the zone's tint, a rule down the edge in the
   game's own amber, which no office skin uses — with an eyebrow naming what it
   is, the question in ink, and who will see the answer. Scrolling down from
   the conversation you meet a thing that is plainly not the calendar, and
   plainly the question.

   The situation used to run as a summary line under the question here too.
   The briefing now delivers it formally before the scenario starts, so the
   card stopped repeating it — one fewer line, and real space back for the
   option grid on an upright phone (measured 2.375rem on a representative
   scenario at 390×844), where it was already scrolling and still is. */
.reply-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  padding: 0.5625rem 0.75rem 0.625rem;
  background: var(--paper);
  border: 0.0625rem solid var(--line);
  border-left: 0.25rem solid var(--hivis);
  border-radius: var(--r-md);
  box-shadow: 0 0.0625rem 0.1875rem rgba(15, 19, 25, 0.06);
}

/* Names the thing, the way an exam paper does. Its own full-width line, so the
   question below it always starts flush left at the top of the card. */
.ask-eyebrow {
  flex: 1 1 100%;
  font-family: var(--mono);
  font-size: 0.59375rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

.ask-q {
  font-size: clamp(1.15rem, 2vw, 1.6rem);
  font-weight: 800;
  line-height: 1.14;
  letter-spacing: -0.015em;
  /* Ink, not the app's accent: the question must not be the same colour as the
     option keys and routing lines it is asking you to choose between. */
  color: var(--text);
  text-wrap: balance;
}

/* The stakes — who can see this. The playtest missed this line every time it
   was grey on grey, so it stays a filled chip; it is no longer in the app's
   accent, because on this card that would put it back in the same family as
   the options. Amber ink on an amber wash keeps it findable and keeps it the
   game's voice rather than the calendar's. */
.ask-stakes {
  padding: 0.125rem 0.5rem;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--hivis) 20%, #fff);
  border: 0.0625rem solid color-mix(in srgb, var(--hivis) 52%, #fff);
  color: #4a3800;
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* --- the suggestions ------------------------------------------------------ */

/* 0 1 auto with a real scroller underneath it: the slot may shrink, and when
   it does the GRID scrolls rather than the slot silently painting its children
   over the message box below. (It used to be 0 0 auto for exactly that reason,
   before the grid had somewhere to put the overflow.) */
.reply-slot {
  flex: 0 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.sugg {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.sugg-label { flex: 0 0 auto; }

/* What this application calls a canned reply. The keyboard hint rides on it,
   because letters nobody is told about are letters nobody presses. */
.sugg-label {
  display: flex;
  align-items: baseline;
  gap: 0.5625rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The rubric. It is the one line that says how many of the six to pick, so it
   is ink rather than the muted grey the hints around it use — the keyboard
   hint and the scroll warning stay quiet, because they are hints. */
.sugg-what {
  color: var(--text);
  font-weight: 700;
}

.sugg-keys {
  padding: 0.0625rem 0.375rem;
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  letter-spacing: 0.08em;
}

/* Only when the grid really does not fit — see markScroll() in answers.js.
   Silent truncation of two of six options is the one failure mode a suggestion
   list must never have. */
.sugg-more { display: none; color: var(--accent-ink, var(--accent)); }
.sugg.is-scrollable .sugg-more { display: inline; }

.sugg.is-scrollable .answers {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.25rem), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 1.25rem), transparent);
}

/* The one thing in the zone that is allowed to scroll, and only when six
   120-character answers genuinely will not fit the third of the pane this is
   allowed to take. 0.0625rem of padding so a focus ring on an edge card is not
   clipped by its own scrollport. */
.answers {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.0625rem;
  margin: -0.0625rem;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.4375rem;
  align-content: start;
}

.answer-btn {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.5625rem;
  align-items: start;
  width: 100%;
  padding: 0.5rem 0.625rem;
  text-align: left;
  background: var(--paper);
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-md);
  color: var(--text);
  cursor: pointer;
  touch-action: manipulation;
  transition: border-color 110ms ease, box-shadow 110ms ease, transform 80ms ease;
}

.ans-key {
  flex: 0 0 auto;
  width: 1.25rem;
  height: 1.25rem;
  display: grid;
  place-items: center;
  border: 0.0625rem solid color-mix(in srgb, var(--accent) 45%, #fff);
  border-radius: 0.25rem;
  background: color-mix(in srgb, var(--accent) 10%, #fff);
  color: var(--accent-ink, var(--accent));
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 700;
}

.ans-body { min-width: 0; }

/* What the option DOES — read before the words it sends. */
.ans-act {
  display: block;
  color: var(--accent-ink, var(--accent));
  font-size: 0.71875rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.ans-text {
  display: block;
  margin-top: 0.125rem;
  font-size: 0.84375rem;
  line-height: 1.38;
  color: var(--text);
  overflow-wrap: anywhere;
}

/* Routing: metadata, subordinate, still legible. */
.ans-route {
  display: block;
  margin-top: 0.25rem;
  font-family: var(--mono);
  font-size: 0.65625rem;
  line-height: 1.35;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.ans-route-mark { margin-right: 0.3125rem; color: var(--accent); }

/* An option that sends nothing is one line, and says so. */
.answer-btn--deed .ans-act { font-size: 0.84375rem; color: var(--text); }

.answer-btn:hover:not(:disabled) {
  border-color: var(--accent);
  box-shadow: 0 0.125rem 0.5625rem color-mix(in srgb, var(--accent) 22%, transparent);
}

.answer-btn:active:not(:disabled) { transform: translateY(0.0625rem); }
.answer-btn:disabled { cursor: default; opacity: 0.45; }

.answer-btn.is-chosen,
.answer-btn.is-chosen:disabled {
  background: var(--accent);
  border-color: var(--accent);
  opacity: 1;
}

.answer-btn.is-chosen .ans-key {
  background: rgba(255, 255, 255, 0.9);
  border-color: rgba(255, 255, 255, 0.9);
  color: var(--accent-ink, var(--accent));
}

.answer-btn.is-chosen .ans-act { color: rgba(255, 255, 255, 0.86); }
.answer-btn.is-chosen .ans-text { color: #fff; }
.answer-btn.is-chosen .ans-route,
.answer-btn.is-chosen .ans-route-mark { color: rgba(255, 255, 255, 0.78); }

/* --- what you sent, while the surface reacts ------------------------------ */

.reply-echo {
  display: flex;
  align-items: baseline;
  gap: 0.5625rem;
  padding: 0.375rem 0 0.125rem;
}

.echo-label {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-ink, var(--accent));
}

.echo-line {
  flex: 1 1 auto;
  min-width: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--muted);
}

.echo-act { color: var(--text); margin-right: 0.375rem; }

/* --- the app's own message box (furniture, never an offer) ----------------- */

.reply-box {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  padding: 0.4375rem 0.625rem;
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  color: var(--muted);
  font-size: 0.78125rem;
}

.reply-to {
  font-size: 0.71875rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-bottom: 0.3125rem;
  border-bottom: 0.0625rem solid var(--line-soft);
}

.reply-field {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}

.reply-ph {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.reply-send {
  flex: 0 0 auto;
  color: var(--accent-ink, var(--accent));
  font-weight: 700;
  font-size: 0.8125rem;
}

.reply-tools {
  display: flex;
  gap: 0.75rem;
  color: var(--muted);
  font-size: 0.75rem;
}

.reply-tool { line-height: 1; }

/* --- skip ----------------------------------------------------------------- */

.reply-wait {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  min-height: 2.5rem;
}

.skip-row { transition: opacity 120ms ease; }
.skip-row.is-spent { opacity: 0; pointer-events: none; }

.skip-btn {
  padding: 0.3125rem 0.8125rem;
  border: 0.0625rem solid var(--line);
  border-radius: var(--r-pill);
  background: var(--paper);
  font-family: var(--mono);
  font-size: 0.65625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  touch-action: manipulation;
}

.skip-btn:hover { border-color: var(--accent); color: var(--accent-ink, var(--accent)); }

/* --- next ------------------------------------------------------------------
   The reaction has landed and the scenario waits (brief §2g.2). This is the
   only live control on the screen at that moment, so it is drawn as the
   application's own primary action — the same weight as the Send button in the
   box underneath it — and it mounts into the slot the skip control just left,
   which means nothing on the screen moves between the two.

   NOTHING HERE TOUCHES pointer-events. The launch blocker fixed in f658088 was
   an id rule (`#coach`, 1-0-0) setting `pointer-events: none` that a class rule
   could not beat, and the button inside it inherited the value: keyboard worked,
   `element.click()` worked, a finger did not. The lesson is not "write auto
   everywhere" — `#app.is-switching .screen` deliberately deadens the stage for
   350ms after a screen change, and a descendant declaring `auto` would punch a
   hole straight through it. The lesson is that only a hit-tested, dispatched
   press proves this, and that is how it is proved. */
.next-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  transition: opacity 120ms ease;
}

.next-row.is-spent { opacity: 0.45; }

.next-hint {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.next-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4375rem 0.9375rem;
  border: 0.0625rem solid var(--accent);
  border-radius: var(--r-md);
  background: var(--accent);
  color: #fff;
  font-size: 0.84375rem;
  font-weight: 650;
  line-height: 1.2;
  cursor: pointer;
  /* A finger's worth of target on a phone, where this is the control the whole
     run passes through ten times. */
  min-height: 2.125rem;
  touch-action: manipulation;
}

.next-btn:hover:not(:disabled) { filter: brightness(1.07); }
.next-btn:active:not(:disabled) { transform: translateY(0.0625rem); }
.next-btn:disabled { cursor: default; }
.next-mark { font-size: 0.9375rem; line-height: 1; }

/* ==========================================================================
   PER-APP TREATMENT

   Same component, seven vocabularies. What changes is what each product
   actually does: where its formatting row sits, whether its send control is a
   glyph or a button, how round its cards are, and what colour it is.
   ========================================================================== */

/* Teams — violet, rounded, format row UNDER the box, arrow send. */
.app--teams { --reply-bg: #f5f5fb; --accent-ink: #4b4fae; }
.app--teams .reply-box { flex-direction: column-reverse; border-radius: 0.5rem; }
.app--teams .answer-btn,
.app--teams .next-btn { border-radius: 0.5rem; }
.app--teams .reply-tools { padding-top: 0.125rem; }

/* Slack — the aubergine workspace, but the composer is white with a bordered
   toolbar ON TOP and a green send button, which is what Slack looks like. */
.app--slack { --reply-bg: #f4f2f4; --accent-ink: #0b4f7f; }
.app--slack .reply-box { padding: 0; border-radius: 0.375rem; gap: 0; }
.app--slack .reply-tools {
  padding: 0.3125rem 0.5625rem;
  border-bottom: 0.0625rem solid var(--line-soft);
  font-weight: 700;
}
.app--slack .reply-field { padding: 0.5rem 0.5625rem 0.5625rem; }
.app--slack .reply-send {
  padding: 0.1875rem 0.5625rem;
  border-radius: 0.25rem;
  background: #007a5a;
  color: #fff;
  font-size: 0.75rem;
}
.app--slack .answer-btn,
.app--slack .next-btn { border-radius: 0.375rem; }
/* Slack's primary button is green, not aubergine — the same green as its Send. */
.app--slack .next-btn { background: #007a5a; border-color: #007a5a; }

/* Outlook — the inline reply pane: recipients over a squared draft area and a
   solid Send button. Nothing in Outlook is round. */
.app--outlook,
.app--calendar { --reply-bg: #f3f6fb; --accent-ink: #0b579b; }
.app--outlook .reply-box,
.app--calendar .reply-box { border-radius: 0.1875rem; }
.app--outlook .answer-btn,
.app--calendar .answer-btn,
.app--outlook .next-btn,
.app--calendar .next-btn { border-radius: 0.1875rem; }
.app--outlook .reply-send,
.app--calendar .reply-send,
.app--linkedin .reply-send,
.app--docs .reply-send,
.app--desk .reply-send {
  padding: 0.1875rem 0.6875rem;
  border-radius: 0.1875rem;
  background: var(--accent);
  color: #fff;
  font-size: 0.75rem;
}
.app--linkedin .reply-send { border-radius: var(--r-pill); padding: 0.1875rem 0.875rem; }
.app--docs .reply-send { border-radius: var(--r-pill); }

/* LinkedIn — pill send button, pale-blue suggestion pills, its own warm ground. */
.app--linkedin { --reply-bg: #f4f2ee; --accent-ink: #0c68c6; }
.app--linkedin .answer-btn { border-radius: 0.75rem; }
.app--linkedin .next-btn { border-radius: var(--r-pill); padding: 0.4375rem 1.125rem; }
.app--linkedin .reply-box { border-radius: 0.5rem; flex-direction: column-reverse; }

/* Docs — a comment card: white, rounded, a soft shadow, blue Comment button. */
.app--docs { --reply-bg: #eef1f6; --accent-ink: #1257b8; }
.app--docs .answer-btn { border-radius: 0.5rem; box-shadow: 0 0.0625rem 0.1875rem rgba(20, 30, 50, 0.08); }
.app--docs .next-btn { border-radius: var(--r-pill); padding: 0.4375rem 1.125rem; }
.app--docs .reply-box { border-radius: 0.5rem; box-shadow: 0 0.0625rem 0.1875rem rgba(20, 30, 50, 0.08); }

/* Service desk — canned responses: squared, dense, mono acts, slate. */
.app--desk { --reply-bg: #eef1f5; --accent-ink: #35557a; }
.app--desk .answer-btn,
.app--desk .next-btn { border-radius: 0.1875rem; }
/* The service desk labels its actions in mono uppercase, and so does this. */
.app--desk .next-word { font-family: var(--mono); font-size: 0.71875rem; letter-spacing: 0.06em; text-transform: uppercase; }
.app--desk .reply-box { border-radius: 0.1875rem; }
.app--desk .ans-act { font-family: var(--mono); font-size: 0.65625rem; letter-spacing: 0.06em; text-transform: uppercase; }
.app--desk .sugg-label { color: #35557a; }

/* ==========================================================================
   Widths and heights
   ========================================================================== */

@media (min-width: 700px) {
  .answers { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* A little more ceiling once the options are in columns: it is only ever a
     ceiling, so on the scenarios that do not need it the conversation still
     takes everything back. */
  .reply { padding: 0.6875rem 1.125rem 0.875rem; max-height: 66%; }
}

/* Three across from the width where the pane can carry it. Two columns at
   1280×800 meant three rows of options, and three rows did not fit the zone's
   share of the pane — measured, 40 of 60 scenarios had to scroll their own
   grid on a laptop, which is the wrong place to be scrolling anything. */
@media (min-width: 1180px) {
  .answers { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Short viewports: the zone sheds padding and type size rather than cropping
   anything, and the conversation's floor rises so the peek stays a peek. */
@media (max-height: 720px) {
  .reply { gap: 0.3125rem; padding: 0.5rem 1rem 0.625rem; }
  /* The card keeps its rule and its ground — those are what make it read as the
     question — and pays with its padding, which is the part nothing depends on. */
  .reply-head { padding: 0.4375rem 0.625rem 0.5rem; }
  .answers { gap: 0.375rem; }
  .answer-btn { padding: 0.4375rem 0.5625rem; }
  .ans-text { font-size: 0.8125rem; }
  .reply-wait { min-height: 2.125rem; }
  .next-btn { padding: 0.375rem 0.8125rem; font-size: 0.8125rem; min-height: 2rem; }
}

/* A phone on its side (844×390) is the worst viewport in the build: 22.25rem of
   stage has to carry a header, a conversation, a question and six options.
   Everything compressible is compressed — the message box is the first thing
   to go, because it is furniture and the six suggestions are not — and the
   options go three across so they fit in two rows. */
@media (max-height: 560px) {
  /* 68%, not 62%: at 24.375rem of viewport the six options need every pixel they
     have, and a zone that scrolls internally is worse than a conversation that
     is a peek — the peek is honest, the hidden option is not. The floor is
     still a floor: a third of the pane is the conversation's whatever happens. */
  .reply { gap: 0.1875rem; padding: 0.375rem 0.75rem 0.4375rem; max-height: 68%; }
  .scroll { padding: 0.3125rem 0.75rem 0.375rem; gap: 0.4375rem; }
  /* The field strip below already reads STATUS · Resolved. */
  .status-chip { display: none; }
  .ask-q { font-size: 1.1rem; }
  /* 24.375rem of stage cannot spend a whole line naming the card. The rule down its
     edge and its paper ground already say it is the question, and the OS bar
     above says which of the ten this is. */
  .reply-head { padding: 0.3125rem 0.5625rem 0.375rem; gap: 0.1875rem 0.5625rem; }
  .ask-eyebrow { display: none; }
  .ask-stakes { padding: 0.0625rem 0.375rem; font-size: 0.5625rem; }
  .sugg { gap: 0.25rem; }
  .sugg-label { font-size: 0.5625rem; }
  .answers { gap: 0.25rem; }
  .answer-btn { padding: 0.25rem 0.4375rem; gap: 0.375rem; }
  .ans-key { width: 1rem; height: 1rem; font-size: 0.59375rem; }
  /* The act runs into the message as one paragraph rather than taking a line
     of its own — 0.75rem a card, 1.5rem over two rows, which is most of the reason
     six options fit here at all. */
  .ans-body { font-size: 0.71875rem; line-height: 1.24; }
  .ans-act { display: inline; margin-right: 0.3125rem; font-size: 0.6875rem; }
  .ans-text { display: inline; margin-top: 0; font-size: 0.71875rem; line-height: 1.24; }
  .answer-btn--deed .ans-act { font-size: 0.71875rem; }
  .ans-route {
    display: block;
    max-width: 100%;
    margin-top: 0.0625rem;
    font-size: 0.5625rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .reply-box { display: none; }
  .reply-wait { min-height: 1.5rem; }
  .skip-btn { padding: 0.1875rem 0.625rem; font-size: 0.59375rem; }
  /* Still a finger's target on the worst viewport in the build: it loses its
     padding, not its height. */
  .next-row { gap: 0.5rem; }
  .next-hint { font-size: 0.5625rem; }
  .next-btn { padding: 0.25rem 0.75rem; font-size: 0.78125rem; min-height: 1.875rem; }

  /* Decoration the conversation is paying for. */
  .doc-toolbar { display: none; }
}

@media (max-height: 560px) and (min-width: 640px) {
  .answers { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The app window's own share of a 24.375rem-tall viewport. Every rule here buys
   height back for the conversation, in the order I was willing to spend it:
   the OS bar shrinks, the empty-state card goes (there is no room to be told a
   chat is empty), the folder strip goes (the coloured bar already says which
   app this is), and the pane header drops to one line — the relation is on
   every byline and every routing line anyway. */
@media (max-height: 560px) {
  :root { --os-h: 2.125rem; }

  .convo-intro { display: none; }
  .app-tabs { display: none; }
  .app-bar { min-height: 2rem; }
  .pane-head { padding: 0.25rem 0.6875rem; gap: 0.5rem; }
  .pane-head .head-count { display: none; }
  .head-title { font-size: 0.84375rem; line-height: 1.2; }
  .head-sub { font-size: 0.65625rem; line-height: 1.25; }

  /* The week grid's 8.25rem floor is taller than the whole calendar pane here,
     so it could not shrink, overflowed its own box and was painted over by the
     suggestions below it. Beside the invitation instead of above it, and with
     no floor: the week compresses, and nothing lands on top of anything. */
  .cal-wrap { flex-direction: row; align-items: stretch; }
  .cal-wrap > .cal { flex: 1 1 46%; min-width: 0; min-height: 0; margin: 0.5rem 0 0.5rem 0.75rem; }
  .cal-wrap > .cal-scroll { flex: 1 1 54%; min-height: 0; }
  .cal-day { padding: 0.125rem; font-size: 0.59375rem; }
  .cal-day b { font-size: 0.6875rem; }
  .cal-block { font-size: 0.5625rem; padding: 0 0.1875rem; }
  .cal-block-time { display: none; }
  .cal-time { font-size: 0.5rem; }

  /* Same class of problem: six stacked field rows cannot be allowed to eat the
     activity log they describe. Every field is still here and still says what
     it says — as one horizontal strip that scrolls sideways, which is what a
     service desk does on a small screen. */
  .app--desk .app-pane > .tk-fields {
    display: flex;
    flex-wrap: nowrap;
    margin: 0.375rem 0.75rem 0.4375rem;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior: contain;
    scrollbar-width: none;
  }
  .app--desk .app-pane > .tk-fields::-webkit-scrollbar { display: none; }
  .tk-fields dt {
    border-bottom: 0;
    padding: 0.125rem 0.375rem 0.125rem 0.625rem;
    font-size: 0.53125rem;
    white-space: nowrap;
  }
  .tk-fields dd {
    border-bottom: 0;
    padding: 0.125rem 0.625rem 0.125rem 0;
    font-size: 0.65625rem;
    white-space: nowrap;
  }
  /* "Activity" over a 2.75rem log is a label with nothing under it. */
  .tk-activity-label { display: none; }
  .tk-update { padding-bottom: 0.5rem; }
}

/* --------------------------------------------------------------------------
   Interstitial · name · results · error
   -------------------------------------------------------------------------- */

.clock-big {
  font-family: var(--mono);
  font-size: clamp(2.4rem, 9vw, 3.4rem);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--hivis);
}

#interstitial-text {
  font-size: 1.125rem;
  line-height: 1.45;
  color: #dfe4ec;
  max-width: 32ch;
  padding-top: 1.125rem;
  border-top: 0.0625rem solid var(--ink-line);
}

#screen-name h2 {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: clamp(2.2rem, 8vw, 3rem);
  line-height: 0.95;
  text-transform: uppercase;
  color: #f6f8fc;
  margin-top: 0.5rem;
}

#screen-name p:not(.eyebrow) { color: #c6cdd9; max-width: 40ch; }

#player-name {
  width: 100%;
  min-height: 3.25rem;
  padding: 0.8125rem 0.9375rem;
  background: var(--ink-2);
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-sm);
  color: #f2f5f9;
  font-size: 1.0625rem;
}

#player-name::placeholder { color: #8b95a8; }
#player-name:focus { border-color: var(--hivis); }

/* The reveal button spends itself on the press — one press, one reveal (see
   js/main.js) — and it can be gone for up to four seconds while the persona
   character loads. A hi-vis button that looks exactly as pressable as it did a
   moment ago, having visibly done nothing, is what makes a person press it
   again. So it takes the pressed pose and the darker half of its own pair:
   #c98f00 against the #221a00 ink it already carries measures 6.1:1, so the
   "Revealing…" it says while it works is as readable as the label it replaces.
   Scoped to this button rather than to .btn-primary — the pulse popup's
   confirm disables itself too, and its beat ends the moment it does. */
#reveal:disabled {
  cursor: default;
  background: var(--hivis-dk);
  transform: translateY(0.1875rem);
  box-shadow: 0 0 0 var(--hivis-dk);
}

/* --------------------------------------------------------------------------
   The results report (brief §5c)
   --------------------------------------------------------------------------
   The owner looked at a card reading LRPD and asked what L, R, P and D meant,
   next to six unexplained stat codes. So this is no longer a card in a void
   with a paragraph beside it — it is a scrollable report where every letter
   and every abbreviation is decoded in the same view.

   One thing stays above the fold at every width: the card, the code, the
   rarity line and the share row. They are the growth engine and they do not
   get buried under six sections of analysis.
   -------------------------------------------------------------------------- */
#screen-results .pad {
  /* Not `margin: auto`: a report taller than the viewport would have its top
     centred out of reach. It starts at the top and scrolls. */
  margin: 0 auto;
  max-width: 66.25rem;
  padding-bottom: 2.75rem;
  gap: 0;
}

.results-inner { align-items: stretch; text-align: left; }

.res-hero {
  display: grid;
  gap: 1.125rem;
  align-items: center;
  padding-bottom: 1.625rem;
}

.res-headline { display: flex; flex-direction: column; gap: 0.5625rem; min-width: 0; }

/* The persona, drawn. Decorative and therefore alt=""; the name beside it is
   the <h2>, and a screen reader that heard both would hear it twice. The auto
   side margins centre it in the flex column above the code. */
.res-character { width: 8.25rem; height: auto; margin: 0.375rem auto 0.125rem; display: block; }

/* An author `display` beats the UA stylesheet's [hidden] rule, so the attribute
   only actually hides this if we say so — same reason .res-rarity repeats it.
   renderHeadline sets it when the SVG failed to load, and an <img> with a
   broken src is a broken-image icon, not nothing. */
.res-character[hidden] { display: none; }

/* The thing you screenshot. Largest type on the page, by a distance. */
.res-code {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: clamp(3.4rem, 15vw, 5.6rem);
  line-height: 0.9;
  letter-spacing: 0.1em;
  color: var(--hivis);
}

.res-code-name {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: clamp(1.5rem, 5.5vw, 2.1rem);
  line-height: 1.05;
  text-transform: uppercase;
  color: #ffffff;
}

/* "12.7% of workdays end like yours" — never "of players", which we have not
   measured. rarityLine() in sharecard.js is the only place this is worded. */
.res-rarity {
  font-family: var(--mono);
  font-size: 0.78125rem;
  letter-spacing: 0.05em;
  color: var(--hivis);
}

.res-rarity[hidden] { display: none; }

.res-blurb {
  font-size: 0.96875rem;
  line-height: 1.5;
  font-style: italic;
  color: #dbe2ee;
  max-width: 46ch;
}

/* The code is your type; the aura is your character. Said next to each other,
   but never run together. */
.res-aura {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  margin-top: 0.125rem;
  padding-top: 0.75rem;
  border-top: 0.0625rem solid var(--ink-line);
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--on-ink-2);
}

.res-aura-mark { font-size: 1.0625rem; }
.res-aura-name { color: #f1f4f9; }

.res-replay { display: flex; flex-direction: column; gap: 0.5rem; }

#card-holder:empty { display: none; }

#card-holder {
  width: 100%;
  display: flex;
  justify-content: center;
  padding: 0.375rem 0 0.625rem;
}

/* The card is a canvas, so its shield silhouette lives in the alpha channel:
   the tier glow has to be a drop-shadow filter, because a box-shadow would
   halo the bounding rectangle instead of the card. */
.card {
  display: block;
  width: 100%;
  max-width: 20.625rem;
  height: auto;
  transform-origin: 50% 58%;
  opacity: 0;
  transform: perspective(87.5rem) rotateY(-88deg) scale(0.94);
  filter: drop-shadow(0 0.875rem 1.375rem rgba(0, 0, 0, 0.55));
  transition:
    opacity 380ms ease-out,
    transform 620ms cubic-bezier(0.17, 0.86, 0.26, 1),
    filter 700ms ease-out;
}

.card-revealed { opacity: 1; transform: perspective(87.5rem) rotateY(0deg) scale(1); }

.card-bronze.card-revealed {
  filter: drop-shadow(0 0 0.875rem rgba(196, 134, 72, 0.34)) drop-shadow(0 0.875rem 1.375rem rgba(0, 0, 0, 0.55));
}

.card-silver.card-revealed {
  filter: drop-shadow(0 0 1.0625rem rgba(206, 220, 235, 0.36)) drop-shadow(0 0.875rem 1.375rem rgba(0, 0, 0, 0.55));
}

.card-gold.card-revealed {
  filter: drop-shadow(0 0 1.375rem rgba(255, 199, 74, 0.5)) drop-shadow(0 0.875rem 1.5rem rgba(0, 0, 0, 0.55));
}

.card-foil.card-revealed {
  filter: drop-shadow(0 0 1.25rem rgba(255, 120, 220, 0.48)) drop-shadow(0 0.875rem 1.5rem rgba(0, 0, 0, 0.55));
  animation: foil-halo 5s ease-in-out infinite;
}

@keyframes foil-halo {
  0%, 100% { filter: drop-shadow(0 0 1.25rem rgba(255, 90, 210, 0.5)) drop-shadow(0 0.875rem 1.5rem rgba(0, 0, 0, 0.55)); }
  33% { filter: drop-shadow(0 0 1.375rem rgba(90, 225, 255, 0.5)) drop-shadow(0 0.875rem 1.5rem rgba(0, 0, 0, 0.55)); }
  66% { filter: drop-shadow(0 0 1.3125rem rgba(255, 214, 92, 0.5)) drop-shadow(0 0.875rem 1.5rem rgba(0, 0, 0, 0.55)); }
}

/* --- the six report sections --------------------------------------------- */

#report { display: flex; flex-direction: column; }

.res-section {
  padding: 1.625rem 0;
  border-top: 0.0625rem solid var(--ink-line);
}

.res-head { display: flex; align-items: baseline; gap: 0.6875rem; }

/* A numbered report, so the player knows how much is left and can find their
   way back to a section they want to screenshot. */
.res-n {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--r-sm);
  background: var(--hivis);
  color: #241b00;
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 700;
}

.res-title {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: clamp(1.35rem, 4.6vw, 1.85rem);
  line-height: 1.05;
  text-transform: uppercase;
  color: #ffffff;
}

.res-lead {
  margin-top: 0.4375rem;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--on-ink-2);
  max-width: 68ch;
}

.res-body { margin-top: 1rem; }

/* Something the player actually sent, quoted back at them. */
.res-quote {
  margin: 0;
  font-size: 1.0625rem;
  line-height: 1.45;
  color: #f4f7fb;
  padding-left: 0.8125rem;
  border-left: 0.1875rem solid var(--hivis);
  overflow-wrap: anywhere;
}

.res-deed {
  font-size: 1.0625rem;
  line-height: 1.45;
  color: #f4f7fb;
  padding-left: 0.8125rem;
  border-left: 0.1875rem solid var(--hivis-dk);
}

.res-quote-src {
  margin-top: 0.3125rem;
  padding-left: 1rem;
  font-family: var(--mono);
  font-size: 0.65625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--on-ink-2);
}

/* --- 1. Your code, decoded ------------------------------------------------ */

.axis-list, .stat-list, .number-list, .via-list, .code-grid {
  list-style: none;
  margin: 0;
  padding: 0;
}

.axis-list { display: grid; gap: 0.75rem; }

.axis {
  padding: 0.875rem 1rem;
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-md);
  background: var(--ink-3);
}

/* Landed by a hair. The border is not the only signal — the near-miss line is
   written out under every axis regardless. */
.axis--near { border-color: var(--hivis-dk); }

.axis-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.1875rem 0.625rem; }

.axis-name {
  font-family: var(--mono);
  font-size: 0.65625rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--hivis);
}

.axis-q { font-size: 0.8125rem; color: var(--on-ink-2); }

.axis-poles { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.25rem 0.625rem; margin-top: 0.375rem; }

.axis-you {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: 1.55rem;
  line-height: 1.05;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #ffffff;
}

.axis-vs {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.05em;
  color: #8b98ad;
}

/* How strongly, normalised per axis — the four axes reach ±118, ±59, ±177 and
   ±118, so a shared bar drawn from raw margins would lie about three of them. */
.axis-bar {
  height: 0.4375rem;
  margin: 0.5625rem 0 0.625rem;
  border-radius: 0.25rem;
  background: #232c3c;
  overflow: hidden;
}

.axis-fill { display: block; height: 100%; background: var(--hivis); }

.axis-said { font-size: 0.9375rem; line-height: 1.45; color: #e7ecf4; }
.axis-evidence { margin-top: 0.3125rem; font-size: 0.84375rem; line-height: 1.45; color: var(--on-ink-2); }

.axis-near {
  margin-top: 0.4375rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  line-height: 1.5;
  letter-spacing: 0.03em;
  color: var(--hivis);
}

/* The same line on an axis that was never close. It still states the real
   distance, but hi-vis is reserved for the near-misses the row border already
   marks — four hi-vis lines on four landslides is four false alarms. */
.axis-near--far { color: var(--on-ink-2); }

/* --- 2. Your stats, decoded ----------------------------------------------- */

.stat-list { display: grid; gap: 0.6875rem; }

.stat-row {
  padding: 0.8125rem 0.9375rem;
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-md);
  background: var(--ink-3);
}

.stat-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }

.stat-name {
  font-family: var(--mono);
  font-size: 0.78125rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--hivis);
}

.stat-value {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: 1.7rem;
  line-height: 1;
  color: #ffffff;
}

.stat-what { margin-top: 0.25rem; font-size: 0.84375rem; line-height: 1.45; color: var(--on-ink-2); }

.stat-peak-label {
  margin: 0.625rem 0 0.3125rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #8b98ad;
}

/* Not a label but a finding, so it reads as a sentence rather than as small
   caps that happen to be a paragraph. */
.stat-peak-label--none {
  margin-bottom: 0;
  font-family: inherit;
  font-size: 0.84375rem;
  line-height: 1.45;
  letter-spacing: normal;
  text-transform: none;
  color: #8b98ad;
}

.stat-row .res-quote { font-size: 0.9375rem; }
.stat-row .res-deed { font-size: 0.9375rem; }

/* --- 3. Your workday, in numbers ------------------------------------------ */

.via-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.via {
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  padding: 0.375rem 0.75rem;
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-pill);
  background: var(--ink-2);
}

.via-count { font-family: var(--display); font-stretch: condensed; font-weight: 400; font-size: 1.15rem; color: var(--hivis); }
.via-label { font-size: 0.78125rem; color: #dbe2ee; }

.number-list { display: grid; gap: 0.625rem; }

.number {
  display: grid;
  grid-template-columns: 3.375rem minmax(0, 1fr);
  align-items: baseline;
  gap: 0.125rem 0.875rem;
  padding: 0.75rem 0.9375rem;
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-md);
  background: var(--ink-3);
}

.number-value {
  grid-row: 1 / span 2;
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: 2rem;
  line-height: 1;
  color: var(--hivis);
  overflow-wrap: anywhere;
}

.number-label { font-size: 0.90625rem; color: #f1f4f9; }
.number-line { font-size: 0.84375rem; line-height: 1.45; color: var(--on-ink-2); }

/* A row whose answer is a person, not a count: a name does not fit a 3.375rem
   numeral column, so it takes the full width and sits above its label. */
.number--name { grid-template-columns: minmax(0, 1fr); gap: 0.125rem; }

.number--name .number-value {
  grid-row: auto;
  font-size: 1.5rem;
  line-height: 1.1;
  text-transform: uppercase;
}

/* --- 4. Your defining moment ---------------------------------------------- */

.res-reaction { margin-top: 0.8125rem; font-size: 0.9375rem; line-height: 1.45; color: #dbe2ee; }

/* The narrator, not a colleague — same voice it has in the app windows. */
.res-caption {
  margin-top: 0.5625rem;
  padding-left: 0.8125rem;
  border-left: 0.1875rem solid var(--hivis-dk);
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--on-ink-2);
}

.aura-verdict {
  margin-top: 1.25rem;
  padding: 0.9375rem 1rem;
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-md);
  background: var(--ink-3);
}

.aura-verdict-name {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: 1.5rem;
  line-height: 1.05;
  text-transform: uppercase;
  color: var(--hivis);
}

.aura-verdict-roast { margin-top: 0.5rem; font-size: 0.90625rem; line-height: 1.55; color: #dbe2ee; }
.aura-verdict-quote { margin-top: 0.625rem; font-style: italic; font-size: 0.96875rem; color: #f1f4f9; }
.aura-verdict-note { margin-top: 0.625rem; font-size: 0.8125rem; line-height: 1.45; color: var(--on-ink-2); }

/* --- 5. Where you belong, and who with ------------------------------------ */

.belong-pair { display: grid; gap: 0.6875rem; margin-bottom: 0.6875rem; }

.belong {
  padding: 0.875rem 0.9375rem;
  border: 0.0625rem solid var(--ink-line);
  border-left-width: 0.1875rem;
  border-radius: var(--r-md);
  background: var(--ink-3);
}

/* Four different things, told apart by their edge AND by their label — the
   label is always the first line, so the colour is never doing it alone. */
.belong--yes { border-left-color: var(--hivis); }
.belong--no { border-left-color: #7d879a; }
.belong--ally { border-left-color: #4fb286; }
.belong--nemesis { border-left-color: #d05f4e; }

.belong-label {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-ink-2);
}

.belong-role {
  display: block;
  margin-top: 0.3125rem;
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: 1.4rem;
  line-height: 1.1;
  text-transform: uppercase;
  color: #ffffff;
}

/* Linked into the collection below: a code named here is never a dead end. */
.belong-code {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.25rem 0.5625rem;
  margin-top: 0.3125rem;
  color: inherit;
  text-decoration: none;
}

.belong-code:hover .belong-code-name { text-decoration: underline; }

.belong-code-key {
  font-family: var(--mono);
  font-size: 0.9375rem;
  letter-spacing: 0.11em;
  color: var(--hivis);
}

.belong-code-name {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: 1.4rem;
  line-height: 1.1;
  text-transform: uppercase;
  color: #ffffff;
}

.belong-share {
  margin-top: 0.1875rem;
  font-family: var(--mono);
  font-size: 0.65625rem;
  letter-spacing: 0.04em;
  color: #8b98ad;
}

.belong-why { margin-top: 0.4375rem; font-size: 0.84375rem; line-height: 1.5; color: #dbe2ee; }

.belong-facts {
  margin: 0.875rem 0 0;
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 0.375rem 0.875rem;
  border-top: 0.0625rem solid var(--ink-line);
  padding-top: 0.875rem;
}

.belong-facts dt {
  font-family: var(--mono);
  font-size: 0.65625rem;
  line-height: 1.7;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--on-ink-2);
}

.belong-facts dd { margin: 0; font-size: 0.875rem; line-height: 1.45; color: #e2e8f1; }

/* --- 6. The collection ---------------------------------------------------- */

.code-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.625rem, 1fr));
  gap: 0.5625rem;
}

.code-tile {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  padding: 0.6875rem 0.75rem;
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-md);
  background: var(--ink-3);
  scroll-margin-top: 1.25rem;
}

.code-key {
  font-family: var(--mono);
  font-size: 0.875rem;
  letter-spacing: 0.13em;
  color: #8b98ad;
}

.code-name {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: 1.15rem;
  line-height: 1.12;
  text-transform: uppercase;
  color: #dbe2ee;
}

.code-share {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.04em;
  color: #7d879a;
}

.code-flag {
  margin-top: 0.25rem;
  font-family: var(--mono);
  font-size: 0.59375rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #7d879a;
}

/* Never earned: named, so the player can see what they are missing, but plainly
   not theirs. Dimmed AND labelled "Not yet" — the words carry it on their own. */
.code-tile.is-locked .code-name { color: #97a2b4; }

.code-tile.is-earned { border-color: #3c4859; background: var(--ink-2); }
.code-tile.is-earned .code-key { color: #b8c3d4; }
.code-tile.is-earned .code-name { color: #f1f4f9; }
.code-flag--past { color: #93cbb1; }

.code-tile.is-mine {
  border-color: var(--hivis);
  background: rgba(255, 190, 11, 0.09);
}

.code-tile.is-mine .code-key,
.code-tile.is-mine .code-name { color: var(--hivis); }
.code-tile.is-mine .code-flag { color: var(--hivis); }

/* A tile jumped to from the ally/nemesis links says so for a beat. */
.code-tile:target { border-color: var(--hivis); }

.code-tally {
  margin-top: 0.875rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--on-ink-2);
}

#share-row {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

#screen-results .btn-primary { max-width: 21.25rem; }

.share-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.625rem;
  padding: 0 1rem;
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-pill);
  background: var(--ink-2);
  color: #e6ecf5;
  font-family: var(--mono);
  font-size: 0.78125rem;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  transition: background-color 120ms ease, border-color 120ms ease, transform 90ms ease;
}

.share-link:hover { background: #273143; border-color: #3c4859; }
.share-link:active { transform: translateY(0.0625rem); }

.share-url {
  min-height: 2.625rem;
  min-width: 0;
  flex: 1 1 13.75rem;
  max-width: 21.25rem;
  padding: 0 0.875rem;
  border: 0.0625rem solid var(--ink-line);
  border-radius: var(--r-pill);
  background: var(--ink-2);
  color: #e6ecf5;
  font-family: var(--mono);
  font-size: 0.75rem;
  text-align: center;
}

#pool-teaser {
  font-family: var(--mono);
  font-size: 0.71875rem;
  line-height: 1.6;
  color: var(--on-ink-2);
}

#pool-teaser:empty { display: none; }

/* Reserved so filling it later cannot shove the page around. */
#ad-slot {
  flex: 0 0 auto;
  width: 20rem;
  max-width: 100%;
  height: 6.25rem;
  margin-top: 0.25rem;
}

#ad-slot:empty { visibility: hidden; }


/* Wide: the card is the poster and the headline reads beside it, so the whole
   thing you screenshot lands in one screen and the report scrolls under it. */
@media (min-width: 880px) {
  .res-hero {
    grid-template-columns: minmax(0, 20.625rem) minmax(0, 1fr);
    column-gap: 2.875rem;
  }

  #card-holder { padding: 0; }
  #screen-results #share-row { justify-content: flex-start; }
  #screen-results #replay { justify-self: start; min-width: 17.5rem; }
  .res-replay { flex-direction: row; align-items: center; gap: 1rem; flex-wrap: wrap; }
  #screen-results .btn-primary { flex: 0 0 auto; }

  /* Two columns of analysis: the report is long and the sections are short. */
  .axis-list, .stat-list, .number-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .belong-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* On a phone the card must not push the code below the fold, so it sits at a
   size that leaves the headline room beside nothing but itself. */
@media (max-width: 560px) {
  #screen-results .pad { padding: 0.75rem 1rem 2.25rem; }
  #card-holder { padding: 0 0 0.25rem; }
  .card { max-width: 15.625rem; }
  .res-hero { gap: 0; padding-bottom: 1.25rem; }
  .res-section { padding: 1.25rem 0; }
  .number { grid-template-columns: 2.875rem minmax(0, 1fr); padding: 0.6875rem 0.8125rem; }
  .number-value { font-size: 1.7rem; }
  .code-grid { grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); gap: 0.4375rem; }

  /* The headline character at its full 8.25rem pushed the share row — the
     growth engine, and the one thing this file swears stays above the fold —
     9.8125rem past an 52.75rem phone: measured at 62.5625rem instead of 844. It also
     duplicated the card's own portrait at near-identical size on the same
     screen. Shrunk to a third of its size, and with .res-hero and
     .res-headline's own gaps zeroed here (every line in this column already
     carries its own font's line-height, so touching margin boxes reads as
     snug rather than broken — checked on screen, not just in the numbers).

     THE WORST CASE IS MEASURED, NOT GUESSED, AND IT IS NOT THE LONGEST STRING.
     Every one of the sixteen codes was rendered against every one of the
     twelve auras — 192 real combinations, each code carrying its own name,
     blurb and rarity — and the share row's bottom read off
     getBoundingClientRect. Picking the worst by character count is what left
     a 0.1875rem breach here before: LAUD has the longest blurb (102 chars) but sets
     two lines, while LACG's 97 wraps to three, so the sweep that "found" 51.5rem
     had never drawn the tallest hero at all. The real numbers at 390×844 are
     52.9375rem with the old spacing (14 of the 192 over the fold) and 50.5rem with
     the rules below — 2.25rem of clearance, nothing over. The blurb is a step
     smaller here for the same reason: measured at 24.375rem wide, LACG and LRUG
     (97 chars each) set three lines at 0.96875rem while every other blurb sets
     two, and at 0.90625rem all sixteen set two. That single line is worth 1.4375rem —
     more than half a point of type is worth on the one screen with no room —
     and it takes the height of this column off the draw of the day. */
  .res-character { width: 2.75rem; margin: 0.125rem auto 0; }
  .res-headline { gap: 0; }
  .res-blurb { font-size: 0.90625rem; line-height: 1.45; }
  /* .res-headline's gap is 0 here, and the aura is the one line with a control
     under it: without this it sits hard against the Share button. */
  .res-aura { margin-bottom: 0.5rem; }
}

/* The middle band — wider than the phone bucket above, narrower than the
   two-column hero at 55rem. The card is at its full 20.625rem AND still stacked on
   top of the headline here, so this column is the tallest the hero ever gets:
   55.5rem of it before the persona is drawn at all, measured across the same 192
   combinations. A 8.25rem persona costs another 11.75rem and puts the share row at
   67.25rem, past the fold on every tablet in this band; at 4rem it costs 6rem
   and lands at 61.5rem, clear of a 64rem iPad by 2.5rem. Bigger than the phone's
   2.75rem because there is more room here — not because it was rounded up. */
@media (min-width: 561px) and (max-width: 879px) {
  .res-character { width: 4rem; margin: 0.25rem auto 0; }
}

/* A phone on its side. 24.375rem of height cannot stack a card on top of a code
   and a share row, so the hero goes side by side at a size the height can
   actually hold — the card stays the poster, but it stops being the entire
   first screen. Everything here buys vertical room; nothing is removed. */
@media (max-height: 560px) and (min-width: 640px) {
  #screen-results .pad { padding: 0.875rem 1.25rem 1.875rem; }

  .res-hero {
    /* Named once because the persona below is placed off both of them: the
       card's cap sets where the empty space under it starts, and the persona's
       own width centres it in that column. */
    --hero-card: 11rem;
    --hero-persona: 4rem;
    position: relative;
    grid-template-columns: minmax(0, var(--hero-card)) minmax(0, 1fr);
    column-gap: 1.5rem;
    align-items: start;
    gap: 0.75rem;
    padding-bottom: 1rem;
    /* The persona is out of flow, so nothing else reserves room for it: card,
       the gap under it, the persona, a little air. Without this the hero can
       end above the persona's feet and it reaches into the report below. */
    min-height: calc(var(--hero-card) * 1.4 + 0.75rem + var(--hero-persona) * 1.3 + 0.625rem);
  }

  #card-holder { padding: 0; }
  .card { max-width: var(--hero-card); }

  /* A phone held sideways is the one place the persona cannot be a line in the
     headline column. That column already runs from the eyebrow to the replay
     button in 20.5rem of a 24.375rem screen; adding 11.5625rem of persona to it put the
     share row at 32.0625rem, 7.6875rem below the fold and entirely off screen — the
     same defect the phone bucket above exists to fix, in the one bucket that
     had not been measured. Shrinking it enough to fit means ~2.25rem, smaller
     than the phone's, and even then a 23.4375rem-tall landscape still breaches.

     So it does not go in that column at all. The card is capped at 11rem and
     the art is 1200×1680, so the card stops 15.375rem down and the rest of its
     column — 8.125rem and more of it — is empty. The persona stands in that
     space, out of flow, and costs the share row nothing: measured at 20.5rem on
     an 844×390 iPhone, exactly where it sat before the persona existed, and
     clear of the fold on every landscape phone tested (800×360, 812×375,
     667×375, 932×430). Both offsets below are computed from the two custom
     properties above, so moving the card's cap moves the persona with it.

     4rem, not larger, because it is the last size whose feet are still on the
     first screen: 0.875rem of padding + 15.375rem of card + 0.75rem of air leaves 5.5rem
     under it on the shortest landscape phone here (800×360), and 64 × 1.3 is
     83. A 6rem persona measures the same at the share row — it is out of flow
     either way — but gets cropped by the fold, which reads as a bug. */
  .res-character {
    position: absolute;
    top: calc(var(--hero-card) * 1.4 + 0.75rem);
    left: calc((var(--hero-card) - var(--hero-persona)) / 2);
    width: var(--hero-persona);
    margin: 0;
  }

  .res-code { font-size: 2.5rem; letter-spacing: 0.08em; }
  .res-code-name { font-size: 1.2rem; }
  .res-rarity { font-size: 0.6875rem; }
  .res-blurb { font-size: 0.8125rem; line-height: 1.4; }
  .res-aura { margin-top: 0; padding-top: 0.5rem; font-size: 0.78125rem; }
  .res-headline { gap: 0.375rem; }
  .res-replay { flex-direction: row; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
  #screen-results #share-row { justify-content: flex-start; }
  #screen-results .btn-primary { flex: 0 0 auto; min-width: 13.75rem; }
  .res-section { padding: 1.125rem 0; }
}

/* --------------------------------------------------------------------------
   Error
   -------------------------------------------------------------------------- */
#screen-error h2 { font-size: 1.375rem; line-height: 1.3; font-weight: 700; color: #f6f8fc; max-width: 22ch; }
#screen-error p { color: var(--on-ink-2); }

/* --------------------------------------------------------------------------
   HR's pulse check (spec §1)
   --------------------------------------------------------------------------
   Four times a day a survey tool takes the stage. It obeys the same rule as
   everything else on this screen: COLOUR = WHERE YOU ARE. The app window has
   gone, so what is left is the desk, with one piece of HR software on it —
   paper, its own brand strip, its own product name. The statement inside is
   set plainly, because the frame is the joke and the question is not.

   The one exception is the confirm button, which stays the game's own hi-vis:
   it is the single control on the screen, there is no second way past it, and
   it has to be unmissable at arm's length on a phone.
   -------------------------------------------------------------------------- */

.pulse-stage {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* Taller than the room it has (a 24.375rem-tall landscape phone) means scroll,
     never clip: the confirm button is the only way out of this beat. The auto
     margins below centre the card when there IS free space and resolve to
     zero when there is not, so its top edge can never end up out of reach. */
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1.125rem 1rem 1.625rem;
  background: var(--desk);
}

.pulse-window {
  /* Synergy Dynamics' people-experience platform, which is a teal none of the
     seven app skins uses. White on it clears 6.5:1. */
  --brand: #0b6a60;
  /* Never shrunk to fit. As a flex item it would otherwise be squeezed into
     whatever height is going and, with overflow hidden for the sake of the
     header's corners, quietly clip its own last line — measured on a 24.375rem-
     tall landscape phone, where the card lost the note under the button. It
     keeps its natural height and the stage scrolls instead. */
  flex: 0 0 auto;
  margin: auto;
  width: 100%;
  max-width: 27.5rem;
  border-radius: var(--r-md);
  background: var(--paper);
  color: var(--text);
  box-shadow: 0 1.125rem 3rem rgba(15, 19, 25, 0.38);
  overflow: hidden;
  opacity: 0;
  transform: translateY(0.875rem);
  transition: transform 240ms ease, opacity 240ms ease;
}

.pulse-window.is-in { opacity: 1; transform: none; }
/* Focused programmatically when the confirm button disables itself, never in
   the tab order — so it must not draw a ring of its own. */
.pulse-window:focus { outline: none; }

.pulse-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.625rem;
  padding: 0.6875rem 1rem;
  background: var(--brand);
  color: #fff;
}

.pulse-title { font-size: 0.90625rem; font-weight: 700; letter-spacing: 0.01em; }

.pulse-meta {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: rgba(255, 255, 255, 0.88);
  white-space: nowrap;
}

.pulse-body { padding: 0.9375rem 1.125rem 1.125rem; }

.pulse-fine {
  font-size: 0.71875rem;
  color: var(--muted);
  margin-bottom: 0.875rem;
}

/* Plain, and the biggest thing in the card. */
.pulse-statement {
  font-size: clamp(1rem, 4.6vw, 1.125rem);
  line-height: 1.45;
  margin-bottom: 1.375rem;
}

.pulse-range {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  /* The element is a 2.75rem touch target; the track is drawn 0.5rem inside it. */
  height: 2.75rem;
  margin: 0;
  background: transparent;
  cursor: pointer;
}

.pulse-range::-webkit-slider-runnable-track {
  height: 0.5rem;
  border-radius: var(--r-pill);
  background: var(--line);
}

.pulse-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 1.75rem;
  height: 1.75rem;
  margin-top: -0.625rem; /* centre the thumb on the 0.5rem track */
  border: 0.1875rem solid var(--paper);
  border-radius: var(--r-pill);
  background: var(--brand);
  box-shadow: 0 0.0625rem 0.25rem rgba(15, 19, 25, 0.35);
}

.pulse-range::-moz-range-track {
  height: 0.5rem;
  border-radius: var(--r-pill);
  background: var(--line);
}

.pulse-range::-moz-range-thumb {
  width: 1.375rem;
  height: 1.375rem;
  border: 0.1875rem solid var(--paper);
  border-radius: var(--r-pill);
  background: var(--brand);
  box-shadow: 0 0.0625rem 0.25rem rgba(15, 19, 25, 0.35);
}

.pulse-ends {
  display: flex;
  justify-content: space-between;
  gap: 0.875rem;
  font-size: 0.71875rem;
  color: var(--muted);
}

/* Where the handle is, in words. Middle of the scale included: "Neutral" is a
   stop like any other and is never dressed up as a missing answer. */
.pulse-reading {
  width: max-content;
  max-width: 100%;
  margin: 0.875rem auto 1.125rem;
  padding: 0.3125rem 0.875rem;
  border-radius: var(--r-pill);
  background: var(--line-soft);
  font-size: 0.90625rem;
  font-weight: 700;
  text-align: center;
}

.pulse-submit { width: 100%; }

/* Spent the instant it is pressed — see renderPulse(): the button is disabled
   in the same task as the press, and this is what that looks like. */
.pulse-window.is-sent .pulse-submit {
  opacity: 0.55;
  transform: translateY(0.1875rem);
  box-shadow: 0 0 0 var(--hivis-dk);
}

.pulse-note {
  margin-top: 0.625rem;
  font-size: 0.71875rem;
  color: var(--muted);
  text-align: center;
}

/* A landscape phone: the card keeps the statement and the scale full size and
   spends its chrome instead. */
@media (max-height: 560px) {
  .pulse-stage { padding: 0.625rem 0.75rem 1rem; }
  .pulse-body { padding: 0.75rem 1rem 0.875rem; }
  .pulse-fine { margin-bottom: 0.625rem; }
  .pulse-statement { margin-bottom: 0.875rem; }
  .pulse-reading { margin: 0.5rem auto 0.75rem; }
}

/* --------------------------------------------------------------------------
   The situation briefing (js/render/briefing.js)
   --------------------------------------------------------------------------
   Before every situation the app window goes away and the game speaks for
   itself, so this is the one dark thing on the game screen: the ink ground the
   interstitials and the lock screen use, the display face reserved for the
   game's own voice, and the amber that no office skin owns.

   It is a document being written a sentence at a time, not three screens in a
   row. Every step keeps a hairline of its own down its left edge — dim for the
   ones already read, amber for the one being read now — so what the player
   sees by the last press is the whole situation, in order, with their place in
   it marked. Nothing is ever removed.

   The control never moves, and the layout is final before the player presses
   anything: the steps not yet read are laid out and invisible from the first
   frame, so the sheet is already the height it will end up. A control that is
   about to be pressed two, three or four times in a row has to stay under the
   finger that is pressing it.
   -------------------------------------------------------------------------- */

.briefing-stage {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 1.625rem 1.25rem 1.25rem;
  background: var(--ink);
  color: var(--on-ink);
  /* The default focus blue is all but invisible on ink; the away-from-the-desk
     screens make the same swap. */
  --focus: var(--hivis);
}

/* Three bands: the heading, the prose, the control. The sheet is exactly as
   tall as the whole briefing — every step reserves its space from the first
   frame (see renderBriefing) — so the control sits in the same pixels for
   every press and none of the read steps ever move. Auto margins centre it
   when there is spare room and collapse to zero when there is not, so the
   heading can never end up above the top of the stage; on a screen too short
   to hold the briefing the middle band is the only thing that gives, and it
   gives by scrolling. */
.briefing-sheet {
  flex: 0 1 auto;
  min-height: 0;
  max-height: 100%;
  margin: auto;
  width: 100%;
  max-width: 34rem;
  display: flex;
  flex-direction: column;
}

/* Focused programmatically when the last press spends the button. */
.briefing-sheet:focus { outline: none; }

/* The rule under the heading belongs to the heading, not to the prose: the
   prose scrolls and the rule must not move with it. */
.briefing-head {
  flex: 0 0 auto;
  padding-bottom: 1rem;
  border-bottom: 0.0625rem solid var(--ink-line);
}

/* The shared eyebrow, laid out as a header row: what this is on the left, how
   far through it the player is on the right. Its own rule comes off — the one
   this sheet needs is the one under the whole heading. */
.briefing-eyebrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
  padding-bottom: 0;
  border-bottom: 0;
}

/* At 24.375rem the whole line wraps under the counter, and the company name is
   the half that can go: the eyebrow's job here is to say what this screen is. */
@media (max-width: 559px) {
  .briefing-org { display: none; }
}

/* Where the player is in the briefing, in the machine voice the OS bar uses. */
.briefing-count {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  color: var(--on-ink-2);
  white-space: nowrap;
}

.briefing-q {
  font-family: var(--display);
  font-weight: 400;
  font-stretch: condensed;
  font-size: clamp(2.1rem, 8vw, 2.9rem);
  line-height: 0.95;
  letter-spacing: 0.005em;
  color: var(--on-ink);
}

/* The run position, kept inside the heading so the dialog's accessible name is
   the whole of "Question 3 of 10", and kept quiet so the number is what reads
   from across a desk. */
.briefing-of {
  font-size: 0.5em;
  color: var(--on-ink-2);
}

/* Which surface is about to open. A label on a thing, so it is set as the
   chrome of that thing would be, not as prose. */
.briefing-where {
  margin-top: 0.5625rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--on-ink-2);
  overflow-wrap: anywhere;
}

/* The one band that gives, and the reason the control below it can be treated
   as fixed furniture: four accumulated steps are taller than a 24.375rem-tall
   landscape phone, and on that phone the last line of the last step would
   otherwise be printed underneath the button. The renderer scrolls the step
   being read into view as it arrives, so it is always on screen and the ones
   before it are always one flick away. */
.briefing-steps {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1rem 0;
  /* Scrolled prose fades out at both edges instead of being sliced off mid-x-
     height against the rule above it and the control below it. A mask rather
     than a gradient overlay because this is the element that scrolls: anything
     painted inside it would scroll away with the text it is covering. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 1rem,
    #000 calc(100% - 1rem), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 1rem,
    #000 calc(100% - 1rem), transparent 100%);
}

/* Laid out, invisible, and out of the accessibility tree: the space the rest
   of the briefing will take, held open from the first frame. */
.briefing-step.is-pending { visibility: hidden; }

/* One sentence, one hairline. Read steps stay at --on-ink-2, which is 7.3:1 on
   this ground: quieter than the live one, never dimmed past reading — the
   whole point of leaving them up is that they can still be read. */
.briefing-step {
  /* Clearance for scrollIntoView, so a step scrolled to is never brought to
     rest with its last line inside the fade at the edge of the band. */
  scroll-margin: 1.125rem 0;
  padding-left: 0.9375rem;
  border-left: 0.125rem solid var(--ink-line);
  font-size: clamp(1rem, 4.3vw, 1.15625rem);
  line-height: 1.6;
  color: var(--on-ink-2);
  transition: color 200ms ease, border-color 200ms ease;
  animation: briefing-step-in 200ms ease-out both;
}

.briefing-step + .briefing-step { margin-top: 0.875rem; }

.briefing-step.is-current {
  color: var(--on-ink);
  border-left-color: var(--hivis);
}

@keyframes briefing-step-in {
  from { opacity: 0; transform: translateY(0.25rem); }
  to   { opacity: 1; transform: none; }
}

/* The SAME pixels on every step of every briefing. A control pressed two, three
   or four times in a row must not move between the presses: the finger is
   already there, and a target that has slid out from under it is how a player
   comes to press the prose and conclude the game has stopped responding. */
.briefing-foot {
  flex: 0 0 auto;
  margin-top: 0.25rem;
}

/* Spent the instant the last step is pressed — the button is disabled in the
   same task as the press, and this is what that looks like. */
.briefing-sheet.is-open .briefing-next {
  opacity: 0.55;
  transform: translateY(0.1875rem);
  box-shadow: 0 0 0 var(--hivis-dk);
}

.briefing-hint {
  margin-top: 0.5rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  color: var(--on-ink-2);
}

/* A landscape phone — 24.375rem tall with the OS bar on top of it, the tightest
   viewport this game has. The heading gives up its size first, then the
   spacing; the steps themselves stay at reading size, because they are the
   only reason this screen exists. */
@media (max-height: 560px) {
  .briefing-stage { padding: 0.875rem 1.125rem 0.75rem; }
  .briefing-q { font-size: clamp(1.5rem, 5.4vh, 2rem); }
  .briefing-where { margin-top: 0.375rem; }
  .briefing-head { padding-bottom: 0.75rem; }
  .briefing-steps { padding: 0.75rem 0; }
  .briefing-step { font-size: 1rem; line-height: 1.5; }
  .briefing-step + .briefing-step { margin-top: 0.625rem; }
  .briefing-foot { margin-top: 0.125rem; }
  .briefing-hint { display: none; }
}

/* --------------------------------------------------------------------------
   Small screens
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Phones — first class, because sharing happens here.
   Every pixel the chrome gives back goes to the conversation. The options are
   the one thing that never shrinks below legibility: the action prefix and the
   message run together as one line of prose so six of them still fit whole.
   -------------------------------------------------------------------------- */

@media (max-width: 559px) {
  /* Every pixel of chrome here is a pixel of conversation. */
  :root { --os-h: 2.25rem; }

  /* The OS bar keeps the clock, the progress and the sound toggle; the
     company name is the first thing to go, because the app bar underneath
     already says where you are. */
  #os-bar { gap: 0.625rem; padding: 0 0.375rem 0 0.6875rem; }
  #clock { font-size: 0.75rem; white-space: nowrap; }
  #company { display: none; }
  #run-count { font-size: 0.625rem; letter-spacing: 0.06em; }
  #sound-toggle { width: 2.125rem; height: 2.125rem; }

  /* Letting the whole pane scroll bought height for the options and cost the
     app its structure: at 390 the week grid, the ticket's field table and the
     document page all scrolled off the top and the message area collapsed to
     nothing. So the pane does NOT scroll here either — each surface keeps the
     thing that makes it that app pinned, and only its conversation moves,
     exactly as on a laptop. The height comes back from the chrome instead:
     the folder strip goes (the bar's "where" chip already names the folder),
     the header is one line, and the participants strip is a single row. */
  .app-tabs { display: none; }
  .pane-head .head-count { display: none; }

  .app-bar { min-height: 2.125rem; gap: 0.5rem; padding: 0 0.625rem; }
  .app-brand { font-size: 0.78125rem; }
  .app-search { display: none; }
  .scroll { padding: 0.5625rem 0.6875rem 0.75rem; gap: 0.5rem; }
  .convo-intro { margin: -0.5625rem -0.6875rem 0; padding: 0.5rem 0.6875rem 0.75rem; }
  .chan-log > .convo-intro { margin-top: -0.5625rem; }
  /* No bleed on the desk at any width — see .tk-scroll's own rule. The `.scroll`
     shorthand above puts the 0.5625rem back, so the desk takes it off again. */
  .app--desk .app-pane > .tk-scroll { padding-top: 0; }
  .tk-scroll > .convo-intro { margin-top: 0; }
  .intro-inner { margin: 0 -0.6875rem; padding: 0.5rem 0.9375rem; }
  .intro-inner .avatar, .intro-mark { width: 2.25rem; height: 2.25rem; font-size: 0.9375rem; }
  .intro-title { font-size: 0.9375rem; }
  .intro-line { font-size: 0.71875rem; }
  /* The To line is back on a phone. It was hidden here because "who a mail is
     addressed to is already in the participants strip" — and the participants
     strip was deleted two rounds of playtest ago (see saidOf in kit.js), so
     for every phone player since, the one thing a reply-all scenario is ABOUT
     was on screen nowhere above the fold. Two lines, then clipped: one line
     ends "…; Katherine; +39…" and eats the number, which is the half of the
     line worth having; three would be a paragraph. */
  .email { padding: 0.5625rem 0.6875rem; }
  .email-to {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .pane-head { padding: 0.4375rem 0.6875rem; gap: 0.5rem; }
  /* A subject that wraps to three lines is a header, not a subject. Two, then
     ellipsis — which is what every phone mail client does. */
  .head-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: 0.90625rem;
  }
  /* -webkit-box is not a flex container, so the `gap` .head-title carries at
     every other width stops applying here and the service desk's ticket-number
     chip runs straight into the title: "#40219RESOLVED". At this width the
     chip carries its own space. */
  .head-title > .tk-no { margin-right: 0.375rem; }
  .head-sub { font-size: 0.6875rem; }
  .head-count b { font-size: 0.78125rem; }
  /* Reply / Reply all / Forward do not belong in a phone mail header, and here
     they were squeezing the subject into three lines. */
  .mail-actions { display: none; }

  .aside-line { font-size: 0.75rem; padding-left: 0.5625rem; }

  /* Each surface's own identity, kept on screen at phone size. */
  /* The invitation card says who invited you and when; the pane header above
     it said the same thing again, in a window that has 18.75rem to spend. */
  .app--calendar .app-pane > .head-cal { display: none; }

  /* Three hours of week, pinned, and the invitation gets the rest. */
  .cal-wrap > .cal { flex: 0 0 auto; margin: 0.5rem 0.6875rem 0; min-height: 6.5rem; max-height: 8.25rem; }
  .cal-wrap > .cal-scroll { flex: 1 1 auto; min-height: 0; }
  .cal-scroll { padding-top: 0.625rem; }
  .cal-day { padding: 0.1875rem 0.125rem; font-size: 0.625rem; }
  .cal-block { font-size: 0.59375rem; padding: 0.0625rem 0.25rem; }
  .cal-block-time { display: none; }
  .cal-time { font-size: 0.53125rem; }
  /* Every field is still here and still says what it says; the table takes
     about three rows and scrolls inside itself, so the activity log below it
     is not squeezed to nothing. */
  /* The service desk is the one app carrying a second block of pinned chrome
     above the conversation. Its field table is what gives way, not the reply
     zone's ceiling: head + 4rem of fields + the scroller's own padding floor
     still leaves room for 76%, where 5.25rem of fields did not and the pane
     overflowed by a dozen pixels. */
  /* Its ceiling comes down too: the ticket's two-line header plus its fields
     left the activity log 2.75rem, which is less than one update. */
  .app--desk .reply { max-height: 70%; }

  .app--desk .app-pane > .tk-fields {
    margin: 0.5rem 0.6875rem 0.5625rem;
    /* A ceiling in whole rows, not a round number of pixels. `max-height: 4rem`
       cut the third row (REQUESTER) horizontally through its glyphs and left
       half of it sitting on the card's own border — the same slice as the seam
       below, twice on one screen. Measured in the browser at 390×844 and
       360×640, a row of this block's own `dt`/`dd` (0.25rem+0.25rem padding, 0.75rem line,
       0.0625rem rule) is exactly 1.6875rem, and the card adds 0.0625rem of border top and
       bottom. 2 × 27 + 2 = 56, so two whole rows show and the third begins
       below the edge rather than across it. The rest still scroll, as before,
       and the ceiling is lower than the 4rem it replaces, so the activity log
       gains 0.5rem rather than losing any. */
    max-height: 3.5rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;
  }
  .app--desk .app-pane > .tk-fields::-webkit-scrollbar { display: none; }
  .tk-fields dt { padding: 0.25rem 0.5rem; font-size: 0.5625rem; }
  .tk-fields dd { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
  /* The redacted body copy and the formatting toolbar are decoration; the
     passage under discussion is not. The page shrinks to that passage and
     stays put above the thread, which is what a phone shows when you open a
     comment. */
  .doc-lines,
  .doc-toolbar,
  .doc-h { display: none; }
  .doc-scroll { padding: 0.625rem 0.6875rem 0.875rem; }
  /* flex:0 0 auto, or the page grows to fill the column and a sticky element
     taller than its scrollport cannot stick. */
  .doc-page { flex: 0 0 auto; position: sticky; top: 0; z-index: 1; padding: 0.625rem 0.75rem 0.75rem; gap: 0.375rem; }
  .doc-para { font-size: 0.8125rem; line-height: 1.45; }
  .doc-layout { gap: 0.625rem; }

  /* The reply zone on a phone. It is still the app's composer, it still shows
     the question at the top of it, and it still takes at most 62% of the pane —
     which on a 390×844 phone leaves the conversation ~16.875rem, roughly four
     messages, rather than the sliver the old dock left. */
  /* A phone gets a bigger ceiling, because one column of six answers is simply
     taller than three columns of six answers, and an option nobody scrolls to
     is an option nobody picks. It is a CEILING, not a height: the zone still
     takes only what its six options need, and everything left over is
     conversation — which on most scenarios here is 18.75rem and change. */
  .reply { gap: 0.3125rem; padding: 0.5rem 0.6875rem 0.625rem; max-height: 76%; }
  .ask-stakes { padding: 0.125rem 0.4375rem; font-size: 0.59375rem; letter-spacing: 0.05em; }
  .ask-q { font-size: 1.32rem; }

  .reply-wait { min-height: 2.125rem; }
  .answers { gap: 0.25rem; }
  .answer-btn { padding: 0.3125rem 0.5625rem; gap: 0.4375rem; }
  .ans-key { width: 1.1875rem; height: 1.1875rem; font-size: 0.65625rem; }
  /* The act and the message run together as one paragraph here. The body's own
     type has to be set with them: left at the inherited 1rem/1.5 its invisible
     strut made every line box 1.5rem tall — six options' worth of line spacing
     for text drawn at 0.84375rem, which is where the conversation's height had
     gone. */
  .ans-body { font-size: 0.8125rem; line-height: 1.35; }
  .ans-act { display: inline; margin-right: 0.3125rem; font-size: 0.78125rem; }
  .ans-text { display: inline; margin-top: 0; font-size: 0.8125rem; line-height: 1.35; }
  /* The destination lives here on a phone, on its own line under the message,
     where it reads as metadata. The full routing is in the accessible name, so
     ellipsising the visible line loses nothing. */
  .ans-route {
    display: block;
    max-width: 100%;
    margin-top: 0.1875rem;
    font-size: 0.625rem;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .answer-btn--deed .ans-act { font-size: 0.8125rem; }
  .caption-line { font-size: 0.84375rem; padding: 0.5rem 0.6875rem; }
}

/* THE SIX FIELDS, ON A PHONE THAT HAS THE ROOM FOR THEM.
   -----------------------------------------------------
   The ceiling above is two whole rows, and it was doing more damage than the
   height it saved. Measured at 390×844 — the phone this suite plays on — the
   field card was 3.375rem of box against 10.0625rem of content with `scrollbar-width:
   none`, so Status and Priority were on screen and Requester, Assignee, Opened
   and Deadline were outside it with NOTHING to say they existed. A hidden
   scrollbar on a box that is a third of its content is not a scroller, it is a
   crop. Deadline is the row this branch renamed out of "SLA" so it could be
   read at all, and it was the last one down.

   The ceiling exists to stop six rows of metadata eating the activity log, and
   on a tall phone that trade is simply not being made: measured across all
   five shipped it_ticket scenarios at 390×844, showing every row costs the log
   6.6875rem and leaves it 289–21.3125rem — six to eight updates, where the fear was 44.
   Below that the ceiling stays, and the reorder in js/render/misc.js is what
   makes it survivable: Status and Deadline are the two rows under it now.

   Keyed on height, not width, because height is what the log is short of. The
   threshold is the shortest viewport measured to keep a usable log with all
   six rows shown; a 375×667 phone stays capped, and the shipped 360×640 case
   (4.3125rem of log with all six) is exactly what it is protecting against. */
@media (max-width: 559px) and (min-height: 740px) {
  .app--desk .app-pane > .tk-fields { max-height: none; }
}

@media (max-height: 700px) {
  .lock-time { font-size: 2.125rem; }
  .lock-notifs li:nth-child(n + 3) { display: none; }
}

@media (max-height: 600px) {
  .lock-notifs { display: none; }
}

/* --------------------------------------------------------------------------
   Motion preferences
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  /* The typing indicator carries information ("Priya is composing something"),
     so it stays — it just stops bouncing. The delays around it are timing, not
     motion, so every beat lands after exactly the same pause. */
  .typing span { animation: none !important; opacity: 0.55; transform: none; }
}


/* ==========================================================================
   Results v2 — the assessment file (16personalities-style, owner-approved
   mock 2026-08-20). Hero with the persona large, four trait bars, and the
   report as TAB PAGES — each section its own page, nothing scroll-spied.
   Axis colours are fixed (the results screen is always on ink):
   volume blue, compliance coral, method violet, labour green — validated
   against the dark surface with the dataviz palette checks.
   ========================================================================== */
:root {
  --ax-vol: #5b93e0; --ax-com: #e06a58; --ax-met: #a179d9; --ax-lab: #3aa981;
  --ax-vol-bg: #1c2836; --ax-com-bg: #33211d; --ax-met-bg: #2a2336; --ax-lab-bg: #1a2e27;
}

/* The cover sheet. Colour comes from the compliance x labour quadrant —
   the figurine's own family — as tokens on the results screen, so the band,
   the tab underline and the pull-quote bar all agree. */
#screen-results { --band-hi: #223048; --band-lo: #141c2b; --band-ac: #9fb4d4; }
#screen-results.rv-fam-ag { --band-hi: #1d4c37; --band-lo: #0f2a1e; --band-ac: #6fc39a; }
#screen-results.rv-fam-ad { --band-hi: #382a6d; --band-lo: #1d1640; --band-ac: #a78fe0; }
#screen-results.rv-fam-rg { --band-hi: #69301c; --band-lo: #38170d; --band-ac: #f2947a; }
#screen-results.rv-fam-rd { --band-hi: #1d466f; --band-lo: #0f2740; --band-ac: #7fb1de; }

.rv-band { flex-shrink: 0; padding: 1.75rem 1.5rem 2.75rem; position: relative;
  background: linear-gradient(150deg, var(--band-hi), var(--band-lo));
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 1.375rem), 0 100%); }
.rv-band-in { max-width: 66.25rem; margin: 0 auto; display: grid; grid-template-columns: 1fr;
  gap: 0.75rem; align-items: center; }
@media (min-width: 880px) {
  .rv-band { padding: 2.75rem 2rem 3.75rem; }
  .rv-band-in { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 2.75rem; }
  .rv-band .rv-art { order: 2; }
}
.rv-art { display: flex; justify-content: center; }
.rv-art img { width: min(24rem, 82vw); height: auto;
  filter: drop-shadow(0 1.5rem 2rem rgba(0,0,0,.45)); }
.rv-id .eyebrow { font-family: var(--mono, ui-monospace); font-size: 0.6875rem;
  letter-spacing: .18em; text-transform: uppercase; color: rgba(255,255,255,.62); margin: 0 0 1rem; }
.rv-poles { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0 0 0.875rem; }
.rv-pole { display: inline-flex; align-items: center; gap: 0.4375rem; font-size: 0.71875rem;
  font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: #fff;
  padding: 0.3125rem 0.8125rem; border-radius: var(--r-pill); background: rgba(255,255,255,.13); }
.rv-pole::before { content: ""; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: currentColor; }
.rv-pole.is-default { opacity: .6; }
.rv-pole.is-default i { font-style: normal; font-weight: 400; letter-spacing: .06em;
  text-transform: none; opacity: .8; }
.rv-pole.is-default i::before { content: "· "; }
.rv-pole.ax-volume::before { background: var(--ax-vol); }
.rv-pole.ax-compliance::before { background: var(--ax-com); }
.rv-pole.ax-method::before { background: var(--ax-met); }
.rv-pole.ax-labour::before { background: var(--ax-lab); }
.rv-name { font-family: var(--display); font-weight: 400; font-stretch: condensed;
  text-transform: uppercase; font-size: clamp(2.625rem, 6.4vw, 4.5rem); line-height: .92;
  letter-spacing: .01em; color: #fff; margin: 0 0 0.5rem; text-wrap: balance; }
.rv-code { font-family: var(--mono); font-weight: 700; font-size: 1.125rem;
  letter-spacing: .3em; line-height: 1; margin: 0 0 0.875rem; }
.rv-code .l1 { color: var(--ax-vol); } .rv-code .l2 { color: var(--ax-com); }
.rv-code .l3 { color: var(--ax-met); } .rv-code .l4 { color: var(--ax-lab); }
.rv-oneliner { font-size: 1.1875rem; line-height: 1.55; color: rgba(255,255,255,.88);
  max-width: 50ch; margin: 0 0 1rem; }
.rv-chips { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0 0 0.875rem; }
.rv-chip { font-family: var(--mono); font-size: 0.71875rem; padding: 0.3125rem 0.6875rem;
  border-radius: var(--r-pill); background: rgba(0,0,0,.25); color: rgba(255,255,255,.68); }
.rv-chip b { color: #fff; }
.rv-rarity { font-size: 0.9375rem; color: rgba(255,255,255,.72); margin: 0 0 1.125rem; }
.rv-rarity b { color: #fff; font-variant-numeric: tabular-nums; }
.rv-band .btn-secondary { background: rgba(255,255,255,.1); border-color: rgba(255,255,255,.3); color: #fff; }
.rv-band .btn-secondary:hover { border-color: rgba(255,255,255,.6); }

.rv-tabs { position: sticky; top: 0; z-index: 9; display: flex; gap: 0.125rem; overflow-x: auto;
  background: var(--ink); border-bottom: 0.125rem solid var(--ink-line); padding-top: 0.375rem;
  scrollbar-width: thin; margin-top: 1rem; }
.rv-tabs button { flex: 0 0 auto; font: inherit; font-size: 0.90625rem; font-weight: 600;
  padding: 0.6875rem 0.8125rem 0.8125rem; color: var(--on-ink-2); background: none; border: 0;
  border-bottom: 0.1875rem solid transparent; margin-bottom: -0.125rem; white-space: nowrap; cursor: pointer; }
.rv-tabs button:hover { color: var(--on-ink); }
.rv-tabs button[aria-selected="true"] { color: var(--on-ink); border-bottom-color: var(--band-ac); }
.rv-tabs button:focus-visible { outline: 0.125rem solid var(--ax-vol); outline-offset: -0.125rem; }

#report { max-width: 51.25rem; }
.rv-page { display: none; padding: 1.5rem 0 2.125rem; }
.rv-page.on { display: block; }
.rv-page h3 { font-size: clamp(1.625rem, 3vw, 2rem); margin: 0 0 0.75rem; letter-spacing: -.015em; }
/* The measurements that produced a page, as chips rather than a data string. */
.rv-kchips { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0 0 1.25rem; }
.rv-kchip { font-size: 0.78125rem; font-weight: 700; padding: 0.375rem 0.875rem;
  border-radius: var(--r-pill); background: var(--ink-2); border: 0.0625rem solid var(--ink-line);
  font-variant-numeric: tabular-nums; }
.rv-kchip.ax-volume { color: var(--ax-vol); } .rv-kchip.ax-compliance { color: var(--ax-com); }
.rv-kchip.ax-method { color: var(--ax-met); } .rv-kchip.ax-labour { color: var(--ax-lab); }
/* Prose is for reading: full-strength ink, book-ish measure and leading.
   The mono face is the OS's voice, not the report's — it stays in chrome. */
.rv-page p.rv-para { max-width: 62ch; font-size: 1.125rem; line-height: 1.7;
  color: var(--on-ink); margin: 0 0 1.125rem; }
.rv-page p.rv-para strong { color: #fff; }
.rv-sub { font-size: 1.25rem; margin: 1.75rem 0 0.625rem; letter-spacing: -.01em; }
.rv-page .rv-sub:first-of-type { margin-top: 0.5rem; }
.rv-quote { margin: 0.25rem 0 1.375rem; padding: 1.125rem 1.375rem; max-width: 56ch;
  background: var(--ink-2); border-left: 0.25rem solid var(--band-ac);
  border-radius: 0 0.75rem 0.75rem 0; font-size: 1.1875rem; line-height: 1.6; color: var(--on-ink); }

/* trait bars */
.rv-trait { padding: 0.875rem 0; border-top: 0.0625rem solid var(--ink-line); }
.rv-trait:first-of-type { border-top: 0; }
.rv-t-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 0.5625rem; }
.rv-t-name { font-family: ui-monospace, Menlo, monospace; font-size: 0.6875rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--on-ink-2); }
.rv-t-name em { font-style: normal; text-transform: none; letter-spacing: 0;
  font-family: var(--ui); font-size: 0.875rem; color: var(--on-ink-2); margin-left: 0.5rem; }
.rv-t-score { font-size: 1.0625rem; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rv-t-bar { position: relative; height: 0.75rem; border-radius: 6.1875rem; background: var(--ink-2); }
.rv-t-bar::after { content: ""; position: absolute; left: 50%; top: -0.1875rem; bottom: -0.1875rem;
  width: 0.125rem; background: var(--ink-line); }
.rv-t-fill { position: absolute; top: 0; bottom: 0; border-radius: 6.1875rem; }
.rv-t-dot { position: absolute; top: 50%; width: 1.375rem; height: 1.375rem; border-radius: 50%;
  transform: translate(-50%, -50%); border: 0.25rem solid var(--ink); box-shadow: 0 0.0625rem 0.25rem rgba(0,0,0,.4); }
.rv-t-poles { display: flex; justify-content: space-between; margin-top: 0.5rem; font-size: 0.78125rem;
  color: var(--on-ink-2); }
/* A dial the day never tested: no fill, no ball, and the score slot says so. */
.rv-trait.is-unmeasured .rv-t-score { font-size: 0.875rem; font-weight: 600; color: var(--on-ink-2); }
.rv-trait.is-unmeasured .rv-t-bar { opacity: .45; }
.rv-t-note { margin: 0.4375rem 0 0; font-size: 0.78125rem; color: var(--on-ink-2); }
.rv-m-stance { margin: 0.25rem 0 0.625rem; font-size: 0.84375rem; color: var(--on-ink-2); }
.rv-t-poles .won { font-weight: 700; }
.rv-ax-volume .rv-t-score, .rv-ax-volume .won { color: var(--ax-vol); }
.rv-ax-volume .rv-t-fill { background: var(--ax-vol-bg); } .rv-ax-volume .rv-t-dot { background: var(--ax-vol); }
.rv-ax-compliance .rv-t-score, .rv-ax-compliance .won { color: var(--ax-com); }
.rv-ax-compliance .rv-t-fill { background: var(--ax-com-bg); } .rv-ax-compliance .rv-t-dot { background: var(--ax-com); }
.rv-ax-method .rv-t-score, .rv-ax-method .won { color: var(--ax-met); }
.rv-ax-method .rv-t-fill { background: var(--ax-met-bg); } .rv-ax-method .rv-t-dot { background: var(--ax-met); }
.rv-ax-labour .rv-t-score, .rv-ax-labour .won { color: var(--ax-lab); }
.rv-ax-labour .rv-t-fill { background: var(--ax-lab-bg); } .rv-ax-labour .rv-t-dot { background: var(--ax-lab); }

/* strengths / blind spots */
.rv-duo { display: grid; grid-template-columns: 1fr; gap: 0.875rem; max-width: 47.5rem; }
@media (min-width: 700px) { .rv-duo { grid-template-columns: 1fr 1fr; } }
.rv-panel { background: var(--ink-2); border: 0.0625rem solid var(--ink-line); border-radius: 0.75rem;
  padding: 1rem 1.125rem; }
.rv-panel h4 { margin: 0 0 0.625rem; font-size: 0.875rem; }
.rv-panel ul { margin: 0; padding-left: 1.125rem; font-size: 0.90625rem; color: var(--on-ink-2); }
.rv-panel li { margin-bottom: 0.5rem; }

/* moments */
.rv-moment { background: var(--ink-2); border: 0.0625rem solid var(--ink-line); border-radius: 0.75rem;
  padding: 0.9375rem 1.0625rem; margin-bottom: 0.75rem; max-width: 47.5rem; }
.rv-m-top { display: flex; gap: 0.75rem; align-items: baseline; margin-bottom: 0.4375rem; flex-wrap: wrap; }
.rv-m-clock { font-family: ui-monospace, Menlo, monospace; font-size: 0.75rem; color: var(--on-ink-2);
  font-variant-numeric: tabular-nums; }
.rv-m-title { font-size: 0.8125rem; color: var(--on-ink-2); }
.rv-m-quote { margin: 0 0 0.625rem; font-size: 0.96875rem; font-style: italic; color: var(--on-ink); }
.rv-m-deed { font-style: normal; }
.rv-m-chips { display: flex; gap: 0.4375rem; flex-wrap: wrap; }
.rv-push { font-family: ui-monospace, Menlo, monospace; font-size: 0.6875rem; padding: 0.1875rem 0.5625rem;
  border-radius: 0.375rem; background: var(--ink); border: 0.0625rem solid var(--ink-line); }
.rv-push.ax-volume { color: var(--ax-vol); } .rv-push.ax-compliance { color: var(--ax-com); }
.rv-push.ax-method { color: var(--ax-met); } .rv-push.ax-labour { color: var(--ax-lab); }
.rv-push.rv-away { opacity: .55; }

/* personnel file */
.rv-pfile { max-width: 45rem; background: var(--ink-2); border: 0.0625rem solid var(--ink-line);
  border-radius: 0.75rem; padding: 1.125rem 1.25rem; }
.rv-pf-row { display: grid; grid-template-columns: 10rem 1fr 2.5rem; gap: 0.75rem; align-items: center;
  padding: 0.4375rem 0; }
@media (max-width: 560px) { .rv-pf-row { grid-template-columns: 7.375rem 1fr 2.25rem; } }
.rv-pf-name { font-size: 0.84375rem; color: var(--on-ink-2); }
.rv-pf-track { height: 0.5625rem; border-radius: 6.1875rem; background: var(--ink); position: relative; }
.rv-pf-fill { position: absolute; inset: 0 auto 0 0; border-radius: 6.1875rem; background: #7e8ea0; }
.rv-pf-val { font-family: ui-monospace, Menlo, monospace; font-size: 0.8125rem; text-align: right;
  font-variant-numeric: tabular-nums; }
.rv-pf-note { margin: 0.875rem 0 0; font-size: 0.78125rem; color: var(--on-ink-2); }

/* rarity */
.rv-rar { display: flex; gap: 1.625rem; align-items: center; flex-wrap: wrap; max-width: 45rem; }
.rv-rar-num { font-size: 3.625rem; font-weight: 700; letter-spacing: -.03em; line-height: 1;
  font-variant-numeric: tabular-nums; }
.rv-rar-cap { font-size: 0.9375rem; color: var(--on-ink-2); max-width: 32ch; }
.rv-dist { display: flex; gap: 0.1875rem; align-items: flex-end; height: 4.375rem; margin-top: 1.125rem; max-width: 45rem; }
.rv-dist span { flex: 1; background: var(--ink-line); border-radius: 0.1875rem 0.1875rem 0 0; position: relative; }
.rv-dist span.you { background: var(--ax-com); }
.rv-dist span.you::after { content: "you"; position: absolute; top: -1.25rem; left: 50%;
  transform: translateX(-50%); font-family: ui-monospace, Menlo, monospace; font-size: 0.625rem;
  color: var(--ax-com); }
.rv-dist-cap { font-size: 0.75rem; color: var(--on-ink-2); margin-top: 0.5rem; }
.code-grid-v2 { margin-top: 1.375rem; }

@media (max-width: 879px) {
  .rv-band { padding: 1rem 1.125rem 2rem; }
  .rv-band .rv-art { order: -1; }
  /* The share row is the growth engine and stays on the first screen: the
     portrait yields height on phones rather than pushing it under the fold. */
  .rv-art img { width: auto; max-width: 78vw; max-height: 30vh; }
  .rv-id .eyebrow { display: none; }
  .rv-name { font-size: clamp(2rem, 9vw, 2.625rem); }
  .rv-oneliner { font-size: 1rem; margin-bottom: 0.75rem; }
  .rv-chips { display: none; }
  .rv-rarity { font-size: 0.875rem; margin-bottom: 0.875rem; }
  .rv-page p.rv-para { font-size: 1.0625rem; }
}

/* The quiet twin of .btn-primary, for the results share row. */
.btn-secondary {
  font: inherit; font-size: 0.9375rem; font-weight: 600; cursor: pointer;
  padding: 0.625rem 1.125rem; border-radius: 0.625rem;
  background: var(--ink-2); color: var(--on-ink);
  border: 0.0625rem solid var(--ink-line);
}
.btn-secondary:hover { border-color: var(--on-ink-2); }
.btn-secondary:focus-visible { outline: 0.125rem solid var(--ax-vol); outline-offset: 0.125rem; }
#share-row { display: flex; gap: 0.625rem; flex-wrap: wrap; align-items: center; }


/* ==========================================================================
   Landing v3 — the locked laptop and the trophy cabinet (owner ask,
   2026-08-20: "completely nuke and rebuild"). One signature element: the
   sixteen real figurines drifting across a lit shelf. Everything else stays
   quiet — the game's own ink, amber, condensed display and mono.
   ========================================================================== */
.ld-wrap { width: 100%; max-width: 66.25rem; margin: 0 auto; padding: 0.75rem 1.5rem 2.5rem;
  display: flex; flex-direction: column; min-height: 100dvh; }
.ld-wrap > * { flex-shrink: 0; }

.ld-lock { display: flex; justify-content: space-between; align-items: flex-start; gap: 1.125rem;
  padding-bottom: 0.625rem; border-bottom: 0.0625rem solid var(--ink-line); }
.ld-when { display: flex; flex-direction: column; gap: 0.125rem; }
.ld-time { font-family: var(--mono); font-size: 2.5rem; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums; color: #f2f5f9; }
.ld-date { font-family: var(--mono); font-size: 0.71875rem; color: var(--on-ink-2); }
.ld-notifs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: row;
  flex-wrap: wrap; justify-content: flex-end; gap: 0.3125rem; align-items: center; }
.ld-notifs li { font-family: var(--mono); font-size: 0.6875rem; color: var(--on-ink-2);
  background: var(--ink-2); border: 0.0625rem solid var(--ink-line); border-radius: 6.1875rem;
  padding: 0.25rem 0.6875rem; white-space: nowrap; }
.ld-notifs b { color: var(--on-ink); font-weight: 600; }

.ld-hero { text-align: center; margin: 0.75rem auto 0; max-width: 74ch; }
.ld-hero .eyebrow { font-family: var(--mono); font-size: 0.6875rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--on-ink-2); margin: 0 0 0.375rem; }
.ld-hero h1 { font-family: var(--display); font-weight: 400; font-stretch: condensed;
  font-size: clamp(3rem, 8.5vw, 4.75rem); line-height: .84; letter-spacing: .01em;
  text-transform: uppercase; color: #f6f8fc; margin: 0 0 0.625rem; }
.ld-hero .wm-accent { color: var(--hivis); }
.ld-hero .tagline { margin: 0 0 0.25rem; font-size: 0.9375rem; color: var(--on-ink-2); }
.ld-hero .purpose { margin: 0.25rem auto 0.75rem; font-size: 1.4375rem; font-weight: 650;
  color: var(--on-ink); max-width: 40ch; text-wrap: balance; }
.ld-hero .purpose-body { margin: 0 auto; font-size: 0.9375rem; line-height: 1.55; color: var(--on-ink-2); max-width: 62ch; }

/* ---- the trophy cabinet ---- */
.ld-shelf { position: relative; margin: 1rem -1.5rem 0; overflow: hidden; flex-shrink: 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent); }
.ld-track { display: flex; align-items: flex-end; gap: 2.625rem; width: max-content;
  padding: 0.125rem 1.3125rem 0; animation: ld-drift 70s linear infinite; }
.ld-shelf:hover .ld-track, .ld-shelf:focus-within .ld-track { animation-play-state: paused; }
@keyframes ld-drift { to { transform: translateX(-50%); } }
.ld-item { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 0.4375rem; }
.ld-item img { height: 7.25rem; width: auto; display: block;
  filter: drop-shadow(0 0.625rem 0.875rem rgba(0,0,0,.45)); }
.ld-item figcaption { font-family: var(--mono); font-size: 0.59375rem; letter-spacing: .08em;
  color: var(--on-ink-2); text-align: center; display: flex; flex-direction: row;
  align-items: baseline; gap: 0.375rem; }
.ld-item figcaption b { color: var(--on-ink); letter-spacing: .3em; font-size: 0.65625rem; }
.ld-ledge { position: absolute; left: 0; right: 0; bottom: 1.625rem; height: 0.125rem;
  background: linear-gradient(90deg, transparent, var(--ink-line) 12%, var(--ink-line) 88%, transparent); }
.ld-shelf-cap { font-family: var(--mono); font-size: 0.6875rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--on-ink-2); margin: 0.25rem 0 0; text-align: center; }
@media (prefers-reduced-motion: reduce) {
  .ld-track { animation: none; }
  .ld-shelf { overflow-x: auto; mask-image: none; -webkit-mask-image: none; }
}

.ld-cta { display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
  margin: 1rem auto 1rem; }
.ld-cta .btn-primary { font-size: 1.1875rem; padding: 0.875rem 2.875rem; width: auto; min-width: 21.25rem; }
.ld-facts { font-family: var(--mono); font-size: 0.6875rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--on-ink-2); margin: 0; }

/* ---- below the fold: the numbers, the framework, the reviews ---- */
.ld-stats { list-style: none; margin: 3rem 0 0; padding: 0; display: grid;
  grid-template-columns: repeat(4, 1fr); gap: 0.875rem; }
.ld-stat { border-radius: 0.875rem; padding: 1.125rem 1.125rem 1rem; border: 0.0625rem solid var(--ink-line); }
.ld-stat b { display: block; font-family: var(--display); font-weight: 400; font-stretch: condensed;
  font-size: 2.625rem; line-height: 1; margin-bottom: 0.375rem; }
.ld-stat span { font-size: 0.84375rem; line-height: 1.4; color: var(--on-ink-2); display: block; }
.ld-stat--vol { background: var(--ax-vol-bg); } .ld-stat--vol b { color: var(--ax-vol); }
.ld-stat--com { background: var(--ax-com-bg); } .ld-stat--com b { color: var(--ax-com); }
.ld-stat--met { background: var(--ax-met-bg); } .ld-stat--met b { color: var(--ax-met); }
.ld-stat--lab { background: var(--ax-lab-bg); } .ld-stat--lab b { color: var(--ax-lab); }

.ld-pill { display: inline-block; font-family: var(--mono); font-size: 0.6875rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--hivis);
  padding: 0.3125rem 0.875rem; border-radius: var(--r-pill); background: var(--ink-2);
  border: 0.0625rem solid var(--ink-line); margin-bottom: 0.875rem; }
.ld-sec { display: grid; grid-template-columns: 1fr 1fr; gap: 3rem; align-items: center;
  margin-top: 4.5rem; }
.ld-sec h2, .ld-quotes h2, .ld-final h2 { font-size: clamp(1.75rem, 3.2vw, 2.375rem);
  line-height: 1.12; letter-spacing: -.015em; margin: 0 0 0.75rem; text-wrap: balance; }
.ld-sec-copy > p { margin: 0; font-size: 1.0625rem; line-height: 1.65; color: var(--on-ink-2); }
.ld-axes { list-style: none; margin: 1.375rem 0 0; padding: 0; }
.ld-axes li { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  border-top: 0.0625rem solid var(--ink-line); padding: 0.625rem 0; font-size: 0.9375rem; }
.ld-axes b { font-weight: 700; white-space: nowrap; }
.ld-axes span { color: var(--on-ink-2); text-align: right; }
.ax-volume { color: var(--ax-vol); } .ax-compliance { color: var(--ax-com); }
.ax-method { color: var(--ax-met); } .ax-labour { color: var(--ax-lab); }

.ld-dials { background: var(--ink-2); border: 0.0625rem solid var(--ink-line);
  border-radius: 1rem; padding: 1.25rem 1.375rem 1.375rem; }
.ld-dials-cap { font-family: var(--mono); font-size: 0.6875rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--on-ink-2); margin: 0 0 1rem; }
.ld-dial { display: grid; grid-template-columns: 1fr 3rem; grid-template-rows: auto auto;
  column-gap: 0.75rem; row-gap: 0.4375rem; align-items: center; padding: 0.5625rem 0; }
.ld-d-poles { grid-column: 1 / -1; display: flex; justify-content: space-between;
  font-size: 0.78125rem; color: var(--on-ink-2); }
.ld-d-poles .won { font-weight: 700; color: currentColor; }
.ld-dial.ax-volume .won { color: var(--ax-vol); } .ld-dial.ax-compliance .won { color: var(--ax-com); }
.ld-dial.ax-method .won { color: var(--ax-met); } .ld-dial.ax-labour .won { color: var(--ax-lab); }
.ld-d-bar { position: relative; height: 0.5625rem; border-radius: var(--r-pill); background: var(--ink); display: block; }
.ld-d-fill { position: absolute; top: 0; bottom: 0; border-radius: var(--r-pill); display: block; }
.ld-dial.ax-volume .ld-d-fill { background: var(--ax-vol-bg); border: 0.0625rem solid var(--ax-vol); }
.ld-dial.ax-compliance .ld-d-fill { background: var(--ax-com-bg); border: 0.0625rem solid var(--ax-com); }
.ld-dial.ax-method .ld-d-fill { background: var(--ax-met-bg); border: 0.0625rem solid var(--ax-met); }
.ld-dial.ax-labour .ld-d-fill { background: var(--ax-lab-bg); border: 0.0625rem solid var(--ax-lab); }
.ld-d-dot { position: absolute; top: 50%; width: 1rem; height: 1rem; border-radius: 50%;
  transform: translate(-50%, -50%); border: 0.1875rem solid var(--ink-2); display: block; }
.ld-dial.ax-volume .ld-d-dot { background: var(--ax-vol); } .ld-dial.ax-compliance .ld-d-dot { background: var(--ax-com); }
.ld-dial.ax-method .ld-d-dot { background: var(--ax-met); } .ld-dial.ax-labour .ld-d-dot { background: var(--ax-lab); }
.ld-d-pct { font-weight: 700; font-variant-numeric: tabular-nums; font-size: 0.9375rem; text-align: right; }
.ld-dial.ax-volume .ld-d-pct { color: var(--ax-vol); } .ld-dial.ax-compliance .ld-d-pct { color: var(--ax-com); }
.ld-dial.ax-method .ld-d-pct { color: var(--ax-met); } .ld-dial.ax-labour .ld-d-pct { color: var(--ax-lab); }

.ld-quotes { margin-top: 4.5rem; text-align: center; }
.ld-quotes-sub { margin: 0; color: var(--on-ink-2); font-size: 1rem; }
.ld-qgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.875rem; margin-top: 1.5rem; }
.ld-quote { margin: 0; text-align: left; background: var(--ink-2); border: 0.0625rem solid var(--ink-line);
  border-radius: 0.875rem; padding: 1.125rem 1.25rem; display: flex; flex-direction: column; gap: 0.75rem; }
.ld-quote blockquote { margin: 0; font-size: 0.96875rem; line-height: 1.6; color: var(--on-ink); }
.ld-quote figcaption { font-size: 0.8125rem; color: var(--on-ink-2); }
.ld-quote figcaption b { color: var(--on-ink); }

.ld-final { margin-top: 4.5rem; padding-top: 2.75rem; border-top: 0.0625rem solid var(--ink-line);
  text-align: center; display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.ld-final p { margin: 0 0 1.125rem; color: var(--on-ink-2); font-size: 1.0625rem; }
.ld-final .btn-primary { font-size: 1.1875rem; padding: 0.875rem 2.875rem; width: auto; min-width: 21.25rem; }

@media (max-width: 860px) {
  .ld-stats { grid-template-columns: 1fr 1fr; margin-top: 2.25rem; }
  .ld-sec { grid-template-columns: 1fr; gap: 1.625rem; margin-top: 3rem; }
  .ld-qgrid { grid-template-columns: 1fr; }
  .ld-quotes, .ld-final { margin-top: 3rem; }
  .ld-final .btn-primary { width: 100%; min-width: 0; }
}

#screen-landing .who-you-are { margin: 1.375rem auto 0; max-width: 58ch; text-align: center;
  font-size: 0.84375rem; color: var(--on-ink-2); border: 0; padding: 0; }
#screen-landing .fine-print { text-align: center; margin-left: auto; margin-right: auto; }
#screen-landing .legal-links { text-align: center; }
#screen-landing #replay-tutorial { align-self: center; margin-top: 0.5rem; }

@media (max-width: 640px) {
  .ld-wrap { padding: 1rem 1.125rem 1.875rem; }
  .ld-lock { flex-direction: column; gap: 0.5rem; }
  .ld-notifs { align-items: flex-start; flex-direction: row; flex-wrap: wrap; }
  .ld-hero { margin-top: 1.125rem; }
  .ld-item img { height: 7.625rem; }
  .ld-shelf { margin: 1rem -1.125rem 0; }
  .ld-cta .btn-primary { width: 100%; }
}
/* a phone on its side: keep the button reachable without scrolling forever */
@media (max-height: 480px) {
  .ld-item img { height: 5.75rem; }
  .ld-hero h1 { font-size: 2.4rem; }
  .ld-hero .purpose-body { display: none; }
}
