/* THE TOKENS COME FROM THE DESIGN SYSTEM (client/design-system.css, ember:task:0089). These names
 * are the shell's existing vocabulary and they stay, but each one now RESOLVES to the system's
 * token rather than carrying a second copy of the value — a palette maintained in two files is a
 * palette that disagrees with itself the first time either one moves, and prefers-contrast would
 * have brightened one of them and not the other.
 *
 * The ownership triple and the ember hues are the exception the visual language names: `--you`,
 * `--them`, `--neutral` and `--progress` are the ONLY place those hues appear, so they are stated
 * here rather than aliased through a generic accent. */
:root {
  --black: #090a0c;
  --panel: var(--ds-surface);
  --panel-strong: var(--ds-surface-strong);
  --line: var(--ds-line);
  --line-hot: var(--ds-line-hot);
  --ink: var(--ds-ink);
  --muted: var(--ds-muted);
  --faint: var(--ds-faint);
  --ember: var(--ds-accent);
  --ember-soft: var(--ds-accent-soft);
  --ember-pale: #ffd0b0;
  --you: #ff7a3d;
  --them: #c44c78;
  --neutral: #aaa9a2;
  --progress: var(--ds-warn);
  --safe-top: var(--ds-safe-top);
  --safe-right: var(--ds-safe-right);
  --safe-bottom: var(--ds-safe-bottom);
  --safe-left: var(--ds-safe-left);
}

* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
body {
  color: var(--ink);
  background: var(--black);
  font: 500 14px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  overscroll-behavior: none;
}
button, a { font: inherit; }
button { -webkit-tap-highlight-color: transparent; }

#game { position: fixed; inset: 0; isolation: isolate; background: #11100e; }
/* ember:task:0054 — the streamed planet, underneath the layer that draws what stands on it. */
#ground {
  position: absolute;
  inset: 0;
  z-index: -5;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}
#stage {
  position: absolute;
  inset: 0;
  z-index: -4;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
  outline: none;
}
#stage:focus-visible { box-shadow: inset 0 0 0 2px var(--ember); }
.world-wash {
  position: absolute;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  /* DISTANCE IS HAZE, NOT A HOLE. Both washes faded to the same near-black, so ground far from the
     expedition did not recede into air - it fell down a dark tunnel, and a planet lit that way has
     no horizon and no scale. Far ground now cools toward a deep atmospheric blue before it darkens,
     which is what distance does to colour, and the near ring warms very slightly because the ember
     you are steering is the light source standing in it. The values stay heavy enough to hold the
     vignette's real job: keeping the eye off the edges and on the middle. */
  background:
    radial-gradient(circle at 52% 48%,
      rgba(255, 176, 106, .05) 0 14%,
      transparent 22%,
      rgba(22, 32, 46, .30) 52%,
      rgba(11, 15, 22, .74) 78%,
      rgba(7, 9, 13, .93) 100%),
    linear-gradient(105deg,
      rgba(9, 12, 18, .78) 0%, transparent 36%, transparent 68%, rgba(9, 12, 18, .72) 100%);
}
.noise {
  position: absolute;
  inset: 0;
  z-index: 10;
  opacity: .11;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
  mix-blend-mode: soft-light;
}

.topbar {
  position: absolute;
  z-index: 4;
  top: 0;
  left: 0;
  right: 0;
  min-height: 82px;
  padding: var(--safe-top) var(--safe-right) 14px var(--safe-left);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(to bottom, rgba(9, 10, 12, .94), rgba(9, 10, 12, .52) 72%, transparent);
}
.brand { display: inline-flex; align-items: center; gap: 12px; color: var(--ink); text-decoration: none; width: fit-content; }
.brand-mark { position: relative; width: 31px; height: 38px; display: grid; place-items: center; }
.brand-mark::before, .brand-mark::after, .brand-mark i {
  content: "";
  position: absolute;
  width: 16px;
  height: 24px;
  border: 1.5px solid var(--ember);
  transform: skew(-17deg) rotate(29deg);
}
.brand-mark::before { translate: -6px 5px; opacity: .45; }
.brand-mark::after { translate: 6px -5px; }
.brand-mark i { height: 12px; width: 8px; border-color: var(--ember-pale); }
.brand b { display: block; font: 750 18px/1 "Arial Narrow", Inter, sans-serif; letter-spacing: .23em; }
.brand small { display: block; margin-top: 5px; color: var(--faint); font: 650 8px/1 sans-serif; letter-spacing: .28em; }
.world-state { display: flex; align-items: center; gap: 10px; color: var(--muted); font: 650 10px/1 sans-serif; letter-spacing: .16em; }
/* CONNECTING / LOST reads as a problem, not a neutral place. There is no calm "solo world" to be
   left in any more — either you are live or the page is telling you it is trying to be. */
#connection.connection--warn { color: var(--progress); }
.signal, .live-label i { width: 6px; height: 6px; border-radius: 50%; background: var(--ember); box-shadow: 0 0 12px var(--ember); }
.divider { width: 1px; height: 13px; background: var(--line); }
/* The identity is a CONTROL now, not a readout: it opens the account surface. Styled to stay the
   quiet corner label it has always been, and sized to the hit floor because it is reached by a
   thumb on the layout where it matters most. */
.identity {
  justify-self: end; text-align: right; min-height: 44px; padding: 0 2px;
  border: 0; background: transparent; color: inherit; cursor: pointer;
}
.identity:hover strong, .identity:focus-visible strong { color: var(--ember-soft); }
.identity span, .identity strong { display: block; }
.identity-label { color: var(--faint); font: 650 8px/1 sans-serif; letter-spacing: .24em; }
.identity strong { margin-top: 6px; font: 650 11px/1 sans-serif; letter-spacing: .13em; }

.card { position: absolute; z-index: 3; border: 1px solid var(--line); background: var(--panel); backdrop-filter: blur(18px); }
.mission { top: 112px; left: var(--safe-left); width: min(330px, calc(100vw - 36px)); padding: 24px 24px 21px; }
.mission::before, .intel::before {
  content: "";
  position: absolute;
  top: -1px;
  left: -1px;
  width: 44px;
  height: 1px;
  background: var(--ember);
  box-shadow: 0 0 12px var(--ember);
}
.eyebrow { margin: 0 0 16px; display: flex; align-items: center; gap: 8px; color: var(--ember-soft); font: 700 9px/1 sans-serif; letter-spacing: .23em; }
.eyebrow span { width: 18px; height: 1px; background: currentColor; }
.mission h1 { margin: 0; font: 520 clamp(25px, 2.25vw, 34px)/1.08 Georgia, serif; letter-spacing: -.025em; }
.mission h1 em, .entry h2 em { color: var(--ember-soft); font-weight: 400; }
.mission > p:not(.eyebrow) { margin: 15px 0 22px; max-width: 29ch; color: var(--muted); font-size: 12px; line-height: 1.6; }
.mission-progress { display: flex; justify-content: space-between; color: var(--faint); font: 700 8px/1 sans-serif; letter-spacing: .16em; }
.mission-progress strong { color: var(--ink); font-size: 9px; }
.progress-track { height: 2px; margin-top: 10px; overflow: hidden; background: rgba(255,255,255,.1); }
.progress-track i { display: block; width: 18%; height: 100%; background: var(--ember); box-shadow: 0 0 10px var(--ember); transition: width .45s ease; }

.intel { top: 112px; right: var(--safe-right); width: 242px; }
.panel-heading { height: 50px; padding: 0 17px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); font: 700 9px/1 sans-serif; letter-spacing: .2em; }
.live-label { display: flex; align-items: center; gap: 7px; color: var(--faint); font-size: 8px; }
.live-label i { width: 5px; height: 5px; }
.intel dl { margin: 0; }
.intel dl > div { display: grid; grid-template-columns: 1fr auto; align-items: center; min-height: 72px; padding: 13px 17px; border-bottom: 1px solid var(--line); }
.intel dt { color: var(--faint); font: 700 8px/1.2 sans-serif; letter-spacing: .16em; }
.intel dd { margin: 0; text-align: right; }
.intel dd strong { display: block; font: 400 23px/1 Georgia, serif; }
.intel dd small { display: block; margin-top: 5px; max-width: 105px; color: var(--faint); font-size: 8px; }
.legend { padding: 15px 17px; display: grid; gap: 11px; color: var(--faint); font: 650 8px/1 sans-serif; letter-spacing: .1em; }
.legend span { display: flex; align-items: center; gap: 9px; }
.legend-dot { width: 6px; height: 6px; border-radius: 50%; }
.legend-ring { width: 9px; height: 9px; border: 1px solid; transform: rotate(45deg); }
.you { background: var(--you); color: var(--you); box-shadow: 0 0 7px var(--you); }
.hostile { background: var(--them); box-shadow: 0 0 7px var(--them); }
.neutral { color: var(--neutral); }

.entry {
  position: absolute;
  z-index: 5;
  top: 50%;
  left: 50%;
  width: min(520px, calc(100vw - 40px));
  padding: 38px 46px;
  translate: -50% -48%;
  text-align: center;
  background: radial-gradient(circle at 50% 100%, rgba(244,98,42,.12), transparent 50%);
}
.entry[hidden] { display: none; }
.entry .eyebrow { justify-content: center; }
.entry h2 { margin: 0; font: 400 clamp(42px, 5.4vw, 72px)/.95 Georgia, serif; letter-spacing: -.045em; text-shadow: 0 8px 40px #000; }
.entry > p:not(.eyebrow) { width: min(42ch, 100%); margin: 22px auto 26px; color: #b3afa7; font-size: 13px; line-height: 1.65; }
#enter {
  min-height: 54px;
  min-width: 226px;
  padding: 0 8px 0 22px;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  border: 1px solid var(--ember-soft);
  color: #140b06;
  background: var(--ember);
  cursor: pointer;
  font: 800 11px/1 sans-serif;
  letter-spacing: .16em;
  box-shadow: 0 12px 42px rgba(244, 98, 42, .2);
  transition: translate .18s ease, box-shadow .18s ease, background .18s ease;
}
#enter i { width: 38px; height: 38px; display: grid; place-items: center; border-left: 1px solid rgba(0,0,0,.18); font: 400 20px/1 serif; }
#enter:hover { translate: 0 -2px; background: #ff7540; box-shadow: 0 15px 48px rgba(244,98,42,.32); }
#enter:focus-visible { outline: 2px solid white; outline-offset: 4px; }
.entry > small { display: block; margin-top: 15px; color: var(--faint); font: 650 8px/1 sans-serif; letter-spacing: .13em; }

.control-deck {
  position: absolute;
  z-index: 4;
  left: 50%;
  bottom: var(--safe-bottom);
  width: min(560px, calc(100vw - 36px));
  min-height: 62px;
  padding: 10px 11px 10px 15px;
  translate: -50% 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  backdrop-filter: blur(18px);
  box-shadow: 0 12px 35px rgba(0,0,0,.28);
}
.control-copy { display: flex; align-items: center; gap: 12px; min-width: 0; }
.control-copy > span:last-child { min-width: 0; }
.control-icon { width: 36px; height: 36px; flex: 0 0 auto; display: grid; place-items: center; border: 1px solid var(--line-hot); color: var(--ember); font-size: 18px; }
.control-copy b, .control-copy small { display: block; }
.control-copy b { font: 750 9px/1 sans-serif; letter-spacing: .16em; }
.control-copy small { margin-top: 6px; overflow: hidden; color: var(--faint); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
.motion-state { min-width: 108px; padding: 9px 10px; text-align: right; border-left: 1px solid var(--line); }
.motion-state span, .motion-state strong { display: block; }
.motion-state span { color: var(--faint); font: 700 7px/1 sans-serif; letter-spacing: .16em; }
.motion-state strong { margin-top: 5px; color: var(--ember-soft); font: 700 9px/1 sans-serif; letter-spacing: .12em; }

#load-error { position: absolute; z-index: 9; inset: auto 20px 90px; padding: 14px; color: white; background: #8d2330; text-align: center; }
noscript { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; color: white; background: var(--black); }

/* ember:task:0481 — the field coach. Floats above the control deck, never over the canvas centre
   where the player's own expedition sits, and is dismissible with one tap/click at all times. */
.coach {
  z-index: 8;
  /* IT ANNOUNCES, IT DOES NOT INTERCEPT. On a phone the coach sits directly over the control row,
     and a card that ate those taps would be teaching a player about a button it was stopping them
     from pressing. Nothing in here is interactive — it is role="status" — so it lets touches
     through to whatever it is covering. */
  pointer-events: none;
  left: 50%;
  bottom: calc(var(--safe-bottom) + 108px);
  transform: translateX(-50%);
  width: min(360px, calc(100vw - 36px));
  padding: 18px 20px 16px;
  text-align: left;
}
.coach[hidden] { display: none; }
.coach h3 { margin: 0 0 8px; font: 650 13px/1.2 Georgia, serif; letter-spacing: -.01em; color: var(--ink); }
.coach p { margin: 0 0 14px; color: var(--muted); font-size: 11.5px; line-height: 1.55; }
.coach button { display: block; margin-left: auto; padding: 8px 16px; min-width: 44px; min-height: 44px; border: 1px solid var(--ember); background: transparent; color: var(--ember-soft); font: 700 9px/1 sans-serif; letter-spacing: .16em; cursor: pointer; }
.coach button:hover, .coach button:focus-visible { background: var(--ember); color: var(--black); }
/* THE CARD LETS TOUCHES THROUGH; ITS OWN CONTROLS DO NOT. `pointer-events: none` on the whole card
   is what keeps a coach out of the input path, and it also made every button inside it — the
   dismiss included — impossible to press: a control a player can see and cannot use. The action
   row takes its events back, and only the row, so a tap on the prose still lands on the world. */
.coach .coach-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; pointer-events: auto; }
.coach .coach-actions button { margin-left: 0; }
.coach .coach-hint { color: var(--ink); font-size: 11.5px; margin: 0 0 12px; }
.coach .coach-why { color: var(--muted); font-size: 11px; margin: 0 0 12px; }
.coach .coach-why[hidden] { display: none; }
/* The ten lessons flow as menu items rather than as a box inside a box. */
#help-lessons { display: contents; }
.help-menu { max-height: calc(100dvh - var(--safe-top) - var(--safe-bottom) - 180px); overflow-y: auto; }

/* ember:task:0540 — what happened to the connection, and the one thing to do about it. Its own
   placement rather than the coach's: two cards sharing one anchor draw on top of each other, and
   the coach is deliberately `pointer-events: none`, which would have made this card's single
   recovery action unclickable — an offer of help that cannot be taken. */
.session-notice {
  z-index: 9;
  left: 50%;
  top: calc(var(--safe-top) + 86px);
  transform: translateX(-50%);
  width: min(360px, calc(100vw - 36px));
  padding: 18px 20px 16px;
  text-align: left;
}
.session-notice[hidden] { display: none; }
.session-notice h3 { margin: 0 0 8px; font: 650 13px/1.2 Georgia, serif; letter-spacing: -.01em; color: var(--ink); }
.session-notice p { margin: 0 0 14px; color: var(--muted); font-size: 11.5px; line-height: 1.55; }
.session-notice button { display: block; margin-left: auto; padding: 8px 16px; min-width: 44px; min-height: 44px; border: 1px solid var(--ember); background: transparent; color: var(--ember-soft); font: 700 9px/1 sans-serif; letter-spacing: .16em; cursor: pointer; }
.session-notice button:hover, .session-notice button:focus-visible { background: var(--ember); color: var(--black); }

/* THE SCORE, in the topbar. A quiet chip until you are leading, then it lights ember so the one
   fact that says "you are winning" reads at a glance. */
.sc-short { display: none; }
.session-score { color: var(--ember-soft); font: 700 10px/1 sans-serif; letter-spacing: .12em; white-space: nowrap; }
.session-score[hidden] { display: none; }
.session-score--leading { color: var(--black); background: var(--ember); padding: 3px 7px; border-radius: 4px; }

/* THE FINISH. Centred, calm, and coloured by whose it is — won reads ember, lost reads the rival. */
.victory {
  z-index: 12; left: 50%; top: calc(var(--safe-top) + 86px); transform: translateX(-50%);
  width: min(400px, calc(100vw - 36px)); padding: 20px 22px 18px; text-align: left;
  border-color: var(--them);
}
.victory[hidden] { display: none; }
.victory h3 { margin: 0 0 8px; font: 650 15px/1.2 Georgia, serif; letter-spacing: -.01em; color: var(--them); }
.victory p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.55; }
.victory--won { border-color: var(--ember); }
.victory--won h3 { color: var(--ember-soft); }

/* ember:task:0061 — the other two ways in, and the four ways out to a friend. */
.entry-more { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 14px; }
.entry-alt {
  min-height: 44px; padding: 10px 18px; border: 1px solid var(--line); background: transparent;
  color: var(--muted); font: 700 9px/1 sans-serif; letter-spacing: .16em; cursor: pointer;
}
.entry-alt:hover, .entry-alt:focus-visible { border-color: var(--ember); color: var(--ember-soft); }
.entry-alt[hidden] { display: none; }
.join-form { margin-top: 14px; text-align: left; }
.join-form[hidden] { display: none; }
.join-form label { display: block; margin-bottom: 7px; color: var(--faint); font: 700 8px/1 sans-serif; letter-spacing: .18em; }
.join-form > div { display: flex; gap: 8px; }
.join-form input {
  flex: 1 1 auto; min-width: 0; min-height: 44px; padding: 10px 12px;
  border: 1px solid var(--line); background: rgba(0,0,0,.35); color: var(--ink);
  font: 600 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.join-form input:focus-visible { border-color: var(--ember); outline: none; }
.join-form button {
  min-width: 56px; min-height: 44px; border: 1px solid var(--ember); background: transparent;
  color: var(--ember-soft); font: 700 9px/1 sans-serif; letter-spacing: .16em; cursor: pointer;
}
.join-form p { margin: 9px 0 0; color: var(--them); font-size: 11px; }
.recent-worlds { margin: 14px 0 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 7px; justify-content: center; }
.recent-worlds[hidden] { display: none; }
.recent-worlds li { padding: 6px 10px; border: 1px solid var(--line); color: var(--faint); font: 650 8px/1 sans-serif; letter-spacing: .14em; }

/* Sits in the camera row with the other deck controls, so it inherits their size and their place
   in the layout rather than being squeezed out of a flex line that was measured without it. */
.invite-open { color: var(--ember-soft); }
.invite-open:hover, .invite-open:focus-visible { border-color: var(--ember); }
.friend-finder { flex: 0 0 auto; margin-left: 12px; color: var(--progress); font: 700 9px/1 sans-serif; letter-spacing: .12em; white-space: nowrap; }
.friend-finder[hidden] { display: none; }

.invite-panel {
  z-index: 11; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(340px, calc(100vw - 36px)); padding: 20px 22px 18px; text-align: left;
}
.invite-panel[hidden] { display: none; }
/* The coach and the share sheet want the same middle of the screen. While somebody is sending an
   invite, the coaching prompt waits — two cards stacked on one anchor is neither of them read. */
body.inviting #coach { display: none; }
.invite-panel h3 { margin: 0 0 10px; font: 650 15px/1.2 Georgia, serif; color: var(--ink); }
.invite-link { margin: 0 0 14px; padding: 10px 12px; border: 1px solid var(--line); background: rgba(0,0,0,.35); color: var(--ember-soft); font: 600 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; user-select: all; }
.invite-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.invite-actions button {
  flex: 1 1 auto; min-height: 44px; padding: 10px 12px; border: 1px solid var(--ember);
  background: transparent; color: var(--ember-soft); font: 700 9px/1 sans-serif;
  letter-spacing: .14em; cursor: pointer;
}
.invite-actions button:hover, .invite-actions button:focus-visible { background: var(--ember); color: var(--black); }
/* WHITE, and never inverted by a theme: a scanner needs dark modules on a light field, and an
   ember-on-black QR is a decorative square nothing can read. */
/* The square is SQUARE by declaration, not by inference. `height: auto` on an inline SVG leans on
   an intrinsic ratio the element does not always get, and the panel then renders a caption about
   pointing a camera at nothing. */
.invite-qr { display: block; width: min(200px, 60vw); height: auto; aspect-ratio: 1 / 1; margin: 16px auto 6px; background: #fff; }
.invite-qr[hidden] { display: none; }
.invite-code { margin: 8px 0 0; color: var(--faint); font: 650 8px/1.6 sans-serif; letter-spacing: .16em; text-align: center; }
.invite-code b { color: var(--ink); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .1em; }
.invite-status { margin: 10px 0 0; color: var(--muted); font-size: 11px; }

.help-toggle {
  position: absolute; z-index: 8; right: var(--safe-right); bottom: calc(var(--safe-bottom) + 108px);
  width: 40px; height: 40px; min-width: 44px; min-height: 44px;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 50%; background: var(--panel); backdrop-filter: blur(18px);
  color: var(--ink); font: 700 15px/1 Georgia, serif; cursor: pointer;
}
.help-toggle:hover, .help-toggle:focus-visible, .help-toggle[aria-expanded="true"] { border-color: var(--ember); color: var(--ember-soft); }
.help-menu {
  position: absolute; z-index: 8; right: var(--safe-right); bottom: calc(var(--safe-bottom) + 156px);
  display: flex; flex-direction: column; gap: 1px;
  border: 1px solid var(--line); background: var(--panel); backdrop-filter: blur(18px);
}
.help-menu[hidden] { display: none; }
.help-menu button {
  min-height: 44px; padding: 10px 16px; border: 0; background: transparent; color: var(--muted);
  font: 700 9px/1 sans-serif; letter-spacing: .1em; text-align: left; white-space: nowrap; cursor: pointer;
}
.help-menu button:hover, .help-menu button:focus-visible { background: rgba(244,98,42,.12); color: var(--ember-soft); }

@media (max-width: 899px) {
  .coach { bottom: calc(var(--safe-bottom) + 74px); width: calc(100vw - 32px); }
  .help-toggle, .help-menu { bottom: calc(var(--safe-bottom) + 74px); }
  .help-menu { bottom: calc(var(--safe-bottom) + 122px); }
}

@media (max-width: 899px) {
  .topbar { min-height: 66px; grid-template-columns: 1fr auto auto; padding-bottom: 10px; }
  .brand small, .divider, #tick { display: none; }
  /* THE ACCOUNT CONTROL SURVIVES THE COMPACT TOPBAR. It used to be hidden here with the rest of
     the corner furniture, which was fine while it was a readout and is not once it is the only
     way to the account surface — §1.8 is equivalent capability, not equivalent decoration. Its
     label goes; the name it opens on stays, which is the part that identifies the control. */
  .identity { padding: 0 0 0 8px; }
  .identity-label { display: none; }
  .identity strong { margin-top: 0; font-size: 9px; letter-spacing: .1em; }
  .brand b { font-size: 15px; }
  .brand-mark { width: 25px; height: 30px; scale: .78; }
  /* THE STRIP HAS TO FIT THE PHONE IT IS ON. `world-state` carries ambient status AND the score,
     and at 390px the two together ran off the right edge and over the wordmark - the objective
     ("HOLD 5 TO WIN") was the half that got pushed out of the viewport, which is the half a player
     needs. The score is never truncated; the ambient line yields, because "LIVE WORLD - REGION 0"
     losing its tail costs nothing while a clipped objective costs the game. */
  .world-state { justify-self: end; font-size: 8px; min-width: 0; overflow: hidden; gap: 7px; }
  /* Measured at 390px: brand 99 + ambient 110 + score 204 + map 70 overflowed the viewport by 29px
     and the strip started at -47, laying the score across the wordmark. The ambient line goes (the
     live dot still reports the connection, and a real failure arrives as the CONNECTION card), and
     the score keeps its meaning in a third of the width. */
  #connection { display: none; }
  .sc-long { display: none; }
  .sc-short { display: inline; }
  .mission { top: 80px; width: calc(100vw - 36px); padding: 15px 17px 14px; }
  /* A phone has one band of room at the top, and the directive and the connection notice both want
     it. While the connection has something to say, it wins: a player whose game just stopped is not
     being asked to read a field directive about ground they cannot currently reach. */
  .session-notice { top: 80px; width: calc(100vw - 36px); }
  /* THE CARD GIVES WAY TO THE COMMANDS. On a phone the connection card is full width and the
     command rail starts just under it, so a card that stayed full width sat on top of REMAP and
     the first two verbs — and a notice that covers the controls is a notice the player cannot act
     on. Only while there is a world to command: on the entry screen the rail does not exist. */
  #game.is-playing .session-notice {
    left: var(--safe-left);
    right: calc(var(--safe-right) + 74px);
    width: auto;
    transform: none;
  }
  /* THE CARD GIVES WAY; THE OBJECTIVE DOES NOT (ember:task:0059).
     The rule above is right that a player whose game just stopped is not being asked to read a
     field directive — but `display: none` on the whole card took the OBJECTIVE with it, and on a
     phone that is the only place the player is told what they are trying to do. Measured at 320px
     in the playing state, #objective-step came back zero-width. So the card collapses to its
     progress line instead of vanishing: the notice gets its band, and the one sentence that says
     what this is all for survives. */
  body.session-alert .mission {
    padding: 0;
    border: 0;
    background: none;
    backdrop-filter: none;
  }
  body.session-alert .mission::before,
  body.session-alert .mission > .eyebrow,
  body.session-alert .mission > h1,
  body.session-alert .mission > p:not(.eyebrow) { display: none; }
  .mission .eyebrow { margin-bottom: 10px; }
  .mission h1 { font-size: 22px; }
  .mission h1 br { display: none; }
  .mission > p:not(.eyebrow) { margin: 9px 0 13px; max-width: none; font-size: 11px; }
  .intel { top: auto; right: auto; left: var(--safe-left); bottom: calc(var(--safe-bottom) + 78px); width: auto; border: 0; background: none; backdrop-filter: none; }
  .intel::before, .panel-heading, .intel dt, .intel dd small, .legend { display: none; }
  .intel dl { display: flex; gap: 7px; }
  .intel dl > div { display: block; min-height: 0; width: 68px; padding: 8px 9px; border: 1px solid var(--line); background: rgba(12,13,15,.82); text-align: left; }
  .intel dd { text-align: left; }
  .intel dd strong { font-size: 18px; }
  .entry { padding: 18px 8px; translate: -50% -34%; }
  .entry h2 { font-size: clamp(40px, 14vw, 60px); }
  .entry > p:not(.eyebrow) { max-width: 34ch; font-size: 12px; }
  #enter { min-width: min(260px, 85vw); min-height: 56px; }
  .entry > small { font-size: 7px; }
  .control-deck { min-height: 60px; bottom: var(--safe-bottom); flex-wrap: wrap; }
  .control-copy small { max-width: 190px; }
  /* The friend's bearing outranks the steering hint on a narrow deck: the hint repeats what the
     coach just said, and the bearing is the only thing on screen that says which way they went. */
  .control-copy:has(.friend-finder:not([hidden])) #control-hint { display: none; }
}

@media (max-height: 620px) and (orientation: landscape) {
  .mission { top: 74px; max-width: 280px; }
  .mission > p:not(.eyebrow), .mission-progress, .progress-track { display: none; }
  .intel { left: auto; right: var(--safe-right); bottom: var(--safe-bottom); }
  .entry { max-width: 440px; padding: 8px; translate: -50% -42%; }
  .entry h2 { font-size: 38px; }
  .entry > p:not(.eyebrow) { margin: 12px auto 14px; }
  .control-deck { left: var(--safe-left); width: 310px; translate: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

@media (prefers-contrast: more) {
  :root { --line: rgba(255,255,255,.38); --muted: #d0cec8; --faint: #b7b5af; }
  .world-wash { background: rgba(0,0,0,.28); }
}


/* A CONTROL A THUMB CAN ACTUALLY HIT. 44 CSS px on both axes is the floor everything interactive
   is held to (tools/tests/test_slice_action_controls.py measures it from layout, not from here),
   and the camera controls live in the deck at the bottom so nothing ever sits over the expedition
   at the centre of the view. */
.brand { min-height: 44px; }
/* WRAPS RATHER THAN OVERFLOWS. The deck was measured for the controls it had; INVITE is a fifth,
   and a fixed row silently pushes the motion readout off the right edge of a phone instead of
   taking a second line. */
.camera-controls { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
.camera-controls button {
  min-width: 64px;
  min-height: 44px;
  padding: 4px 8px;
  display: grid;
  place-items: center;
  gap: 3px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  cursor: pointer;
}
.camera-controls button span { font-size: 15px; line-height: 1; }
.camera-controls button small { font: 700 7px/1 sans-serif; letter-spacing: .1em; white-space: nowrap; }
.camera-controls button:hover, .camera-controls button:focus-visible { color: var(--ink); border-color: var(--line-hot); }
.camera-controls button.is-on { color: var(--ember); border-color: var(--line-hot); }
.motion-state em { display: block; margin-top: 4px; white-space: nowrap; color: var(--faint); font: 650 9px/1 sans-serif; font-style: normal; letter-spacing: .08em; }

@media (max-width: 899px) {
  /* On a phone the deck is the only furniture at the bottom, and there is not room for both a
     sentence and the controls it describes. The BUTTON LABELS stay — they are what a thumb reads
     and what makes free look discoverable at all — and the long hint goes, rather than staying and
     being truncated to a single letter. */
  /* The LONG HINT goes and the rest stays. Hiding the whole `.control-copy` block took three
     things with it that have no second home on a phone: the words "HOLD TO STEER" (measured — the
     compact layout said neither "hold" nor anything else about how to move, anywhere on screen),
     the friend bearing, and the compass mark PING drops. The two bearings are the only things on a
     phone that say which way your friend went and which way you marked; laid out to `display:none`
     they could not appear even once their state arrived. */
  .control-deck { gap: 8px; padding: 8px 10px; justify-content: space-between; }
  .camera-controls button { min-width: 58px; }
  .control-copy { gap: 8px; }
  .control-copy .control-icon, .control-copy #control-hint { display: none; }
  .control-copy b { font-size: 8px; }
  .control-copy .friend-finder { display: block; }
  .motion-state { min-width: 0; padding: 6px 0 6px 10px; }
  .motion-state em { white-space: nowrap; }
}


/* THREE BARE NUMBERS ARE NOT A HUD. The compact layout shed every intel label for density, so a
   phone player saw "10 0 1" and had no way to know which one was the hostile force bearing down on
   them — and the word they most need on a small screen is exactly the one that was dropped. The
   labels come back in short form: the density was worth having, the silence was not.

   THREATS, not HOSTILE, deliberately. The desktop legend carries the word "HOSTILE" whether or not
   anything is actually out there, so a reader that trusts the WORD learns nothing; what carries the
   claim is the NUMBER beside a label naming what it counts. Sharing the desktop's vocabulary
   ("VISIBLE THREATS" / "THREATS") means one phrase identifies the count at either shape. */
.dt-long { display: inline; }
.dt-short { display: none; }
@media (max-width: 899px) {
  .intel dt { display: block; margin-bottom: 4px; color: var(--faint); font: 700 7px/1 sans-serif; letter-spacing: .14em; }
  .dt-long { display: none; }
  .dt-short { display: inline; font-weight: 700; }
  .intel dl > div { width: 74px; }
}

/* ember:task:0493 — contact and the way back.
   The contact panel sits ABOVE the control deck and never over the canvas centre: a fight is the
   moment the player most needs to see their own expedition and keep steering it, so this reports
   and never captures. The defeat panel is the one surface allowed to be modal-looking, and even it
   leaves the world visible behind it — being scattered is a setback the player watches happen, not
   a screen that replaces the game. */
.contact {
  z-index: 8;
  left: 50%;
  bottom: calc(var(--safe-bottom) + 108px);
  transform: translateX(-50%);
  width: min(340px, calc(100vw - 36px));
  padding: 12px 16px 14px;
  text-align: left;
  border-color: var(--ember);
}
.contact[hidden], .defeat[hidden] { display: none; }
/* ember:task:0054 — the selected ground. Sits above the control deck and clear of the canvas
   centre, the same rule the contact panel follows: nothing a player needs may cover the fight. */
.selection {
  position: absolute;
  z-index: 7;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--safe-bottom) + 84px);
  max-width: calc(100vw - 36px);
  color: var(--faint);
  font: 400 9.5px/1 sans-serif;
  letter-spacing: .14em;
  pointer-events: none;
  white-space: nowrap;
}
.selection[hidden] { display: none; }

/* THE WORLD SIGNAL LOG (ember:task:0058).

   A column against the LEFT edge, mirroring the command rail against the right, for the same
   reason that rail gives: its x never reaches the centre of the view, so the expedition a player
   is steering stays visible at 320 px with a fight and a weather warning on screen at once. It
   starts below the mission card and stops above the intel strip, so it takes no band that is
   already spoken for.

   `pointer-events: none` because nothing in it is interactive: a steering contact that slides
   under this surface stays a steering contact, which is what §4 of the action contract asks of
   anything drawn over the world. */
.world-signals {
  position: absolute;
  z-index: 6;
  left: var(--safe-left);
  top: 430px;
  bottom: 150px;
  width: 214px;
  overflow: hidden;
  pointer-events: none;
}
.world-signals[hidden] { display: none; }
.world-signals ul { margin: 0; padding: 0; list-style: none; }
.world-signals li {
  display: flex;
  gap: 6px;
  align-items: baseline;
  margin-bottom: 3px;
  padding: 4px 7px;
  border-left: 3px solid var(--line);
  background: rgba(12, 13, 15, .74);
  color: var(--muted);
  font: 400 9px/1.35 sans-serif;
  letter-spacing: .06em;
}
/* THE CHANNEL, IN THE BORDER AND IN A WORD. §6.9's four channels are a rank, and a rank carried
   only by which row is on top is a rank a screen reader receives as an ordering accident. The
   channel name rides in the row as text; the weight and the border are the two channels that make
   it readable without it. */
.world-signals li b {
  flex: 0 0 auto;
  color: var(--faint);
  font: 700 7px/1.5 sans-serif;
  letter-spacing: .14em;
}
.world-signals li[data-channel="critical"] { border-left-color: var(--them); color: var(--ink); }
/* The channel word at 7px carried `--them` straight, which measures 4.28:1 on the row's own
   background — under AA, on the one channel a player must never have to squint at. Lightened from
   the SAME token rather than from a second hex: the hostile hue stays stated in one place, and the
   ownership separation the visual pipeline measures against `--them` is untouched. */
.world-signals li[data-channel="critical"] b {
  color: color-mix(in srgb, var(--them) 82%, #fff);
}
.world-signals li[data-channel="objective"] { border-left-color: var(--ember); }
.world-signals li[data-channel="social"] { border-left-color: var(--progress); }
.world-signals li[data-channel="ambient"] { opacity: .78; }
.contact-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.contact-head b { color: var(--ember-soft); font: 700 9px/1 sans-serif; letter-spacing: .16em; }
.contact-head em { color: var(--faint); font: 400 9.5px/1 sans-serif; font-style: normal; }
.strength-track { margin: 9px 0 7px; height: 3px; background: rgba(255,255,255,.08); overflow: hidden; }
.strength-track i { display: block; height: 100%; background: var(--ember); transition: width .18s linear; }
.contact p { margin: 0 0 4px; color: var(--muted); font-size: 11px; line-height: 1.45; }
.contact-escape { color: var(--faint) !important; font-size: 10.5px !important; }
.contact button {
  margin-top: 9px; width: 100%; min-height: 44px; padding: 9px 14px;
  border: 1px solid var(--ember); background: transparent; color: var(--ember-soft);
  font: 700 9px/1 sans-serif; letter-spacing: .16em; cursor: pointer;
}
.contact button:hover, .contact button:focus-visible { background: var(--ember); color: var(--black); }

.defeat {
  z-index: 9;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: min(400px, calc(100vw - 36px));
  padding: 22px 24px 18px;
  text-align: left;
}
.defeat h3 { margin: 0 0 10px; color: var(--ink); font: 650 15px/1.2 Georgia, serif; letter-spacing: -.01em; }
.defeat p { margin: 0 0 14px; color: var(--muted); font-size: 11.5px; line-height: 1.55; }
.defeat-ledger { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 0 0 16px; }
.defeat-ledger dt { color: var(--faint); font: 700 7px/1 sans-serif; letter-spacing: .16em; }
.defeat-ledger dd { margin: 5px 0 0; color: var(--ember-soft); font: 400 11px/1.35 sans-serif; }
.defeat button {
  display: block; width: 100%; min-height: 44px; padding: 11px 16px;
  border: 1px solid var(--ember); background: var(--ember); color: var(--black);
  font: 700 10px/1 sans-serif; letter-spacing: .16em; cursor: pointer;
}
.defeat button[disabled] { background: transparent; color: var(--faint); border-color: var(--line); cursor: default; }
.defeat-note { margin: 12px 0 0 !important; color: var(--faint) !important; font-size: 10.5px !important; }

/* ember:task:0057 — the world commands.
   A RAIL DOWN THE RIGHT EDGE, on every screen. Twelve verbs need somewhere permanent, and the two
   places a panel can go on a phone are both already spoken for: the band above the control deck
   belongs to the contact panel, and the middle belongs to the expedition. A column against the
   right edge takes neither — its x never reaches the centre of the view, so the thing the player
   is steering stays visible at 320 px with a fight on screen and every command in reach. */
/* NOTHING IN HERE IS SQUEEZED TO NOTHING, AND NOTHING IS PUSHED OFF THE END.
   The list of commands was `flex: 1 1 auto` beside a `flex: 0 0 auto` row of input options. At
   1280x720 the panel is 240px tall and the options row wraps to three rows of 44px, so the only
   shrinkable child — every command in the game — was laid out to ZERO height: twelve verbs with
   bounding boxes and no pixels, and a press at any of their centres landing on POINTER STEER
   behind them. A mouse player could not issue a single order.
   Letting the whole panel scroll instead fixed that and broke the mirror of it — the options row
   went to y=1205 in a 720px viewport, reachable only by scrolling past twelve commands. So both
   regions scroll inside their OWN bounds and both keep a floor: the list never falls below two
   commands, the options row never below one, and each of them is always on screen. */
.commands {
  z-index: 6;
  right: var(--safe-right);
  top: 330px;
  bottom: 150px;
  width: 214px;
  display: flex;
  flex-direction: column;
  padding: 12px 12px 10px;
  overflow: hidden;
}
.commands[hidden] { display: none; }
.commands .panel-heading { flex: 0 0 auto; margin-bottom: 8px; }
.link-button {
  min-width: 64px; min-height: 44px; padding: 0 10px;
  border: 1px solid var(--line); background: transparent; color: var(--ember-soft);
  font: 700 8px/1 sans-serif; letter-spacing: .16em; cursor: pointer;
}
.link-button:hover, .link-button:focus-visible { border-color: var(--ember); color: var(--ink); }
.emphasis {
  flex: 0 0 auto; margin: 0 0 8px; overflow: hidden;
  color: var(--faint); font: 700 8px/1.4 sans-serif; letter-spacing: .12em;
  text-overflow: ellipsis; white-space: nowrap;
}
.command-list {
  /* `min-height` is the whole fix: a scrolling box has no content height to defend, so without a
     floor the flex parent is free to shrink it to zero — which is exactly what it did. */
  flex: 1 1 auto; min-height: 88px; margin: 0; padding: 0; list-style: none;
  overflow-y: auto; overscroll-behavior: contain;
}
.command-list li { margin-bottom: 4px; }
.command {
  display: block; width: 100%; min-height: 44px; padding: 7px 9px;
  border: 1px solid var(--line); background: rgba(12,13,15,.72); color: var(--muted);
  text-align: left; cursor: pointer;
}
.command b { display: block; font: 750 9px/1.2 sans-serif; letter-spacing: .16em; }
.command kbd {
  position: absolute; right: 10px; margin-top: -12px;
  color: var(--faint); font: 700 8px/1 sans-serif; letter-spacing: .1em;
}
.command small { display: block; margin-top: 3px; color: var(--faint); font-size: 8.5px; line-height: 1.35; }
.command-list li { position: relative; }
.command.is-ready { border-color: var(--line-hot); color: var(--ink); }
.command.is-ready b { color: var(--ember-soft); }
/* UNAVAILABLE, AND STILL PRESSABLE. `aria-disabled` rather than `disabled`, because a disabled
   button dispatches no events: on the compact rail — where the rule beside it does not fit and is
   not rendered — a greyed command was a dead square that could never tell anybody why. */
.command[aria-disabled="true"] { cursor: default; opacity: .72; }
.command:not([aria-disabled="true"]):hover, .command:focus-visible {
  border-color: var(--ember); color: var(--ink);
}
/* THE ANSWER TO THE LAST ORDER. In the selection's own band and above it in priority: a rule the
   world refused on is a thing to act on, and the name of the ground under the pointer is not. */
.order-answer {
  position: absolute;
  z-index: 7;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--safe-bottom) + 84px);
  max-width: min(420px, calc(100vw - 36px));
  margin: 0;
  color: var(--ember-pale);
  font: 500 10.5px/1.4 sans-serif;
  text-align: center;
  pointer-events: none;
}
.order-answer[hidden] { display: none; }
/* Shrinkable to one row and no further, and it scrolls the rest — so a panel too short for both
   regions gives up height evenly instead of handing all of it to whichever child cannot shrink. */
.command-modes {
  flex: 0 1 auto; min-height: 44px; display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px;
  overflow-y: auto; overscroll-behavior: contain;
}
/* The account surface borrows the bindings dialog's shape — it is the same kind of thing: a panel
   a player stops the world to read. Only its own lines are new. */
.account-line { margin: 0 0 10px; color: var(--muted); font: 400 11.5px/1.5 sans-serif; }
.account-line b { color: var(--ink); letter-spacing: .1em; }
.command-modes button {
  flex: 1 1 auto; min-width: 64px; min-height: 44px; padding: 0 8px;
  border: 1px solid var(--line); background: transparent; color: var(--faint);
  font: 700 7.5px/1 sans-serif; letter-spacing: .12em; cursor: pointer;
}
.command-modes button[aria-pressed="true"] { border-color: var(--ember); color: var(--ember-soft); }

/* WHAT EVERY KEY DOES. A dialog rather than a rail: remapping is a thing a player stops to do,
   and it is the one surface that has to show all of them at once. */
.bindings-panel {
  z-index: 12;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: min(430px, calc(100vw - 32px));
  max-height: min(78vh, 620px);
  padding: 20px 22px 16px;
  display: flex; flex-direction: column;
}
.bindings-panel[hidden] { display: none; }
.bindings-panel h3 { margin: 0 0 12px; font: 650 15px/1.2 Georgia, serif; }
.bindings-list { flex: 1 1 auto; margin: 0; padding: 0; list-style: none; overflow-y: auto; }
.bindings-list li {
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px 10px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.bindings-list b { font: 700 9px/1.2 sans-serif; letter-spacing: .14em; }
.bindings-list small { grid-column: 1 / -1; color: var(--faint); font-size: 9px; line-height: 1.4; }
.binding-key {
  min-width: 74px; min-height: 44px; padding: 0 10px;
  border: 1px solid var(--line-hot); background: transparent; color: var(--ember-soft);
  font: 700 9px/1 sans-serif; letter-spacing: .14em; cursor: pointer;
}
.binding-key:hover, .binding-key:focus-visible { background: var(--ember); color: var(--black); }
.bindings-status { margin: 10px 0; min-height: 14px; color: var(--ember-pale); font-size: 10px; }

/* The touch stick, where the thumb put it. Presentation only, and never in the way of a press. */
.stick {
  position: absolute; z-index: 5;
  translate: -50% -50%;
  border: 1px solid var(--line-hot); border-radius: 50%;
  background: radial-gradient(circle, rgba(244,98,42,.10), transparent 70%);
  pointer-events: none;
}
.stick[hidden] { display: none; }
.stick i {
  position: absolute; left: 50%; top: 50%; width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border: 1px solid var(--ember); border-radius: 50%; background: rgba(244,98,42,.22);
}

@media (max-width: 899px) {
  /* On a phone the rail keeps its edge and loses its width: the label and the bound key are what a
     thumb needs, and the sentence underneath is what a wide screen has room for. */
  /* Clear of the control deck, which wraps to 168 px on a 320 px screen — measured, not guessed:
     a rail that ends where the deck begins puts the camera buttons under it and a thumb reaching
     for RECENTRE presses LEFT-HANDED instead. */
  .commands { top: 150px; bottom: 190px; width: 66px; padding: 6px 5px; }
  /* The log narrows with the rail and stays off the middle: 320 − 66 (rail) − 96 (this) leaves a
     clear column of world down the centre at the narrowest phone anybody still ships. */
  .world-signals { top: 300px; bottom: 200px; width: 96px; }
  .world-signals li { padding: 3px 5px; font-size: 7.5px; letter-spacing: 0; }
  .commands .panel-heading span, .commands .emphasis, .command small { display: none; }
  /* `display: flex` restated, not inherited: the compact rules above switch every `.panel-heading`
     off for the intel card, and REMAP is the only way to the controls list on a phone. */
  .commands .panel-heading { display: flex; justify-content: center; height: auto; padding: 0;
                             border: 0; margin-bottom: 6px; }
  .link-button { min-width: 56px; }
  .command { min-height: 46px; padding: 5px 4px; text-align: center; }
  .command b { font-size: 7.5px; letter-spacing: .08em; }
  .command kbd { position: static; display: block; margin: 3px 0 0; }
  .command-modes { flex-direction: column; }
  .command-modes button { min-width: 56px; font-size: 6.5px; letter-spacing: .06em; }
  /* Clear of the rail, so a refusal is a full sentence rather than six characters a column wide. */
  .order-answer { left: var(--safe-left); right: calc(var(--safe-right) + 74px); max-width: none;
                  transform: none; text-align: left; }
}

/* ── THE FRONTIER PANEL (ember:task:0563) ────────────────────────────────────────────────────
   The living world, on screen. Six biomes, three deposit families, six creatures, twelve event
   families and a Codex were all simulated and none of them had ever crossed the wire, so none had
   a line to be styled. Placed under the intel card on a desk and folded into the bottom sheet on a
   phone, because §1.8's parity is about what a player can READ and DO, never about identical
   pixels — the same facts, laid out for the device holding them. */
.field {
  position: absolute; top: 350px; right: var(--safe-right); width: 242px;
  max-height: 42vh; overflow-y: auto; z-index: 4;
}
.field[hidden] { display: none; }
.field .panel-heading { display: flex; align-items: center; justify-content: space-between; }
.field-sky { margin: 10px 14px; color: var(--ink); font: 400 11px/1.5 Georgia, serif; }
/* A WARNING OUTRANKS EVERYTHING ELSE IN THIS PANEL, and is coloured to say so. §6 puts critical
   response above ambient; a forecast and an incoming hazard printed identically is a HUD that has
   dropped the distinction the contract makes. */
.field-warning { margin: 0 14px 10px; padding: 6px 9px; border-left: 2px solid var(--ember);
                 color: var(--ember-soft); font: 700 9.5px/1.45 sans-serif; letter-spacing: .04em; }
.field-warning[hidden] { display: none; }
.field-list { margin: 0 0 8px; padding: 0 14px; list-style: none; }
.field-list li { padding: 4px 0; color: var(--muted); font: 400 10px/1.45 sans-serif;
                 border-bottom: 1px solid var(--line); }
.field-list li:last-child { border-bottom: 0; }
.field-list li.quiet { color: var(--faint); font-style: italic; }
.field-events li { color: var(--ink); }
.field-events li.joined { color: var(--ember-soft); }
.field-standing, .field-shelter { margin: 8px 14px; color: var(--faint);
                                  font: 400 9.5px/1.5 sans-serif; }
.field #field-activities { margin: 4px 14px 12px; }

/* ── WHAT YOU CAN DO ─────────────────────────────────────────────────────────────────────────
   The shipped registry, as the authority surveyed it. Centred and scrollable on both compositions
   because it is a full answer rather than a glance, and a list a phone cannot scroll is a list a
   phone does not have. */
.activities-panel {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 32px)); max-height: min(76vh, 620px); overflow-y: auto;
  padding: 20px 22px; z-index: 40;
}
.activities-panel[hidden] { display: none; }
.activities-panel h3 { margin: 0 0 6px; font: 650 16px/1.2 Georgia, serif; color: var(--ink); }
.activities-summary { margin: 0 0 14px; color: var(--ember-soft);
                      font: 700 9.5px/1.5 sans-serif; letter-spacing: .06em; }
.activities-list h4 { margin: 14px 0 6px; color: var(--faint);
                      font: 700 8.5px/1 sans-serif; letter-spacing: .18em; }
.activities-list ul { margin: 0; padding: 0; list-style: none; }
.activities-list li { display: flex; flex-direction: column; gap: 2px; padding: 6px 0 6px 18px;
                      position: relative; border-bottom: 1px solid var(--line); }
.activities-list li::before { content: ""; position: absolute; left: 2px; top: 11px;
                              width: 7px; height: 7px; border-radius: 50%; }
.activities-list li.can::before { background: var(--ember); }
/* NOT WORKING IS SAID, NOT HIDDEN. A registry that quietly dropped the activities its world cannot
   offer would be a list of claims again — the exact failure this whole surface exists to refuse. */
.activities-list li.cannot::before { border: 1px solid var(--faint); }
.activities-list li.cannot b { color: var(--faint); }
.activities-list b { font: 400 11.5px/1.45 Georgia, serif; color: var(--ink); }
.activities-list small { color: var(--faint); font: 400 9px/1.4 monospace; }
.activities-panel > button { display: block; margin: 16px 0 0 auto; padding: 9px 18px;
                             min-width: 44px; min-height: 44px; border: 1px solid var(--ember);
                             background: transparent; color: var(--ember-soft);
                             font: 700 9px/1 sans-serif; letter-spacing: .16em; cursor: pointer; }
.activities-panel > button:hover, .activities-panel > button:focus-visible {
  background: var(--ember); color: var(--black);
}

@media (max-width: 899px) {
  /* ON A PHONE the frontier is a strip above the control rail rather than a column beside the
     world: there is one screen, and the world is what the player came for.
     NOTHING MAY REACH THE MIDDLE OF THE SCREEN. The expedition is drawn at the centre and a tap on
     it is how every order finds its subject, so a panel that overlays it does not merely obscure
     the game - it eats the tap, and the player is left pressing a verb with nothing selected.
     Measured at 320x640, which is the narrowest band the control suite drives: the rail sits 150 px
     up, so 20vh of panel above it stops well short of the 320 px centre line. It scrolls inside
     that rather than growing past it. */
  .field { top: auto; left: var(--safe-left); right: var(--safe-right);
           bottom: calc(var(--safe-bottom) + 150px); width: auto; max-height: 20vh; }
  .field-sky { font-size: 10.5px; }
  .field-list li { font-size: 9.5px; }
  /* Collapsed, the panel is its own heading and nothing else - a strip a thumb can open. */
  .field #field-body[hidden] { display: none; }
}

/* THE MINIMAP (ember:task:0059). Wide-composition only — `renderMinimap` decides that from width
   AND pointer via worldmap.js, so this is presentation and never the rule. Pointer-transparent and
   unfocusable for the same reason the signal log is: a steering drag that crosses it stays
   steering, and a glance surface must not add a control to tab past. */
.minimap {
  position: absolute;
  /* THE RIGHT COLUMN HAS ONE OCCUPANT AT A TIME. Pinned under the top bar, the minimap landed on
     FIELD INTEL - both had been written as "the panel at the top right", and neither could know
     about the other while the rule was unparsed. Intel keeps the top (it is read continuously);
     the map takes the free ground above the control deck, where a glance costs no other surface. */
  bottom: calc(var(--safe-bottom) + 132px);
  right: var(--ds-safe-right);
  z-index: 6;
  width: 13rem;
  max-height: 40vh;
  overflow: hidden;
  padding: var(--ds-space-snug);
  background: var(--ds-surface);
  border: var(--ds-border) solid var(--ds-line);
  border-radius: var(--ds-radius-soft);
  font-size: var(--ds-type-micro);
  color: var(--ds-muted);
  pointer-events: none;
}
.minimap ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--ds-space-hair); }
.minimap li { display: flex; gap: var(--ds-space-tight); align-items: baseline; }
/* Side is never carried by colour alone — the glyph is the second channel, the same law
   signals.js holds every world mark to. */
.minimap li::before { content: "■"; color: var(--neutral); }
.minimap li[data-side="yours"]::before { content: "●"; color: var(--you); }
.minimap li[data-side="theirs"]::before { content: "▲"; color: var(--them); }
.minimap small { display: block; margin-top: var(--ds-space-tight); color: var(--ds-faint); }

#map-marks { list-style: none; margin: 0; padding: var(--ds-space-snug); display: grid;
             gap: var(--ds-space-tight); max-height: 46vh; overflow-y: auto; }
#map-marks li { display: flex; gap: var(--ds-space-tight); align-items: baseline; }
#map-marks li::before { content: "■"; color: var(--neutral); }
#map-marks li[data-side="yours"]::before { content: "●"; color: var(--you); }
#map-marks li[data-side="theirs"]::before { content: "▲"; color: var(--them); }

/* INPUT OPTIONS: open and unadorned where there is room, folded to one line where there is not.
   The summary marker only appears at the size where it means something to press. */
.input-options > summary { display: none; }
.input-options[open] > summary { display: none; }
@media (max-width: 899px) {
  .input-options > summary {
    display: block; cursor: pointer; list-style: none; margin-top: 8px; padding: 7px 0 0;
    border-top: 1px solid var(--rule); color: var(--faint);
    font: 700 8px/1 sans-serif; letter-spacing: .16em;
  }
  .input-options > summary::-webkit-details-marker { display: none; }
  .input-options > summary::after { content: " +"; color: var(--ember-soft); }
  .input-options[open] > summary::after { content: " -"; }
}

/* THE CHANNEL ON ITS OWN LINE WHERE THE ROW IS NARROW. The rail is 214px and the channel word sits
   BESIDE the message, so a phone got about 140px of text column and "A scout is closing from N, 64
   m out." arrived as four ragged lines of three words. The rank still rides in the row as text (a
   screen reader must not receive it as an ordering accident) - it simply sits above the sentence
   instead of eating its width, which is the difference between a readout and a stack of fragments.
   The wide layout keeps the single-line row: it has the room, and one line scans faster. */
@media (max-width: 899px) {
  .world-signals li { flex-direction: column; align-items: stretch; gap: 1px; }
  .world-signals li b { font-size: 6.5px; opacity: .9; }
}

/* ── WEATHER THAT MOVES ───────────────────────────────────────────────────────────────────────
   A planet that never changes light is a diagram of a planet. This is the ONE moving thing in the
   backdrop: a slow band of brighter air crossing the ground, on a minute-and-a-half cycle so it
   reads as weather passing rather than as an effect running. It is drawn ABOVE the terrain and
   BELOW everything a player acts on, it is nearly transparent, and it never touches the ownership
   markers or the signal layer - the eye still lands on what you do, and the world underneath is
   no longer still.

   The existing prefers-reduced-motion block already stops it (it clamps every animation to .01ms),
   which is the right answer: motion here is atmosphere, and atmosphere is exactly what a reader
   who asked for stillness is asking not to have. */
.sky-drift {
  position: absolute;
  inset: -20% -30%;
  z-index: -2;
  pointer-events: none;
  opacity: .5;
  background:
    linear-gradient(74deg,
      transparent 0 34%,
      rgba(226, 184, 132, .055) 44%,
      rgba(255, 214, 160, .085) 50%,
      rgba(226, 184, 132, .055) 56%,
      transparent 66% 100%);
  animation: sky-drift 96s linear infinite;
  will-change: transform;
}
@keyframes sky-drift {
  from { transform: translate3d(-16%, -6%, 0); }
  to   { transform: translate3d(16%, 6%, 0); }
}

/* TWO MORE AIRS AT TWO MORE SPEEDS. One band crossing alone reads as a single object sliding past;
   weather is layers moving at different rates, and the parallax between them is the whole of the
   effect. The second is cooler, wider and slower - high cloud - and the third is a narrow warm edge
   running the other way, so the ground is never lit exactly the same way twice. Both sit inside
   .sky-drift, which is already below everything a player acts on, so neither can touch a marker.
   The reduced-motion block stops all three together. */
.sky-drift::before, .sky-drift::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  will-change: transform;
}
.sky-drift::before {
  background: linear-gradient(96deg, transparent 0 28%, rgba(120, 156, 190, .055) 42%,
              rgba(150, 186, 220, .075) 51%, rgba(120, 156, 190, .055) 60%, transparent 74% 100%);
  animation: sky-high 173s linear infinite;
}
.sky-drift::after {
  background: linear-gradient(62deg, transparent 0 44%, rgba(255, 176, 108, .05) 49%,
              rgba(255, 208, 150, .07) 51%, rgba(255, 176, 108, .05) 54%, transparent 60% 100%);
  animation: sky-ember 61s linear infinite;
}
@keyframes sky-high {
  from { transform: translate3d(9%, 4%, 0); }
  to   { transform: translate3d(-9%, -4%, 0); }
}
@keyframes sky-ember {
  from { transform: translate3d(8%, -6%, 0); }
  to   { transform: translate3d(-8%, 6%, 0); }
}

/* THE RIGHT COLUMN HOLDS TWO PANELS AND HAD ROOM FOR ONE. `.intel` starts at 112px with no bottom
   and its three rows are 72px tall, so it ran to about 378px - straight through `.commands`, which
   starts at 330. Two panels claiming one space is not a z-index question, it is an arithmetic one:
   the rows come down to a size that actually fits above the commands deck, which also buys back the
   density the operator asked for. 112 + 50 heading + 3 x 56 = 330 exactly. */
@media (min-width: 900px) {
  .intel dl > div { min-height: 56px; padding: 9px 17px; }
  .intel { max-height: 218px; overflow: hidden; }
}

/* THE VERB DECK IS GONE. Operator ruling 2026-08-08: moving and touching the world are the only
   actions, and the deck's buttons do not do anything. Their own definitions say so - `capture` and
   `engage` are route "automatic" ("Begins on its own once you stand inside a ring", "Contact starts
   itself"), `move` is the steer and `inspect` is the tap. A panel of controls that cannot be
   controlled teaches a player to distrust every panel, and it was the largest thing on the screen.
   Hidden rather than deleted: the verbs are still the authority's vocabulary, still keybound and
   still named in the remap screen, so nothing the server accepts has been taken away - only the
   pretence that a button was how you did it. What remains in this panel is the input SETTINGS,
   which are real and do work. */
#command-list, #emphasis { display: none !important; }

/* ── THE MINIMAL RUN SURFACE ──────────────────────────────────────────────────────────────────
   A run game asks three questions and a screen should answer exactly those: where am I going,
   what am I made of, and what is about to happen to me. Everything else on this display was a
   control room built around a world nobody could see - eight resident panels for a game whose
   only verbs are steer and touch.

   WHAT STAYS RESIDENT: the world, the directive, the vitals, the deck. Everything below is either
   gone or reduced to the smallest form that still answers its question. Hidden, never deleted -
   every one of these is still built, still populated, still reachable by a screen reader, and one
   line brings any of it back the moment it earns its place. */

/* The event rail was a scrolling debug log wearing a HUD's clothes: twelve rows deep, most of them
   AMBIENT ("SPRING level 1" four times over), pushing the one CRITICAL line off the bottom. A
   runner needs the threat, not the transcript - three rows, criticals only. */
.world-signals { max-height: 132px; }
.world-signals li[data-channel="ambient"],
.world-signals li[data-channel="objective"] { display: none; }
.world-signals li:nth-of-type(n+4) { display: none; }

/* The CONTROLS panel is now input SETTINGS and nothing else - a thing a player touches once, and
   never during a run. It lives behind the same REMAP dialog its own heading points at. */
.commands { display: none; }

/* Intel is four numbers. It does not need four rows of 56px and a live badge to say so. */
@media (min-width: 900px) {
  .intel { top: 112px; width: 210px; max-height: none; }
  .intel dl > div { min-height: 0; padding: 7px 14px; }
  .intel dt { font-size: 7.5px; }
  .intel dd { font-size: 20px; }
  .intel .live-label { display: none; }
}

/* The frontier read-out is a paragraph of weather and inventory in the middle of a run. It is a
   glance surface, so it collapses to its own heading until asked for. */
.field #field-body { display: none; }

/* The minimap renders as a LIST - "site 263, site 262, an expedition, +245 further out" - which is
   the accessible transcript of a map, not a map. Twelve rows of site numbers tell a runner nothing
   they can steer by; the edge markers already point at what matters and the M key opens the real
   one. The list stays in the document for the screen reader that wants it and leaves the screen. */
.minimap { position: absolute; width: 1px; height: 1px; overflow: hidden;
           clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }

/* THE FRONTIER strip was landing on the intel panel it sits beneath. With its body collapsed it is
   a heading and a toggle, so it tucks under the numbers instead of across them. */
@media (min-width: 900px) {
  .field { top: 300px; right: var(--safe-right); left: auto; width: 210px; bottom: auto; }
}

/* A NUMBER MOVING, MADE VISIBLE. It rises off the figure it belongs to and fades - loud enough to
   catch the eye mid-run, brief enough that it never becomes another resident panel. Green is a
   gain, ember a loss; the sign is drawn as well as coloured, because colour alone is not a channel. */
.intel dd { position: relative; }
.delta {
  position: absolute; right: -2px; top: -6px;
  font: 700 12px/1 ui-sans-serif, system-ui, sans-serif; letter-spacing: .02em;
  pointer-events: none; animation: delta-rise 2.4s ease-out forwards;
}
.delta--up { color: var(--progress, #7fd1a8); }
.delta--down { color: var(--ember, #ff7a28); }
@keyframes delta-rise {
  0%   { opacity: 0; transform: translateY(6px) scale(.9); }
  14%  { opacity: 1; transform: translateY(-2px) scale(1.06); }
  70%  { opacity: 1; transform: translateY(-14px) scale(1); }
  100% { opacity: 0; transform: translateY(-24px) scale(1); }
}
