/* The Dig — one stylesheet, tokens first. Dark by default: you are looking
   down into a hole, and the soil should be the brightest thing on screen. */

:root {
  /* ------------------------------------------------------------------------
     Warm dark, not cold dark.

     The old ground was #17131a -- a purple-black -- under ink at #f2e9e0, which
     is about fourteen to one. That is a specification for a control room. The
     board itself is ochre, sand and rust, so the chrome was the one cold thing
     on screen and it made the whole game feel like a dashboard with a game
     inside it.

     Everything here is pulled towards the colour of the dirt: browner darks,
     softer ink, contrast brought down to a level you can sit in front of for
     an hour. Nothing is grey.
     ------------------------------------------------------------------------ */
  /* ------------------------------------------------------------------------
     AND THEN THE SITE HAPPENED, AND THE CHROME FOLLOWED IT.

     > "I think the game should match the new theme of the website."

     The paragraph above is still the reason this palette is warm rather than
     grey, and it is still right: nothing here is a cold grey, everything is
     pulled toward the colour of the dirt. What has changed is the LEVEL. The
     site settled on a near-black ground with bone text and blood edges, and a
     product whose page and whose board are two different rooms is two products.

     So the darks come down to the site's own values and the ink goes up to the
     wordmark's bone. The hue is unchanged -- these are the same browns, further
     down -- which is what keeps the chrome from reading as a dashboard around
     an ochre board.

     THE BOARD ITSELF IS NOT TOUCHED BY ANY OF THIS. The ground, the crew and
     the animals are drawn art, not CSS. Darkening the furniture around them is
     the whole intent: the game gets quieter and the race gets louder.
     ------------------------------------------------------------------------ */
  --bg:        #0c0a08;
  --bg-panel:  #14110d;
  --bg-raise:  #1c1813;
  --line:      #2b241c;
  --ink:       #f4efe2;
  --ink-dim:   #c3b6a0;
  --ink-faint: #92836c;

  /* The rail's width. Four rules depend on it -- its own width, the panel's
     right offset, the panel's parked transform and the toolbar's padding --
     and they were four separate 40s. One number now, so making the marks
     bigger is one edit rather than a hunt. */
  --rail-w: 46px;

  --gold:   #e8b44e;
  /* THE WORDMARK'S OWN THREE COLOURS, so the buttons and the logo are made of
     the same thing. See `img/logo.svg` and `tools/makelogo.mjs`: white
     letters, blood red, and one pixel of near-black around everything. The
     outline colour is the load-bearing one -- it is what turns a flat rectangle
     into a key you can press. */
  /* THE SUBJECT'S COLOUR. Names, titles and headings were gold because they
     mattered, which is the one thing gold cannot mean here any more -- it has
     to keep meaning VALUE or it means nothing. Eleven rules moved; the other
     sixty-five that set a gold text colour were left, and reading them is what
     took the time: about fifteen are money and belong in gold, about forty-five
     are STATE (an active tab, an armed switch, a live mast, a hovering row, a
     subscriber's name) where gold is the highlight rather than the emphasis,
     and a handful carry a comment arguing for gold specifically. */
  --bone:    #f4efe2;
  --blood-line: #9c1622;
  --blood-deep: #6d0f18;
  --blood:   #d92433;
  /* NOT `--edge`, WHICH IS TAKEN. There is a second `:root` further down this
     file defining `--edge: #4a3d2f` for compressed borders, and a later block
     wins -- so naming this one `--edge` silently handed every button that
     brown instead of this near-black, which looks like nothing at all in a
     screenshot and shows up immediately in a computed style. */
  --outline: #0d0a08;
  --good:   #8ecb92;
  --warn:   #dfa863;
  --bad:    #d97a62;
  --accent: #6fb3bf;

  /* ------------------------------------------------------------------------
     One type scale, with a floor.

     Nine different sizes were in use between 9.5px and 15px, and the commonest
     was 10. When everything is small nothing has hierarchy, so the eye never
     gets told where to rest -- it just works, continuously, at the limit of
     legibility. These are five steps with real distance between them and a body
     size somebody can actually read.
     ------------------------------------------------------------------------ */
  --t-xs:  11.5px;   /* labels and units, and nothing else */
  --t-sm:  13px;     /* secondary text */
  --t-md:  14.5px;   /* body -- the default */
  --t-lg:  17px;     /* card and panel headings */
  --t-xl:  22px;     /* screen titles */

  --lh:     1.62;    /* generous, because most of the text here is prose */

  /* Softer geometry. Cozy is mostly radius, spacing and restraint. */
  --r:      13px;
  --r-sm:   9px;
  --pad:    16px;
  --shadow: 0 10px 30px rgba(0, 0, 0, 0.38);

  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--t-md);
  line-height: var(--lh);
  overflow: hidden;
  /* The board is pixel art; the chrome is not. Smooth the chrome only. */
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------------------
   THE QUIET FRAME

   The board is the product, so the board gets the screen. There is no panel
   column any more: one row for the header, everything below it is ground. The
   panel still exists and still holds everything it held -- it simply waits
   off-stage behind the edge rail until you ask for it.

   Measured before this: chrome took 38% of the viewport and the board got 62%.
   --------------------------------------------------------------------------- */
#app {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  grid-template-areas: "top" "stage";
  height: 100%;
}

/* ---- top bar ------------------------------------------------------------ */

.topbar {
  grid-area: top;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  padding: 7px 12px;
  background: linear-gradient(180deg, #2a2129, #201921);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset, 0 3px 12px rgba(0, 0, 0, 0.3);
}

/* ---- identity ----------------------------------------------------------- */

.brand { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 0 1 auto; }
.brand-mark {
  width: 22px; height: 22px;
  flex-shrink: 0;
  fill: none;
  stroke: var(--gold);
  stroke-width: 1.4;
  stroke-linejoin: round;
}
.brand-mark-rib { opacity: 0.55; }
.brand-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.brand-name {
  font-weight: 700;
  font-size: var(--t-md);
  letter-spacing: 0.015em;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.brand-line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.brand-site {
  color: var(--ink-faint);
  font-size: var(--t-sm);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.brand-owner {
  flex-shrink: 0;
  font-size: var(--t-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--bone);
  padding: 1px 6px;
  border: 1px solid #5c4626;
  border-radius: var(--r-sm);
  white-space: nowrap;
}
.brand-owner:empty { display: none; }

/* ---- meters ------------------------------------------------------------- */

.meters {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}
.meter {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 4px 8px;
  border-radius: var(--r);
  position: relative;
}
.meter + .meter::before {
  content: "";
  position: absolute;
  left: -2px; top: 20%; bottom: 20%;
  width: 1px;
  background: var(--line);
}
.meter-mark {
  width: 16px; height: 16px;
  flex-shrink: 0;
  fill: none;
  stroke: var(--ink-faint);
  stroke-width: 1.3;
  stroke-linecap: round;
}
.meter-text { display: flex; flex-direction: column; gap: 1px; }
.meter-label {
  color: var(--ink-faint);
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  line-height: 1;
}
.meter-row { display: flex; align-items: center; gap: 7px; line-height: 1.1; }
.meter-value {
  font-weight: 700;
  font-size: var(--t-md);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.meter.is-funds .meter-mark { stroke: var(--gold); }
.meter.is-funds .meter-value { color: var(--gold); }

/* The change, flashed once beside the figure and then gone. */
.meter-delta {
  font-size: var(--t-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  opacity: 0;
  animation: delta-pop 1.5s ease-out;
}
.meter-delta.is-up { color: var(--good); }
.meter-delta.is-down { color: var(--bad); }
@keyframes delta-pop {
  0%   { opacity: 0; transform: translateY(5px); }
  14%  { opacity: 1; transform: translateY(0); }
  70%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-5px); }
}

/* Stamina reads as eight stretches of work, not as a percentage. */
.meter-bar .bar {
  position: relative;
  width: 78px; height: 8px;
  background: #120e14;
  border: 1px solid #0b080c;
  border-radius: var(--r-sm);
  overflow: hidden;
}
.meter-bar .bar i {
  display: block; height: 100%;
  width: 100%;
  background: linear-gradient(90deg, #4f9e6d, #7ee0a2);
  transition: width 0.18s ease;
}
.meter-bar .bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    90deg,
    transparent 0 calc(12.5% - 1px),
    rgba(10, 8, 12, 0.85) calc(12.5% - 1px) 12.5%
  );
  pointer-events: none;
}
.meter.is-low .bar i { background: linear-gradient(90deg, #a35b3e, var(--bad)); }
.meter.is-low .meter-mark { stroke: var(--bad); }
.meter.is-low .meter-value { color: var(--bad); }

/* ---- top-right actions -------------------------------------------------- */

.topbar-actions { display: flex; align-items: center; gap: 3px; margin-left: 10px; }
.topbar-rule { width: 1px; height: 22px; background: var(--line); margin: 0 3px; }
.topbar-actions .btn svg {
  width: 15px; height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.35;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.btn-icon, .btn-rest {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.btn-rest kbd {
  font: inherit;
  font-size: var(--t-xs);
  opacity: 0.65;
  padding: 0 3px;
  border: 1px solid currentColor;
  border-radius: 3px;
}
.btn-round {
  width: 30px;
  padding: 0;
  height: 30px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--t-md);
}

/* ---- buttons ------------------------------------------------------------ */

/* A KEY, NOT A RECTANGLE.

   These were a one-pixel border, a soft radius and a gradient -- the shape a
   form control takes on a settings page, on a screen that is otherwise pixel
   art inside a pixel frame. Every part of the new treatment is doing one job:

     TWO PIXELS OF NEAR-BLACK, the same edge the wordmark carries, so the
     button belongs to the same drawing as the logo above it.

     A HARD OFFSET SHADOW AND NO BLUR. Blur is the thing that makes a control
     look like software; a solid block of dark under the bottom edge makes it
     look like a key standing off the surface. It is also the half that makes
     the press real -- without something to travel into, a button has nowhere
     to go.

     SQUARE. A pixel frame cannot be rounded (the note by `--frame` says why),
     and neither can anything sitting inside one. */
.btn {
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink);
  background: var(--bg-raise);
  border: 2px solid var(--blood-line);
  border-radius: 0;
  padding: 8px 14px;
  cursor: pointer;
  box-shadow: 0 3px 0 #0a0806;
  transition: background 0.12s, transform 0.05s, box-shadow 0.05s;
}
.btn:hover { background: #3b2e26; }
/* THE TRAVEL AND THE SHADOW MOVE TOGETHER, and by the same amount. The button
   drops three pixels and its shadow shrinks by three, so the bottom edge stays
   exactly where it was and nothing below the button shifts. Getting these two
   numbers out of step is what makes a pressed button look like it fell over. */
.btn:active { transform: translateY(3px); box-shadow: 0 0 0 #0a0806; }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-quiet { background: transparent; color: var(--ink-dim);
             border-color: var(--line); box-shadow: none; }
.btn-quiet:hover { color: var(--ink); background: var(--bg-raise); }
.btn-quiet:active { transform: translateY(1px); box-shadow: none; }

/* THE FLAT GOLD IS THE POINT. A vertical gradient reads as a glossy web button
   and fought the flat sprites beside it; one colour with a darker block under
   it is how a pixel game draws a raised surface. */
.btn-primary {
  background: var(--blood);
  border-color: var(--blood-line);
  color: var(--bone);
  box-shadow: 0 3px 0 var(--blood-deep);
}
.btn-primary:hover { background: #e83544; }
.btn-primary:active { transform: translateY(3px); box-shadow: 0 0 0 var(--blood-deep); }

/* THE BLOOD RED, FOR THE ONE ACTION A SCREEN IS ACTUALLY FOR. Gold is already
   spent on every ordinary affirmative here, so the thing that starts the race
   needed a register above it -- and the wordmark had the colour waiting. Used
   sparingly on purpose: a screen with two of these has neither. */
.btn-blood {
  background: var(--blood);
  border-color: var(--outline);
  color: var(--bone);
  text-transform: uppercase;
  letter-spacing: .09em;
  box-shadow: 0 3px 0 #6d0f18;
}
.btn-blood:hover { background: #ea3040; }
.btn-blood:active { transform: translateY(3px); box-shadow: 0 0 0 #6d0f18; }

.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn:disabled:active { transform: none; }
.btn.is-urgent {
  border-color: var(--warn);
  color: var(--warn);
  animation: nudge 1.6s ease-in-out infinite;
}
@keyframes nudge {
  0%, 100% { box-shadow: 0 0 0 0 rgba(224, 163, 90, 0); }
  50%      { box-shadow: 0 0 0 4px rgba(224, 163, 90, 0.18); }
}

/* ---- stage -------------------------------------------------------------- */

.stage-wrap {
  grid-area: stage;
  position: relative;
  overflow: hidden;
  /* What shows through before the first frame of ground. See the layer note. */
  background: #17120c;
}
/* STACKED, NOT SIDE BY SIDE. `world` is the GPU surface and `stage` is the 2D
   overlay over it -- see the note in `play.html`. Both fill the wrapper, which
   is already `position: relative`.

   THE OVERLAY TAKES THE POINTER AND THE WORLD DOES NOT. Every click, drag and
   hover in this game is handled against `#stage`, and adding a second canvas
   over the top would have silently stolen all of it. `pointer-events: none` on
   the lower one keeps input exactly where it already is, so nothing that reads
   the mouse has to know this happened. */
/* TWO LAYERS: the GPU draws the GROUND, the 2D canvas draws everything standing
   on it. There was briefly a third for the sky, because a backdrop painted on
   `stage` would have covered the ground the GPU had just drawn -- and then the
   sky was removed altogether, because the view is always on the level and the
   park's own boundary cliff frames every edge. No backdrop, nothing to sit over,
   two layers.
   The wrapper carries the colour that shows through wherever the ground does
   not reach, which on a framed park is nowhere -- it is there so that a frame
   drawn before the ground arrives is not a flash of white.
   With the GPU path off, `world` is a transparent canvas of zero cost behind
   one that covers it completely. */
#world, #stage {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%; height: 100%;
}
#world { pointer-events: none; }
/* Above both canvases, blending with what they drew. See the note in the markup. */
#tint {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: multiply;
  background: transparent;
}
#stage { cursor: crosshair; background: transparent; }
#stage.is-panning { cursor: grabbing; }

.context {
  position: absolute;
  top: 12px; left: 12px;
  display: flex; align-items: baseline; gap: 10px;
  padding: 8px 13px;
  background: rgba(12, 18, 24, 0.82);
  border: 1px solid var(--line);
  border-radius: var(--r);
  backdrop-filter: blur(6px);
  pointer-events: none;
  max-width: min(560px, 70%);
}
.context-layer { font-weight: 700; }
.context-dates { color: var(--ink-faint); font-size: var(--t-sm); }
.context-msg {
  color: var(--ink-dim);
  font-size: var(--t-sm);
  border-left: 1px solid var(--line);
  padding-left: 10px;
}

/* ---- toolbar ------------------------------------------------------------ */

.toolbar-wrap {
  position: absolute;
  left: 50%; bottom: 16px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  pointer-events: none;
}

/* One caption for the whole row: what you are pointing at, or what you hold. */
.tool-caption {
  max-width: 460px;
  padding: 5px 13px;
  border-radius: 999px;
  background: rgba(28, 21, 16, 0.88);
  border: 1px solid var(--line);
  backdrop-filter: blur(6px);
  font-size: var(--t-sm);
  line-height: 1.4;
  color: var(--ink-faint);
  text-align: center;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.15s, transform 0.15s;
}
.tool-caption.is-on { opacity: 1; transform: translateY(0); }
.tool-caption b { color: var(--ink); font-weight: 700; margin-right: 6px; }
.tool-caption em { color: var(--gold); font-style: normal; }

/* The caption is normally click-through, because it floats over the board and
   the board is what you are aiming at. It only takes the mouse when it is
   actually holding a control. */
.tool-caption.has-act { pointer-events: auto; max-width: 560px; }
.cap-act {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--line);
}
.cap-n { color: var(--gold); font-style: normal; font-weight: 700; font-variant-numeric: tabular-nums; }
.cap-hint { color: var(--ink-faint); }
.cap-act .btn { border-color: var(--line); }

.toolbar {
  display: flex;
  gap: 4px;
  padding: 7px;
  background: rgba(28, 21, 16, 0.88);
  border: 1px solid var(--line);
  border-radius: 15px;
  backdrop-filter: blur(6px);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.45);
  pointer-events: auto;
}
.tool {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  width: 86px;
  padding: 8px 7px 7px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r);
  color: var(--ink-dim);
  font: inherit;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.1s;
}
.tool:hover { background: #2e242d; color: var(--ink); }
.tool:hover .tool-icon { transform: translateY(-1px) rotate(-3deg); }

.tool-icon {
  display: block;
  line-height: 0;
  transition: transform 0.14s, filter 0.14s;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}
.tool-icon canvas { display: block; }
.tool-name {
  font-weight: 700;
  font-size: var(--t-xs);
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* The chip in the corner: a key when you own it, a price when you do not,
   a count when it is something you spend. */
.tool-tag {
  position: absolute;
  top: 4px; right: 5px;
  min-width: 15px;
  padding: 0 4px;
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 15px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  background: rgba(255, 255, 255, 0.06);
}
.tool-tag.is-price { color: #1b1405; background: var(--gold); }

/* Opposite corner from the key chip, so the two never argue: how many flags
   are out on this section right now. Hidden at zero, because a counter that
   reads nothing all game teaches you to stop looking at that corner. */
.tool-count {
  position: absolute;
  bottom: 4px; left: 5px;
  min-width: 15px;
  padding: 0 4px;
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 15px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: #1b1405;
  background: var(--gold);
}
.tool-count[hidden] { display: none; }

.tool.is-active {
  background: linear-gradient(180deg, #46353f, #352831);
  border-color: var(--gold);
  color: var(--ink);
  box-shadow: 0 0 0 1px rgba(232, 180, 78, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.07);
}
.tool.is-active .tool-icon { filter: drop-shadow(0 0 5px rgba(232, 180, 78, 0.45)); }
.tool.is-active .tool-tag { color: #1b1405; background: var(--gold); }

/* Locked: the tool is greyed out, but still legible — you are meant to want it. */
.tool.is-locked { opacity: 0.62; }
.tool.is-locked .tool-icon { filter: grayscale(0.85) brightness(0.8); }
.tool.is-locked:hover { opacity: 1; }
.tool.is-locked:hover .tool-icon { filter: none; }

/* ---- toasts ------------------------------------------------------------- */

.toasts {
  position: absolute;
  top: 12px; right: 12px;
  display: flex; flex-direction: column; gap: 6px;
  align-items: flex-end;
  pointer-events: none;
  /* Above every full-screen overlay.
   *
   * Without this the toasts sat at the default level and the screens sat at
   * 40-60, so every message the operator's console raised -- errors as much as
   * confirmations -- was painted BEHIND the console that raised it. A button
   * that fails silently reads as a button that does nothing, and that is
   * exactly how it was reported. */
  z-index: 90;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  max-width: 340px;
  padding: 8px 12px 8px 10px;
  border-radius: var(--r);
  background: rgba(28, 21, 16, 0.92);
  border: 1px solid var(--line);
  border-left-width: 3px;
  font-size: var(--t-sm);
  line-height: 1.4;
  backdrop-filter: blur(6px);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  animation: toast-in 0.2s cubic-bezier(0.2, 0.9, 0.3, 1.2);
  transition: opacity 0.3s, transform 0.3s;
}
/* Drifting right on the way out. In the top-right stack that read as sliding
   back off the edge it came from; in the broadcast lane it is the same twelve
   pixels reading as being pushed off the end of the ticker. One rule, honest in
   both places, which is why it is not overridden below. */
.toast.is-going { opacity: 0; transform: translateX(12px); }
.toast-mark {
  flex-shrink: 0;
  width: 15px; height: 15px;
  margin-top: 1.5px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
}
.toast.good { border-left-color: var(--good); }
.toast.good .toast-mark { color: var(--good); }
.toast.warn { border-left-color: var(--warn); }
.toast.warn .toast-mark { color: var(--warn); }
.toast.bad  { border-left-color: var(--bad); }
.toast.bad .toast-mark { color: var(--bad); }
.toast.find { border-left-color: var(--gold); }
.toast.find .toast-mark { color: var(--gold); }
.toast.info { border-left-color: var(--accent); }
.toast.info .toast-mark { color: var(--accent); }
@keyframes toast-in { from { opacity: 0; transform: translateX(14px) scale(0.96); } }
/* The same move, mirrored, for the bottom lane. See `body.is-broadcast .toast`. */
@keyframes toast-in-lane { from { opacity: 0; transform: translateX(-18px) scale(0.96); } }

/* ---- side panel --------------------------------------------------------- */

.panel {
  position: fixed;
  top: var(--top-h, 40px);
  bottom: 0;
  right: var(--rail-w);            /* clear of the rail, which stays on top */
  width: min(372px, calc(100vw - var(--rail-w) - 16px));
  z-index: 38;
  background: var(--bg-panel);
  border-left: 1px solid var(--line);
  box-shadow: -18px 0 40px rgba(0, 0, 0, 0.45);
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Off-stage, and `visibility` as well as the transform so nothing inside it
     can be tabbed into while it is shut. */
  transform: translateX(calc(100% + var(--rail-w)));
  visibility: hidden;
  transition: transform 0.18s ease, visibility 0s linear 0.18s;
  /* A canvas carries its pixel width as intrinsic min-content, which would
     otherwise let the section drawing shove the whole grid column wider. */
  min-width: 0;
}

/* The edge rail. Six icons, forty pixels wide, and it is the only piece of the
   old panel that is on screen when you are working. */
.tabs {
  position: fixed;
  right: 0;
  top: var(--top-h, 40px);
  bottom: 0;
  z-index: 40;
  width: var(--rail-w);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 6px 0;
  gap: 2px;
  background: linear-gradient(90deg, rgba(24,19,15,0), rgba(24,19,15,0.82) 40%, rgba(28,22,17,0.94));
  border-left: 1px solid var(--line);
}
.tab {
  flex: 0 0 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 9px 2px;
  background: none;
  border: none;
  /* The mark of the open tab moves from an underline to a bar on the outer
     edge, which is the edge you are looking at. */
  border-right: 2px solid transparent;
  color: var(--ink-faint);
  font: inherit;
  cursor: pointer;
  transition: color 0.12s, background 0.12s, border-color 0.12s;
}
/* The words go. Every one keeps its title, so the name is a hover away. */
.tab-label { display: none !important; }
.tab:hover { color: var(--ink-dim); background: rgba(255, 255, 255, 0.03); }
.tab.is-active {
  color: var(--gold);
  border-right-color: var(--gold);
  background: rgba(232, 180, 78, 0.09);
}
.tab-mark { position: relative; display: block; line-height: 0; }
.tab-mark svg {
  width: 19px; height: 19px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.35;
  stroke-linecap: round;
  opacity: 0.9;
}
.tab.is-active .tab-mark svg { stroke-width: 1.6; opacity: 1; color: var(--gold); }
.tab-label {
  font-size: var(--t-sm);
  font-weight: 700;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The count rides the corner of the mark rather than pushing the label about. */
.pip {
  position: absolute;
  top: -5px; right: -9px;
  min-width: 15px;
  padding: 0 4px;
  border-radius: var(--r);
  background: var(--bad);
  color: #24100c;
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 15px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--bg-panel);
  transition: opacity 0.15s, transform 0.15s;
}
.tab .pip.is-zero { opacity: 0; transform: scale(0.6); }
.tab[data-tab="finds"] .pip,
.tab[data-tab="record"] .pip,
.tab[data-tab="claims"] .pip { background: var(--gold); color: #1b1405; }

.tab-body {
  display: none;
  padding: var(--pad);
  overflow-y: auto;
  min-height: 0;
  min-width: 0;
  flex: 1;
}
.tab-body.is-active { display: block; }

.lede {
  margin: 0 0 12px;
  font-size: var(--t-md);
  line-height: 1.5;
  color: var(--ink-dim);
}
.sub {
  margin: 20px 0 8px;
  font-size: var(--t-sm);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-faint);
}

/* ---- section column ----------------------------------------------------- */

/* The section drawing is a canvas, sunk into the panel like a plate in a
   report — a hairline frame and a little shadow, nothing decorative. */
.section-wrap {
  min-width: 0;
  overflow: hidden;
  padding: 4px 6px 6px;
  background: #1b151d;
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.column { display: block; width: 100%; max-width: 100%; }

.section-key {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 13px;
  margin-top: 7px;
  font-size: var(--t-xs);
  color: var(--ink-faint);
}
.section-key span { display: inline-flex; align-items: center; gap: 5px; }
.key-swatch {
  width: 11px; height: 3px;
  border-radius: 2px;
  flex-shrink: 0;
}
.key-opened { background: var(--gold); }
.key-cut {
  height: 0;
  border-top: 2px dashed var(--gold);
  border-radius: 0;
}
.key-gap { background: var(--bad); height: 4px; }

.note-lith {
  font-size: var(--t-sm);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-faint);
}

.layer-note {
  margin-top: 10px;
  padding: 11px 12px;
  background: var(--bg-raise);
  border-radius: var(--r);
  font-size: var(--t-md);
  line-height: 1.55;
  color: var(--ink-dim);
}
.layer-note b { color: var(--ink); }

/* ---- log ---------------------------------------------------------------- */

.log { list-style: none; margin: 0; padding: 0; }
.log li {
  padding: 5px 0 5px 10px;
  border-left: 2px solid var(--line);
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-dim);
}
.log li.find { border-left-color: var(--gold); color: var(--ink); }
.log li.good { border-left-color: var(--good); }
.log li.bad  { border-left-color: var(--bad); color: #f0b3a9; }
.log li.layer { border-left-color: var(--accent); }

/* ---- finds -------------------------------------------------------------- */

.finds { display: flex; flex-direction: column; gap: 9px; }
.find-card {
  display: grid;
  grid-template-columns: 38px 1fr;
  gap: 10px;
  padding: 10px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.find-card.is-catalogued { border-color: #4d6a4f; }
/* A grid child will not shrink below its min-content unless told to, and one
   nowrap date range in the meta line is enough to shove the whole panel wide. */
.find-card > * { min-width: 0; }
.find-title { font-weight: 700; font-size: var(--t-md); overflow-wrap: anywhere; }
.find-meta {
  font-size: var(--t-md);
  color: var(--ink-faint);
  margin-top: 1px;
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.cond {
  display: flex; align-items: center; gap: 7px;
  margin: 7px 0 8px;
  font-size: var(--t-sm);
}
.cond .bar {
  flex: 1; height: 6px;
  background: #0d1319;
  border-radius: 3px;
  overflow: hidden;
}
.cond .bar i { display: block; height: 100%; }
.find-actions { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.price {
  margin-left: auto;
  font-weight: 700;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.badge {
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 6px;
  border-radius: var(--r-sm);
  background: #2f4a35;
  color: var(--good);
}
.badge-context { background: #2f4152; color: var(--accent); }
.no-context {
  margin: -3px 0 7px;
  font-size: var(--t-sm);
  color: var(--warn);
}

.empty {
  padding: 26px 14px;
  text-align: center;
  color: var(--ink-faint);
  font-size: var(--t-sm);
  line-height: 1.5;
}

/* ---- museum ------------------------------------------------------------- */

.survey {
  display: flex; align-items: center; gap: 12px;
  padding: 12px;
  margin-bottom: 14px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.survey.is-done { border-color: #3d6a52; }
.survey-title { font-weight: 700; font-size: var(--t-md); }
.survey-sub {
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-faint);
  margin-top: 3px;
}
.survey .btn { margin-left: auto; flex-shrink: 0; }

.features, .claims { display: flex; flex-direction: column; gap: 9px; }

.museum-head {
  display: flex; align-items: center; gap: 12px;
  padding: 13px;
  background: var(--bg-raise);
  border-radius: var(--r);
}
.museum-total {
  font-size: var(--t-xl);
  font-weight: 700;
  color: var(--gold);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.museum-sub { font-size: var(--t-sm); color: var(--ink-faint); margin-top: 3px; }
.museum-head .btn { margin-left: auto; }
.progress-line {
  margin-top: 10px;
  font-size: var(--t-sm);
  color: var(--ink-dim);
}
.sold { list-style: none; margin: 0; padding: 0; }
.sold li {
  display: flex; gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-sm);
  color: var(--ink-dim);
}
.sold li b { color: var(--ink); font-weight: 600; }
.sold .paid { margin-left: auto; color: var(--gold); font-variant-numeric: tabular-nums; }

/* ---- dialogs ------------------------------------------------------------ */

.dialog {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--bg-panel);
  color: var(--ink);
  padding: 0;
  max-width: 520px;
  width: calc(100% - 32px);
}
.dialog::backdrop { background: rgba(6, 10, 14, 0.7); }
.dialog-inner { padding: 20px; }
.dialog h2 { margin: 0 0 4px; font-size: var(--t-xl); }

.quiz-head { display: flex; gap: 13px; align-items: center; }
.quiz-swatch {
  width: 56px; height: 56px;
  background: #0f161d;
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.quiz-meta { margin: 2px 0 0; font-size: var(--t-sm); color: var(--ink-faint); }
.quiz-blurb {
  margin: 14px 0;
  font-size: var(--t-md);
  line-height: 1.6;
  color: var(--ink-dim);
  border-left: 2px solid var(--line);
  padding-left: 12px;
}
.quiz-q { margin: 0 0 12px; font-weight: 600; line-height: 1.45; }
.quiz-options { display: flex; flex-direction: column; gap: 7px; }
.opt {
  padding: 10px 12px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-md);
  text-align: left;
  line-height: 1.4;
  cursor: pointer;
}
.opt:hover:not(:disabled) { background: #3b2e3a; border-color: #5c4759; }
.opt:disabled { cursor: default; opacity: 0.55; }
.opt.is-right { border-color: var(--good); background: #21372a; opacity: 1; }
.opt.is-wrong { border-color: var(--bad); background: #38221e; opacity: 1; }
.quiz-result {
  margin-top: 14px;
  padding: 12px;
  border-radius: var(--r);
  background: var(--bg-raise);
  font-size: var(--t-sm);
  line-height: 1.6;
}
.quiz-result b { display: block; margin-bottom: 4px; }
.quiz-result.right b { color: var(--good); }
.quiz-result.wrong b { color: var(--warn); }

.dialog-actions {
  display: flex; justify-content: flex-end; gap: 8px;
  margin: 18px 0 0; padding: 0;
}
.help-list { margin: 12px 0; padding-left: 20px; font-size: var(--t-md); line-height: 1.65; color: var(--ink-dim); }
.help-list li { margin-bottom: 9px; }
.help-list b { color: var(--ink); }
.help-keys { font-size: var(--t-sm); color: var(--ink-faint); }
.help-keys b { color: var(--ink-dim); }

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

/* Small screens.
   The `#app` grid override that used to live here dropped the panel into a
   42vh row along the bottom -- correct for the layout this replaced, and
   meaningless now that the panel is a fixed overlay that slides in from behind
   the rail. Left in, it fought the new layout at exactly the widths where
   things were already tight. */
@media (max-width: 900px) {
  .meters { gap: 0; }
  .meter { padding: 4px 7px; }
  .meter-bar .bar { width: 52px; }
  .meter-label { display: none; }
  .brand-name { font-size: var(--t-sm); }
  .toolbar-wrap { bottom: 10px; max-width: 100vw; }
  .tool { width: 70px; }
  .tool-name { font-size: var(--t-xs); }
  .tool-caption { max-width: 82vw; }
}

/* Phone-width. The bar has more in it than a 375px row will hold, so the
   meters drop to their own line and the top-right buttons keep only their
   marks — which is exactly why they were given marks. */
/* Phone width.
   This block used to make the header WRAP and give the meters a line of their
   own -- the right answer for a horizontal tab strip along the top of a panel
   that sat under the board. With the strip now a vertical rail and the header
   set to `nowrap`, the two designs fought: `nowrap` won, the meters kept their
   `width: 100%`, and 530px of meter was placed on a 600px line that already
   had the buttons on it. That is the 254px of overflow, and it was the reason
   FUNDS was being cut off the end at small sizes.
   One design now. The header narrows by dropping things, and it drops them in
   the order set out above. */
@media (max-width: 620px) {
  .topbar { gap: 4px 8px; padding: 5px calc(10px + var(--rail-w)) 5px 10px; }
  .meter { padding: 3px 5px; gap: 6px; }
  .meter-value { font-size: var(--t-md); }
  .meter-bar .bar { width: 44px; }
  .meter + .meter::before { display: none; }
  .topbar-actions .btn { padding: 6px 7px; }
  .btn-rest kbd { display: none; }
  .topbar-rule { display: none; }
  /* The toolbar scrolls rather than shrinking: nine tools squeezed to fit a
     phone are nine things you cannot hit. */
  .toolbar {
    max-width: calc(100vw - var(--rail-w) - 12px);
    overflow-x: auto;
    scrollbar-width: none;
  }
  .toolbar::-webkit-scrollbar { display: none; }
  .tool { flex: 0 0 auto; }
}


/* ---- full-screen overlays: claims office and mercantile ------------------ */

.screen {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(12, 9, 14, 0.94);
  overflow-y: auto;
  backdrop-filter: blur(3px);
}
.screen[hidden] { display: none; }
.screen-inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: 26px 22px 60px;
}
.screen-head {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  margin-bottom: 18px;
}
/* THE SCREEN TITLE, IN THE WORDMARK'S VOICE. Uppercase monospace with the
   letters opened up is what the logo does, and a heading set in the body face
   directly under it made the two look like they came from different products.
   Bone rather than plain ink, for the same reason. */
.screen-head h2 {
  margin: 0 0 6px;
  font: 800 var(--t-xl)/1.1 ui-monospace, monospace;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--bone);
}
.screen-sub {
  margin: 0;
  max-width: 60ch;
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--ink-dim);
}
.screen-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 9px;
  flex-shrink: 0;
}
.portrait {
  width: 72px; height: 72px;
  background: #2a2029;
  border: 1px solid var(--line);
  border-radius: var(--r);
}

.screen-filters { display: flex; gap: 7px; margin-bottom: 16px; }
.chip {
  padding: 5px 13px;
  border-radius: 0;
  border: 2px solid var(--line);
  background: transparent;
  color: var(--ink-faint);
  font: inherit; font-size: var(--t-sm); font-weight: 700;
  cursor: pointer;
  transition: transform 0.05s, box-shadow 0.05s;
}
.chip:hover { color: var(--ink-dim); }
.chip.is-active { background: var(--gold); border-color: var(--outline); color: #1e1508;
                  box-shadow: 0 3px 0 #7a5a14; }
.chip.is-active:active { transform: translateY(3px); box-shadow: 0 0 0 #7a5a14; }

.site-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 13px;
}
.site-card {
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.site-card.is-mine { border-color: #7a6432; }
.site-card.is-open { box-shadow: inset 0 0 0 2px var(--gold); }
.site-thumb {
  display: block;
  width: 100%;
  height: 108px;
  background: #241d28;
  border-bottom: 1px solid var(--line);
}
.site-body { padding: 11px 12px 12px; }
.site-name { font-weight: 700; font-size: var(--t-md); }
.site-where {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--accent);
  margin-top: 1px;
}
.site-meta { font-size: var(--t-sm); color: var(--ink-faint); margin-top: 2px; line-height: 1.5; }
.site-owner {
  display: inline-block;
  margin-top: 7px;
  padding: 2px 7px;
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: #3a2f22;
  color: var(--bone);
}
.site-owner.is-free { background: #2c2a35; color: var(--ink-faint); }
.site-staked {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 7px 0 0 5px;
  padding: 2px 7px 2px 5px;
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: #33291a;
  color: #f0cd78;
  border: 1px solid #5d4a26;
}
.site-staked svg {
  width: 10px;
  height: 10px;
  fill: none;
  stroke: #f0cd78;
  stroke-width: 1.4;
  stroke-linejoin: round;
}
.site-card .find-actions { margin-top: 10px; }

.store-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  gap: 12px;
}
.store-section {
  grid-column: 1 / -1;
  margin: 12px 0 -2px;
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-faint);
}
.store-card {
  display: flex;
  gap: 11px;
  padding: 11px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.store-card.is-worn { border-color: var(--gold); }
.store-card canvas {
  flex-shrink: 0;
  width: 56px; height: 56px;
  background: #2a2029;
  border-radius: var(--r-sm);
}
.store-name { font-weight: 700; font-size: var(--t-md); }
.store-blurb { font-size: var(--t-sm); color: var(--ink-faint); line-height: 1.45; margin: 3px 0 7px; }

/* ---- rival --------------------------------------------------------------- */

.rival-head {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 13px;
  background: var(--bg-raise);
  border-radius: var(--r);
  margin-bottom: 12px;
}
.rival-name { font-weight: 700; font-size: var(--t-md); }
.rival-from { font-size: var(--t-sm); color: var(--ink-faint); margin-top: 2px; }
.rival-score {
  margin-left: auto;
  font-weight: 700;
  font-size: var(--t-xl);
  font-variant-numeric: tabular-nums;
}
.rival-score em { font-style: normal; color: var(--ink-faint); font-size: var(--t-sm); margin: 0 4px; }
#rivalMine { color: var(--good); }
#rivalTheirs { color: var(--bad); }

/* ---- expedition parties and closed claims -------------------------------- */

.whoami {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 9px 12px;
  margin-bottom: 14px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  font-size: var(--t-sm);
}
.whoami-label {
  font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-faint);
}
.whoami code {
  font-size: var(--t-sm);
  padding: 2px 7px;
  border-radius: var(--r-sm);
  background: var(--bg-raise);
  color: var(--bone);
  user-select: all;
}
.whoami-note { color: var(--ink-faint); font-size: var(--t-sm); margin-left: auto; }

.site-card.is-guest { border-color: #3d5a6a; }
.site-card.is-closed { opacity: 0.86; border-style: dashed; }
.site-card.is-closed .site-thumb { filter: grayscale(0.55) brightness(0.72); }

.site-owner.is-guest { background: #24404c; color: var(--accent); }
.site-owner.is-closed { background: #402a2a; color: var(--bad); }

.closed-note {
  font-size: var(--t-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}

.btn-danger {
  background: #4a2320;
  border-color: #7a3a30;
  color: #f0b0a4;
}
.btn-danger:hover:not(:disabled) { background: #5e2c27; border-color: #96483c; }

.party {
  margin-top: 11px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.party-list { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 7px; }
.party-empty { font-size: var(--t-sm); color: var(--ink-faint); line-height: 1.45; }
.party-tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 4px 2px 8px;
  border-radius: 999px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  font-size: var(--t-sm);
}
.party-tag button {
  border: none; background: none; cursor: pointer;
  color: var(--ink-faint); font-size: var(--t-sm); line-height: 1;
  padding: 0 3px;
}
.party-tag button:hover { color: var(--bad); }

.party-add { display: flex; gap: 5px; }
.party-add input {
  flex: 1; min-width: 0;
  padding: 5px 8px;
  background: #1b151c;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--ink);
  font: inherit; font-size: var(--t-sm);
}
.party-add input:focus { outline: none; border-color: var(--gold); }
.party-add .btn { padding: 5px 10px; font-size: var(--t-sm); }


/* ---- dug in -------------------------------------------------------------- */

.stuck {
  position: absolute;
  left: 50%;
  bottom: 122px;
  transform: translateX(-50%);
  max-width: 460px;
  padding: 10px 15px;
  background: rgba(74, 35, 32, 0.95);
  border: 1px solid #96483c;
  border-radius: var(--r);
  font-size: var(--t-sm);
  line-height: 1.55;
  color: #f5ded8;
  text-align: center;
  pointer-events: none;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
}
.stuck[hidden] { display: none; }
.stuck b { display: block; margin-bottom: 2px; color: #ffb4a4; }
.stuck kbd {
  font: inherit;
  padding: 1px 5px;
  border-radius: var(--r-sm);
  background: rgba(0, 0, 0, 0.35);
}


/* ---- type series --------------------------------------------------------- */

.grammar-bar {
  display: flex; flex-wrap: wrap; gap: 8px 20px;
  padding: 10px 13px;
  margin-bottom: 14px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  font-size: var(--t-sm);
  color: var(--ink-dim);
}
.grammar-bar b { color: var(--gold); font-variant-numeric: tabular-nums; }

.type-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 12px;
}
.type-card {
  display: flex; gap: 12px;
  padding: 12px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.type-card.is-authored { border-color: #6d5a32; }
/* The catalogue is pixel art now: never let the browser resample it, and give
   each object a dark ground of its own so a pale bone reads against the card. */
.type-card canvas {
  flex-shrink: 0;
  background: #1e1822;
  border: 1px solid #362c3a;
  border-radius: var(--r-sm);
  image-rendering: pixelated;
}
.find-card canvas { image-rendering: pixelated; }
.type-name { font-weight: 700; font-size: var(--t-md); }
.type-parts {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin: 5px 0 6px;
}
.part {
  font-size: var(--t-xs);
  padding: 1px 6px;
  border-radius: var(--r-sm);
  background: var(--bg-raise);
  color: var(--ink-faint);
}
.part.is-material { color: #9fd0b0; }
.part.is-form { color: #9fbcd8; }
.part.is-trait { color: var(--gold); }
.part.is-authored { color: var(--gold); background: #3a2f22; }
.type-blurb { font-size: var(--t-md); line-height: 1.5; color: var(--ink-dim); }
.type-q {
  margin-top: 7px;
  padding-top: 7px;
  border-top: 1px solid var(--line);
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--ink-faint);
}
.type-q b { color: var(--ink-dim); font-weight: 600; }
.type-value { margin-left: auto; font-weight: 700; color: var(--gold); font-variant-numeric: tabular-nums; }
.type-head { display: flex; align-items: baseline; gap: 8px; }


/* ---- earned cosmetics ---------------------------------------------------- */

.store-card.is-earned { border-style: dashed; border-color: #6b5a3a; }
.earn {
  display: flex; align-items: center; gap: 7px;
  margin: 2px 0 7px;
  font-size: var(--t-xs);
  color: var(--ink-faint);
}
.earn .bar {
  flex-shrink: 0;
  width: 52px; height: 5px;
  background: #17121a;
  border-radius: 3px;
  overflow: hidden;
}
.earn .bar i {
  display: block; height: 100%;
  background: linear-gradient(90deg, #8a6a2c, var(--gold));
}


/* ---- dating by association ----------------------------------------------- */

.dating {
  margin: 9px 0 8px;
  padding: 9px 10px 8px;
  background: #1b151d;
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.dating-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 5px;
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}
.dating-head em {
  margin-left: auto;
  font-style: normal;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: var(--t-xs);
}
.timeline { display: block; width: 100%; max-width: 100%; }

.dating-hint {
  margin-top: 6px;
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-faint);
}
.dating-verdict {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--line);
  font-size: var(--t-sm);
  line-height: 1.45;
}
.dating-verdict.is-right { color: var(--good); }
.dating-verdict.is-wrong { color: var(--bad); }

/* The date line on a feature card, in four states. */
.is-undated { color: var(--ink-faint); font-style: italic; }
.is-provisional { color: var(--accent); }
.is-dated { color: var(--good); font-weight: 600; }
.is-misdated { color: var(--bad); font-weight: 600; text-decoration: line-through; }

/* How useful an object is for dating — the thing the game most wants a player
   to start noticing. Tight is gold; broad is grey and nearly worthless. */
.range { white-space: nowrap; }
.range.is-tight { color: var(--gold); font-weight: 600; }
.range.is-fair  { color: var(--accent); }
.range.is-broad { color: var(--ink-faint); }

/* ---- the quarry ---------------------------------------------------------- */

.quarry-card {
  padding: 12px;
  margin-bottom: 12px;
  background: linear-gradient(180deg, #2b2230, #241d28);
  border: 1px solid #6d5a32;
  border-radius: 12px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.quarry-card.is-done { border-color: #4d6a4f; }

.quarry-head { display: flex; align-items: flex-start; gap: 10px; }
.quarry-name { font-weight: 700; font-size: var(--t-md); line-height: 1.2; }
.quarry-sub { font-size: var(--t-sm); color: var(--ink-faint); margin-top: 2px; }
.quarry-sub .is-bad { color: var(--bad); }
.quarry-worth {
  margin-left: auto;
  font-weight: 700;
  font-size: var(--t-lg);
  color: var(--gold);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.quarry-map {
  display: block;
  width: 100%;
  max-width: 100%;
  margin: 9px 0 8px;
  background: #1b151d;
  border: 1px solid var(--line);
  border-radius: var(--r);
}

.quarry-note {
  padding: 8px 10px;
  background: #1b151d;
  border-left: 2px solid var(--accent);
  border-radius: 0 7px 7px 0;
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--ink-dim);
}
.quarry-note b { color: var(--ink); }
.quarry-note.is-done { border-left-color: var(--good); }

/* One row per element met. The list is the finds tray beside the map. */
.bone-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 9px 0 8px;
  max-height: 232px;
  overflow-y: auto;
}
.bone-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 6px;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.03);
}
.bone-row > div { min-width: 0; flex: 1; }
.bone-row canvas { flex-shrink: 0; }
.bone-row.is-out { opacity: 0.55; }
.bone-row.is-wrecked { opacity: 0.5; background: rgba(224, 112, 94, 0.08); }
.bone-row.is-diagnostic { box-shadow: inset 2px 0 0 var(--gold); }
.bone-name { font-size: var(--t-sm); font-weight: 600; }
.bone-state { font-size: var(--t-sm); color: var(--ink-faint); }
.bone-row.is-wrecked .bone-state { color: var(--bad); }

.btn-tiny { padding: 3px 8px; font-size: var(--t-sm); flex-shrink: 0; }

.quarry-actions {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  margin-top: 9px;
}
.plaster {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 6px 3px 9px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  font-size: var(--t-sm);
  color: var(--ink-faint);
}
.plaster b { color: var(--ink); font-variant-numeric: tabular-nums; }

.quarry-breakdown {
  margin-top: 9px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
  font-size: var(--t-sm);
}
.quarry-breakdown > div {
  display: flex;
  gap: 8px;
  padding: 2px 0;
  color: var(--ink-dim);
}
.quarry-breakdown b {
  margin-left: auto;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.quarry-breakdown .is-nil { color: var(--ink-faint); }
.quarry-breakdown .is-nil b { color: var(--ink-faint); }

/* ---- field survey -------------------------------------------------------- */

.fieldwalk {
  padding: 11px 12px 10px;
  margin-bottom: 12px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.fieldwalk-head { display: flex; align-items: flex-start; gap: 10px; }
.scatter-plot {
  display: block;
  width: 100%;
  max-width: 100%;
  margin: 9px 0 7px;
  background: #16121a;
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.fieldwalk-read {
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--ink-dim);
}
.fieldwalk-read b { color: var(--ink); }
.fieldwalk-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.fieldwalk-bag {
  font-size: var(--t-sm);
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.fieldwalk-actions .btn { margin-left: auto; }

/* ---- the forecast -------------------------------------------------------- */

.sky-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px 5px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--ink-dim);
  white-space: nowrap;
}
.sky-chip svg {
  width: 16px; height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
  stroke-linecap: round;
}
.sky-chip.is-clear    { color: var(--ink-faint); }
.sky-chip.is-hazy     { color: #c9a86e; border-color: #5c4a30; }
.sky-chip.is-building {
  color: var(--warn);
  border-color: var(--warn);
  background: rgba(224, 163, 90, 0.09);
  animation: nudge 2.2s ease-in-out infinite;
}
.sky-chip.is-storm    { color: var(--bad); border-color: #6b3a33; background: rgba(224, 112, 94, 0.1); }

.btn-rest.is-risky { border-color: var(--warn); color: var(--warn); }

/* A survey anomaly nobody has dug down to yet. */
.find-card.is-indicated { border-color: #6d5a32; border-style: dashed; }
.find-card.is-indicated .find-title { color: var(--gold); }

/* ---- index of survey marks ----------------------------------------------
   Every geophysics reading is drawn in the same flat blue, because that is
   what the instrument actually gives you. The index is what makes seven
   identically coloured smudges tellable apart, so it lives right under the
   survey that produces them. */

.geokey {
  margin-bottom: 14px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}

.geokey-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 12px;
  background: none;
  border: 0;
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.geokey-head:hover { color: var(--ink); }
.geokey-head.is-open { color: var(--ink); }

.geokey-mark {
  width: 16px; height: 16px;
  flex-shrink: 0;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.geokey-title { flex: 1; }

.geokey-count {
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.geokey-count.is-live { color: var(--accent); }

.geokey-chev {
  width: 14px; height: 14px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.16s ease;
}
.geokey-head.is-open .geokey-chev { transform: rotate(180deg); }

.geokey-body { padding: 0 12px 11px; }

.geokey-lede, .geokey-foot {
  margin: 0 0 10px;
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--ink-faint);
}
.geokey-lede b { color: var(--ink-dim); }
.geokey-foot {
  margin: 10px 0 0;
  padding-top: 9px;
  border-top: 1px solid var(--line);
  font-style: italic;
}

.geokey-list { display: flex; flex-direction: column; }

.geokey-row {
  display: flex;
  gap: 10px;
  padding: 9px 0;
  border-top: 1px solid var(--line);
}
.geokey-row:first-child { border-top: 0; padding-top: 0; }

/* The mark sits on soil, because that is where you will meet it. A swatch on
   panel-grey would teach a contrast the board never shows. */
.geokey-pic {
  flex-shrink: 0;
  align-self: flex-start;
  padding: 3px;
  background: linear-gradient(#5f4a36, #4a382a);
  border: 1px solid #2e2219;
  border-radius: var(--r-sm);
  line-height: 0;
}
.geokey-pic canvas { display: block; }

.geokey-text { min-width: 0; }

.geokey-shape {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink);
}
.geokey-is {
  margin-top: 1px;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.02em;
}
.geokey-tip {
  margin-top: 4px;
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-dim);
}

/* A mark the survey is picking up on this ground right now. Says nothing the
   board is not already drawing — it is a tally of what is on screen. */
.geokey-tally {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gold);
  white-space: nowrap;
}
.geokey-row.is-present .geokey-pic { border-color: var(--gold); }

/* ---- the camp ----------------------------------------------------------- */

.camp {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 12px;
  margin-bottom: 12px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.camp.is-pitched { border-color: #5c4626; }
.camp-mark { width: 52px; height: 52px; flex-shrink: 0; }
.camp-body { flex: 1; min-width: 0; }
.camp-title { font-weight: 700; font-size: var(--t-md); }
.camp.is-pitched .camp-title { color: var(--gold); }
.camp-sub {
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-faint);
  margin-top: 3px;
}
.camp .btn { flex-shrink: 0; }

/* ---- the world map ------------------------------------------------------ */

.crew-panel { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); }
.crew-head { display: flex; align-items: flex-start; gap: 16px; }
.crew-head h3 { font-size: var(--t-md); margin: 0 0 3px; }
.crew-wages { margin-left: auto; font-size: var(--t-sm); color: var(--ink-faint); text-align: right; white-space: nowrap; }
.crew-wages b { color: var(--gold); }
.crew-hire { display: flex; flex-wrap: wrap; gap: 12px; margin: 14px 0 4px; }
.hire-card {
  flex: 1 1 220px; padding: 12px; border: 1px solid var(--line);
  border-radius: var(--r); background: var(--bg-panel);
}
.hire-card h4 { margin: 0 0 2px; font-size: var(--t-md); }
.hire-card .hire-tool { font-size: var(--t-sm); color: var(--gold); margin-bottom: 6px; }
.hire-card p { font-size: var(--t-sm); line-height: 1.5; color: var(--ink-faint); margin: 0 0 10px; }
.hire-card .btn { width: 100%; }
.crew-list { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.crew-row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; border: 1px solid var(--line); border-radius: 999px;
  font-size: var(--t-sm); background: var(--bg-panel);
}
.crew-row .who { font-weight: 600; }
.crew-row .viewer { color: #9ad0e0; }
.crew-row .tally { color: var(--ink-faint); font-size: var(--t-sm); }
.crew-row button { background: none; border: 0; color: var(--bad); cursor: pointer; font: inherit; font-size: var(--t-sm); }
.twitch-box { margin-top: 18px; padding-top: 14px; border-top: 1px dashed var(--line); }
.twitch-row { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.twitch-row label { font-size: var(--t-sm); color: var(--ink-faint); }
.twitch-row input {
  padding: 5px 9px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--bg-deep, #17131a); color: var(--ink); font: inherit; font-size: var(--t-sm); min-width: 160px;
}
.twitch-state { font-size: var(--t-xs); color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.06em; }
.twitch-state.is-live { color: var(--good, #7fc08a); }
.twitch-state.is-bad { color: var(--bad); }
/* Somebody is waiting to be let onto the dig.
   On the Store button because that is where the answer is, and pulsing because
   a streamer is looking at the trench and not at the toolbar. It stops as soon
   as the queue is empty, so it is never decoration. */
#btnStore { position: relative; }
.knock-badge {
  position: absolute;
  top: -5px; right: -5px;
  min-width: 16px; height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--gold);
  color: #241d10;
  font-size: var(--t-xs); font-weight: 700; line-height: 16px;
  text-align: center;
  box-shadow: 0 0 0 2px var(--bg-deep, #17131a);
  animation: knock-pulse 1.9s ease-in-out infinite;
}
@keyframes knock-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.16); }
}
@media (prefers-reduced-motion: reduce) { .knock-badge { animation: none; } }

.door-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 14px; }
.door-label { font-size: var(--t-sm); color: var(--ink-faint); }
.door-modes { display: flex; gap: 6px; flex-wrap: wrap; }
.door-modes .chip { font-size: var(--t-sm); padding: 3px 10px; }
.door-queue { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.knock {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px 5px 11px; border: 1px solid var(--gold); border-radius: 999px;
  font-size: var(--t-sm); background: rgba(232,180,78,0.08);
}
.knock .nick { color: #9ad0e0; }
.knock button { border: 0; background: none; font: inherit; font-size: var(--t-sm); cursor: pointer; }
.knock .yes { color: var(--gold); font-weight: 700; }
.knock .no { color: var(--ink-faint); }
.door-invite { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.door-invite input {
  padding: 4px 8px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--bg-deep, #17131a); color: var(--ink); font: inherit; font-size: var(--t-sm); min-width: 130px;
}
.door-invite .who-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px; border: 1px solid var(--line); border-radius: 999px; font-size: var(--t-sm);
}
.door-invite .who-chip button { border: 0; background: none; color: var(--bad); cursor: pointer; font: inherit; }
.crowded { color: var(--bad); }

.twitch-cmds { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 10px; font-size: var(--t-sm); color: var(--ink-faint); }
.twitch-cmds code { color: var(--gold); }

.home-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 10px 4px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-panel);
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
}
.home-chip:hover { border-color: var(--gold); color: var(--ink); }
.home-chip.is-armed { border-color: var(--gold); color: var(--gold); }
/* The town chip sits beside the camp chip and is deliberately the cooler of the
   two: your camp is yours, Lodore is everybody's. */
.home-chip.is-town .home-arrow { color: var(--accent); }
.home-chip.is-town:hover { border-color: var(--accent); }
.home-chip.is-town.is-here { border-color: #3d5a63; color: var(--ink-faint); }
.home-arrow {
  font-size: var(--t-md);
  line-height: 1;
  color: var(--gold);
  min-width: 12px;
  text-align: center;
}

.worldmap {
  margin: 14px 0 16px;
  padding: 12px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.worldmap-head {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 10px;
}
.worldmap-title { font-weight: 700; font-size: var(--t-md); }
.worldmap-sub {
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-faint);
  margin-top: 3px;
  max-width: 62ch;
}
.worldmap-key {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-left: auto;
  font-size: var(--t-sm);
  color: var(--ink-faint);
  white-space: nowrap;
}
.worldmap-key span { display: inline-flex; align-items: center; gap: 5px; }
.mk {
  width: 11px; height: 11px;
  border-radius: 2px;
  display: inline-block;
  background: #7a6f58;
}
.mk-mine   { background: rgba(232,180,78,0.3); border: 2px solid var(--gold); }
.mk-theirs { background: transparent; border: 2px solid var(--bad); }
.mk-open   { background: transparent; border: 1px dashed rgba(255,255,255,0.7); }
.mk-river  { background: #3f6b78; }
.mk-quarry { background: #a8703f; }

.worldmap-side { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.worldmap-scale { display: flex; gap: 6px; }
.worldmap-scale .chip { font-size: var(--t-sm); padding: 3px 10px; }
.mk-rim { background: #6a6055; }
.mk-road { background: #d8c69a; }

.worldmap-canvas {
  display: block;
  width: 100%;
  height: 340px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  cursor: crosshair;
}
.worldmap-canvas.is-country { height: 992px; cursor: pointer; }
.worldmap-read {
  margin-top: 9px;
  min-height: 34px;
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-dim);
}
.worldmap-read b { color: var(--ink); }
.worldmap-read .is-stake { color: var(--gold); font-weight: 700; }
.worldmap-read .is-closed { color: var(--bad); }

/* ---- the mailbox -------------------------------------------------------- */

.postbox {
  margin-bottom: 12px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.postbox.has-post { border-color: #7d4a2c; }
.postbox-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 12px;
  background: none;
  border: 0;
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.postbox-head:hover { color: var(--ink); }
.postbox-flag {
  width: 8px; height: 14px;
  border-radius: 1px;
  background: var(--line);
  flex-shrink: 0;
}
.postbox.has-post .postbox-flag { background: #e0552f; }
.postbox-title { flex: 1; }
.postbox-count { font-size: var(--t-xs); color: var(--ink-faint); font-weight: 600; }
.postbox.has-post .postbox-count { color: #e0552f; }
.postbox-list {
  list-style: none;
  margin: 0;
  padding: 0 12px 10px;
  max-height: 190px;
  overflow-y: auto;
}
.postbox-list li {
  padding: 7px 0;
  border-top: 1px solid var(--line);
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-dim);
}
.postbox-list li.is-new { color: var(--ink); }
.postbox-list .post-where {
  display: block;
  font-size: var(--t-sm);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 2px;
}

/* ---------------------------------------------------------------------------
   Talking to somebody

   Sits over the board, low and wide, with the room still visible behind it.
   Every other panel in this game replaces the view; this one must not, because
   the point of walking into the mercantile is that you are IN the mercantile.
   --------------------------------------------------------------------------- */
.talk {
  position: absolute;
  left: 50%;
  bottom: 118px;
  transform: translateX(-50%);
  width: min(560px, calc(100% - 32px));
  background: rgba(26, 21, 28, 0.96);
  border: 1px solid #5c4626;
  border-radius: var(--r);
  padding: 12px 14px 13px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
  z-index: 6;
}
.talk[hidden] { display: none; }
.talk-who { display: flex; align-items: center; gap: 11px; }
.talk-face {
  width: 46px; height: 46px;
  image-rendering: pixelated;
  background: #221c26;
  border: 1px solid #4a3d2c;
  border-radius: var(--r-sm);
  flex: none;
}
.talk-name { font-weight: 700; font-size: var(--t-md); color: var(--bone); }
.talk-role {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-faint);
}
.talk-x {
  margin-left: auto;
  background: none; border: 0; cursor: pointer;
  color: var(--ink-faint); font-size: var(--t-xl); line-height: 1; padding: 2px 6px;
}
.talk-x:hover { color: var(--ink); }
.talk-line {
  margin: 9px 0 10px;
  font-size: var(--t-md); line-height: 1.55;
  color: #e6dcc8;
}
.talk-options { display: flex; flex-wrap: wrap; gap: 7px; }
.talk-options .btn { font-size: var(--t-sm); }

/* ---------------------------------------------------------------------------
   A keepsake box, opened

   Deliberately quiet and narrow. Everything else on screen is a dashboard; this
   is a page, and it is meant to be read at reading width rather than scanned.
   --------------------------------------------------------------------------- */
.keepsake-inner { max-width: 720px; }
.kp-owner {
  display: flex;
  gap: 16px;
  align-items: center;
  padding: 14px 16px;
  margin: 4px 0 16px;
  background: #241d28;
  border: 1px solid #4a3d2c;
  border-radius: var(--r);
}
.kp-tin {
  width: 72px; height: 72px;
  image-rendering: pixelated;
  background: #1b1620;
  border: 1px solid #3a2f26;
  border-radius: var(--r);
  flex: none;
}
.kp-name { font-size: var(--t-lg); font-weight: 700; color: var(--bone); }
.kp-lived {
  font-size: var(--t-xs); font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--ink-faint); margin-top: 2px;
}
.kp-was { font-size: var(--t-sm); color: var(--ink-dim); margin-top: 5px; font-style: italic; }

.kp-contents { list-style: none; margin: 0 0 18px; padding: 0; counter-reset: kp; }
.kp-contents li {
  counter-increment: kp;
  position: relative;
  padding: 9px 0 9px 34px;
  border-bottom: 1px solid var(--line);
}
.kp-contents li:last-child { border-bottom: 0; }
.kp-contents li::before {
  content: counter(kp);
  position: absolute; left: 0; top: 10px;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  background: #33291a; color: #f0cd78;
  border-radius: 50%;
  font-size: var(--t-xs); font-weight: 700;
}
.kp-item { font-weight: 700; font-size: var(--t-md); color: var(--ink); }
.kp-says { font-size: var(--t-md); color: var(--ink-dim); line-height: 1.6; margin-top: 2px; }

.kp-reading {
  padding: 14px 16px;
  background: #1f2a2c;
  border: 1px solid #3d5a63;
  border-radius: var(--r);
}
.kp-reading h3 {
  margin: 0 0 6px;
  font-size: var(--t-xs); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--accent);
}
.kp-reading p { margin: 0; font-size: var(--t-md); line-height: 1.65; color: #dcd3c2; }

.kp-caveat {
  margin: 14px 0 0;
  font-size: var(--t-sm); line-height: 1.6;
  color: var(--ink-faint);
  border-left: 2px solid #5c4626;
  padding-left: 10px;
}

/* A folded repeat in the notebook. Quiet: it is a footnote on the line, not a
   badge competing with it. */
.log-again {
  margin-left: 7px;
  padding: 1px 6px;
  border-radius: 999px;
  background: #2c231c;
  color: var(--ink-faint);
  font-size: var(--t-xs);
  font-weight: 700;
}

/* ---------------------------------------------------------------------------
   Letting the panels breathe

   Measured before the pass: twenty-three interactive controls and sixty-eight
   separate pieces of text visible at once. That is a cockpit. Most of it has to
   stay -- it is all doing a job -- so what changes is the spacing between
   things, which is what decides whether a dense screen reads as busy or as
   calm.
   --------------------------------------------------------------------------- */
.tab-body { line-height: var(--lh); }
.lede { line-height: var(--lh); }
#log li { padding: 7px 0; line-height: 1.55; }
.site-meta, .type-blurb, .find-meta { line-height: 1.55; }

/* The grubstake, while it is owed. Warm rather than alarming -- a debt you are
   working off is not a failure state, and the top bar should not shout at
   somebody for taking the only route the game offered them. */
.meter.is-owing .meter-value { color: var(--warn); }
.meter.is-owing .meter-mark { stroke: var(--warn); }
.meter[hidden] { display: none; }

/* A repeated message counts up rather than stacking up, and once it has been
   said three times it stops expiring -- you are not being informed any more,
   you are being blocked, and it should stay put while you deal with it. */
.toast-count {
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.28);
  font-size: var(--t-xs);
  font-weight: 700;
  opacity: 0.85;
}
.toast.is-stuck { border-color: var(--warn); }

/* ---------------------------------------------------------------------------
   The learning moment

   The rarest event in the game, and it was presented as a form: a grey button
   beside the button that sells the object, then a paragraph. Breaking into a
   new soil layer throws sixteen particles; understanding something threw none.

   The whole budget here is a bigger look at the object, one beat before the
   reason arrives, and one sound heard nowhere else. No confetti, no score, no
   progress figure -- this is a game about patience and over-decorating its
   thesis would cheapen it.
   --------------------------------------------------------------------------- */
.quiz-swatch {
  width: 96px; height: 96px;
  image-rendering: pixelated;
  background: #1e1822;
  border: 1px solid #3a2f26;
  border-radius: var(--r);
}
.quiz-result {
  opacity: 0;
  transform: translateY(3px);
  transition: opacity .34s ease, transform .34s ease;
}
.quiz-result.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .quiz-result { transition: none; }
}

/* A worked example where the empty bag used to be. */
.find-card.is-sample { opacity: 0.5; pointer-events: none; margin-top: 10px; }
.find-card.is-sample .sample-swatch { flex-shrink: 0; }
.sample-note {
  margin-top: 8px;
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-faint);
}

/* ---------------------------------------------------------------------------
   Reduced motion, honoured properly

   Somebody who has asked their system for less movement has asked for a
   reason, and a cozy game of all things should take that at face value. The
   board keeps animating -- it is the game -- but nothing in the CHROME slides,
   fades or eases.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* A visible focus ring, on the keyboard only. There was none: a player working
   without a mouse could not see where they were. */
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
.btn:focus-visible, .chip:focus-visible, .tool:focus-visible, .opt:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}

/* ---- the structure plan ------------------------------------------------- */
/* A top-down drawing of the structure with every square in the state the site
   is actually in: drawn, open and waiting, buried under N layers, or lost. */
.feat-planwrap {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 7px;
  padding: 7px 9px;
  background: rgba(0, 0, 0, 0.18);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.feat-plan { display: block; image-rendering: auto; flex-shrink: 0; }
.feat-next {
  flex: 1 1 140px;
  min-width: 140px;
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-faint);
}

/* ---- what the bag is holding -------------------------------------------- */
/* One line at the top of the list, and only when there is something to say.
   A type you have never named beats a big number, so that is what it says. */
.bag-note {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 8px;
  padding: 8px 11px;
  border-radius: var(--r-sm);
  border: 1px solid rgba(232, 180, 78, 0.4);
  background: linear-gradient(180deg, rgba(232, 180, 78, 0.11), rgba(232, 180, 78, 0.04));
  font-size: var(--t-sm);
  line-height: 1.45;
}
.bag-note b {
  color: var(--gold);
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.bag-note span { color: var(--ink-dim); }

/* ---- the type series, as a series ---------------------------------------- */
.part.is-named { color: #1b1405; background: #7ee0a2; }
.type-card.is-named { border-color: rgba(126, 224, 162, 0.38); }
.grammar-bar .is-named b { color: #7ee0a2; }

/* A toast with a way out of the mistake in it. */
.toast.has-act { flex-wrap: wrap; align-items: center; pointer-events: auto; }
.toast-act {
  margin-left: 26px;
  border-color: var(--gold);
  color: var(--gold);
  background: rgba(232, 180, 78, 0.1);
}
.toast-act:hover { background: var(--gold); color: #1b1405; }

/* A field notebook is kept by the day and read by the day. */
.log li.log-day {
  margin: 12px 0 2px;
  padding: 0 0 4px;
  border-left: none;
  border-bottom: 1px solid var(--line);
  color: var(--ink-faint);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.log li.log-day:first-child { margin-top: 0; }

/* ---- first steps -------------------------------------------------------- */
/* The loop, one rung at a time. Shares a slot with the boxed-in banner and
   always loses to it. */
.firststeps {
  position: absolute;
  left: 50%;
  bottom: 152px;
  transform: translateX(-50%);
  display: flex;
  align-items: flex-start;
  gap: 10px;
  max-width: 520px;
  padding: 9px 8px 9px 12px;
  background: rgba(28, 21, 16, 0.94);
  border: 1px solid rgba(232, 180, 78, 0.35);
  border-radius: var(--r);
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-dim);
  pointer-events: auto;
}
.firststeps[hidden] { display: none; }
.fs-n {
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(232, 180, 78, 0.16);
  color: var(--gold);
  font-size: var(--t-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
}
.fs-text { flex: 1 1 auto; }
.fs-close {
  flex-shrink: 0;
  width: 22px; height: 22px;
  padding: 0;
  border: none;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-faint);
  font: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.fs-close:hover { background: rgba(255, 255, 255, 0.07); color: var(--ink); }

/* ---- arrival ------------------------------------------------------------ */
.arrive-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.72fr);
  gap: 32px;
  align-items: center;
  min-height: 100vh;
  max-width: 1000px;
  padding-top: 40px;
  padding-bottom: 40px;
}
.arrive-copy h2 {
  margin: 0 0 14px;
  font-size: var(--t-xl);
  letter-spacing: 0.01em;
}
.arrive-copy p {
  margin: 0 0 12px;
  max-width: 46ch;
  font-size: var(--t-md);
  line-height: 1.65;
  color: var(--ink-dim);
}
.arrive-lede { color: var(--ink) !important; }
.arrive-copy b { color: var(--ink); }
.arrive-note {
  margin-top: 18px !important;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  color: var(--ink-faint) !important;
}
.arrive-inner .screen-actions { margin-top: 22px; display: flex; gap: 10px; flex-wrap: wrap; }

.arrive-map { min-width: 0; }
.arrive-canvas {
  display: block;
  width: 100%;
  max-width: 400px;
  margin: 0 auto;
  height: min(70vh, 640px);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: #17131a;
}
.arrive-cap {
  margin-top: 8px;
  font-size: var(--t-sm);
  color: var(--ink-faint);
  text-align: center;
}

@media (max-width: 860px) {
  .arrive-inner { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .arrive-canvas { height: 46vh; }
}

/* ---- the crew rail ------------------------------------------------------ */
/* Down the left of the board, opposite the panel, so it never fights the
   section column for the same corner of the eye. */
.crewrail {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 7px;
  pointer-events: auto;
  z-index: 5;
}
.crewrail[hidden] { display: none; }

.crew-card {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) 26px;
  align-items: center;
  gap: 8px;
  width: 186px;
  padding: 6px 7px;
  border-radius: var(--r);
  background: rgba(28, 21, 16, 0.9);
  border: 1px solid var(--line);
  backdrop-filter: blur(6px);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  transition: border-color 0.15s, opacity 0.15s;
}
.crew-face {
  width: 40px; height: 40px;
  display: block;
  image-rendering: pixelated;
  border-radius: var(--r-sm);
  background: rgba(0, 0, 0, 0.22);
}
.crew-who { min-width: 0; }
.crew-name {
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.crew-state {
  font-size: var(--t-xs);
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.crew-bar {
  margin-top: 3px;
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.09);
  overflow: hidden;
}
.crew-bar i { display: block; height: 100%; background: var(--good); }
.crew-card.is-low .crew-bar i { background: var(--bad); }

.crew-go {
  width: 26px; height: 26px;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-dim);
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.crew-go svg { width: 12px; height: 12px; fill: currentColor; }
.crew-go:hover { color: var(--ink); border-color: var(--gold); background: rgba(232, 180, 78, 0.12); }

/* Paused: the card steps back and the button turns gold, because gold here
   means "this is the thing to press". */
.crew-card.is-paused { opacity: 0.72; border-color: rgba(232, 180, 78, 0.4); }
.crew-card.is-paused .crew-go { color: var(--gold); border-color: var(--gold); }
.crew-card.is-idle { border-color: rgba(224, 112, 94, 0.45); }
.crew-card.is-idle .crew-state { color: #e0a89a; }

@media (max-width: 1100px) { .crewrail { display: none; } }

/* ---- signing in --------------------------------------------------------- */
.signin-inner {
  max-width: 460px;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.signin-inner h2 { margin: 0 0 10px; font-size: var(--t-xl); }
.signin-lede {
  margin: 0 0 20px;
  font-size: var(--t-md);
  line-height: 1.65;
  color: var(--ink-dim);
}
.signin-form { display: flex; flex-direction: column; gap: 12px; }
.signin-field { display: flex; flex-direction: column; gap: 4px; }
.signin-field span {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.signin-field input {
  padding: 9px 11px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.28);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-md);
}
.signin-field input:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.signin-actions { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 6px; }
.signin-err {
  padding: 8px 11px;
  border-radius: var(--r-sm);
  border: 1px solid #96483c;
  background: rgba(74, 35, 32, 0.6);
  color: #f5ded8;
  font-size: var(--t-sm);
  line-height: 1.5;
}
.signin-err[hidden] { display: none; }

/* The honest small print. Open by default would shout; buried would hide. */
.signin-note {
  margin-top: 22px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--ink-faint);
}
.signin-note summary { cursor: pointer; color: var(--ink-dim); }
.signin-note summary:hover { color: var(--ink); }
.signin-note p { margin: 10px 0; }
.signin-note b { color: #e8b44e; }
.signin-note code {
  padding: 1px 4px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.07);
}
.signin-note .signin-field { margin-top: 12px; }

/* ---- the party ---------------------------------------------------------- */
.whoami-spacer { flex: 1 1 auto; }
.whoami-net {
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  color: var(--ink-faint);
  font-size: var(--t-xs);
  font-weight: 700;
  white-space: nowrap;
}
.whoami-net.is-online { color: #1b1405; background: var(--good); }

.party {
  margin: 18px 0;
  padding: 14px 16px;
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.2);
}
.party[hidden] { display: none; }
.party-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.party-title { font-weight: 700; color: var(--ink); }
.party-sub {
  max-width: 52ch;
  margin-top: 3px;
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-faint);
}
.party-add { display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
.party-add select, .party-add input {
  padding: 6px 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.3);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-sm);
}
.party-add input { width: 74px; text-align: right; font-variant-numeric: tabular-nums; }

.party-list { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; }
.party-row {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.04);
  font-size: var(--t-sm);
}
.party-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-faint); }
.party-row.is-here .party-dot { background: var(--good); box-shadow: 0 0 6px rgba(126, 224, 162, 0.7); }
.party-name { font-weight: 700; color: var(--ink); }
.party-wage { color: var(--gold); font-variant-numeric: tabular-nums; white-space: nowrap; }
.party-owed { color: var(--ink-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
.party-empty { color: var(--ink-faint); font-size: var(--t-sm); }

/* ---- operator's console ------------------------------------------------- */
.admin-inner { max-width: 1180px; }

.admin-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}
.admin-stat {
  padding: 8px 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.2);
  font-size: var(--t-sm);
  color: var(--ink-faint);
}
.admin-stat b {
  display: block;
  color: var(--ink);
  font-size: var(--t-lg);
  font-variant-numeric: tabular-nums;
}

.admin-search { margin: 12px 0; }
.admin-search input {
  width: 100%;
  max-width: 340px;
  padding: 8px 11px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.28);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-sm);
}

.admin-body { display: flex; flex-direction: column; gap: 7px; }

.adm-row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr) auto;
  align-items: center;
  gap: 14px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.18);
}
.adm-row.is-suspended { border-color: rgba(224, 112, 94, 0.5); }
.adm-row.is-frozen { border-color: rgba(232, 180, 78, 0.5); }
.adm-name { font-weight: 700; color: var(--ink); }
.adm-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--t-xs);
  color: var(--ink-faint);
}
.adm-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  font-size: var(--t-sm);
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}
.adm-acts { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }

.adm-tag {
  padding: 1px 7px;
  border-radius: 999px;
  font-size: var(--t-xs);
  font-weight: 700;
}
.adm-tag.is-admin { color: #1b1405; background: var(--gold); }
.adm-tag.is-dev { color: #1b1405; background: #7ee0a2; }
.adm-tag.is-off { color: #f5ded8; background: #96483c; }

/* Anything that cannot be taken back reads as dangerous before it is pressed. */
.btn-danger {
  border-color: #96483c;
  color: #f0b3a9;
  background: rgba(74, 35, 32, 0.4);
}
.btn-danger:hover { background: #96483c; color: #fff; }

.adm-log {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--t-xs);
  line-height: 1.6;
  color: var(--ink-dim);
  white-space: pre-wrap;
  word-break: break-word;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.28);
}
.adm-empty { color: var(--ink-faint); padding: 18px 2px; }
.adm-note {
  margin: 0 0 14px;
  padding: 10px 13px;
  border-radius: var(--r-sm);
  border: 1px solid rgba(232, 180, 78, 0.4);
  background: rgba(232, 180, 78, 0.08);
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-dim);
}

/* Sign-in and the console can both be opened from ON TOP of another screen —
   the Claims Office has the "Dig together" button in it — and every `.screen`
   shares one z-index, so they opened behind the thing that launched them. */

/* ---- a one-time secret, shown in the page ------------------------------- */
/* Outside #adminBody so a refresh cannot sweep it away while it is being
 * copied, and loud enough that it is not mistaken for another row. */
.adm-notice {
  margin: 0 0 14px;
  padding: 14px 16px;
  border: 1px solid #9a6b3a;
  border-left: 4px solid #d9a441;
  border-radius: 6px;
  background: rgba(217, 164, 65, 0.09);
}
.adm-notice-head {
  font-weight: 700;
  letter-spacing: 0.02em;
  margin-bottom: 8px;
}
.adm-pass {
  display: block;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 1.25rem;
  letter-spacing: 0.04em;
  padding: 10px 12px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.35);
  user-select: all;
  word-break: break-all;
}
.adm-notice-why {
  margin: 10px 0 0;
  font-size: 0.86rem;
  opacity: 0.85;
  line-height: 1.45;
}
.adm-notice-acts {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}


/* ---- the console asks in the page --------------------------------------- */
/* Nothing the browser can switch off, and it sits above the console that
 * raised it rather than behind it. */
.adm-ask {
  position: fixed;
  inset: 0;
  z-index: 95;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(8, 6, 12, 0.72);
  backdrop-filter: blur(2px);
}
.adm-ask-card {
  width: min(460px, 100%);
  max-height: 100%;
  overflow-y: auto;
  padding: 20px 22px;
  border: 1px solid #6b5b45;
  border-radius: 8px;
  background: #17131c;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
}
.adm-ask-card.is-danger { border-color: #96483c; }
.adm-ask-card h3 { margin: 0 0 8px; font-size: 1.05rem; }
.adm-ask-detail {
  margin: 0 0 14px;
  font-size: 0.88rem;
  line-height: 1.5;
  opacity: 0.85;
  white-space: pre-wrap;
}
.adm-ask-field { display: block; margin-bottom: 12px; }
.adm-ask-field > span { display: block; font-size: 0.82rem; margin-bottom: 4px; opacity: 0.9; }
.adm-ask-field input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid #4d4256;
  border-radius: 4px;
  background: #0f0c14;
  color: inherit;
  font: inherit;
}
.adm-ask-field input:focus { outline: none; border-color: #d9a441; }
.adm-ask-hint { display: block; margin-top: 4px; font-size: 0.76rem; opacity: 0.6; }
.adm-ask-err { min-height: 1.1em; font-size: 0.8rem; color: #e08a7a; }
.adm-ask-acts { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }

#screenSignIn { z-index: 60; }
#screenAdmin { z-index: 55; }

/* ---- the artwork tab ---------------------------------------------------- */
.art-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 10px;
}
.art-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.2);
}
.art-card.is-replaced { border-color: rgba(126, 224, 162, 0.5); }
.art-shot {
  display: grid;
  place-items: center;
  height: 96px;
  border-radius: var(--r-sm);
  background:
    linear-gradient(45deg, rgba(255,255,255,0.035) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.035) 75%),
    linear-gradient(45deg, rgba(255,255,255,0.035) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.035) 75%);
  background-size: 14px 14px;
  background-position: 0 0, 7px 7px;
}
.art-shot canvas, .art-shot img { image-rendering: pixelated; display: block; }
.art-name {
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.3;
}
.art-key {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--t-xs);
  color: var(--ink-faint);
  word-break: break-all;
}
.art-acts { display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto; }
.art-tag {
  align-self: flex-start;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: var(--t-xs);
  font-weight: 700;
  color: #1b1405;
  background: #7ee0a2;
}
.art-pair { display: flex; gap: 8px; align-items: center; justify-content: center; }
.art-pair figure { margin: 0; text-align: center; }
.art-pair figcaption { font-size: var(--t-xs); color: var(--ink-faint); margin-top: 3px; }
.art-arrow { color: var(--ink-faint); }

/* The section reference is what tells two parcels of the same name apart. */
.adm-where {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--bone);
  font-weight: 700;
}
.adm-quiet { color: var(--ink-faint); font-style: normal; }

/* A price the museum has marked down because of who is selling it. */
.price.is-docked { color: var(--warn); }


/* ---------------------------------------------------------------------------
   The Quiet Frame, continued
   --------------------------------------------------------------------------- */

.panel.is-open {
  transform: translateX(0);
  visibility: visible;
  transition: transform 0.18s ease, visibility 0s;
}
@media (prefers-reduced-motion: reduce) {
  .panel { transition: none; }
  .panel.is-open { transition: none; }
}

/* One thin line, and it holds identity, ten actions and five meters without
   wrapping -- which the old bar could not do at any width, because it was set
   to wrap and then given more than it could hold. Measured at 98px tall on a
   wide screen and 141px at 980. */
.topbar {
  flex-wrap: nowrap;
  /* The rail is fixed to the right edge and sits ABOVE this bar, so the bar has
     to stop short of it. Without the reserve the right-hand buttons ran under
     the rail -- measured at full width, the actions ended 13px past the edge of
     a 1563px viewport and the help button was simply behind the icons. */
  padding: 5px calc(12px + var(--rail-w)) 5px 12px;
  gap: 4px 9px;
  /* Nothing may push the bar wider than the window. Everything inside it either
     shrinks or is hidden by the rules below; this is the backstop that decides
     what happens if some future thing does neither. */
  overflow: hidden;
}

/* ---------------------------------------------------------------------------
   Narrowing, in order of what you can most afford to lose

   `nowrap` fixed the header's height and did nothing about its WIDTH: a row
   that cannot wrap and cannot shrink simply runs off the end of the screen,
   which is what it was doing. So each part is allowed to give way, and they
   give way in a deliberate order -- the game's own name first (you know what
   you are playing), then decoration, then labels, and the numbers last,
   because the numbers are the reason the bar exists.
   --------------------------------------------------------------------------- */
/* WHICH ONE GIVES WAY. This is the whole fix, and it was backwards.
   `.topbar-actions` was the shrinkable one, so the browser squeezed 469px of
   buttons into 380 and quietly clipped the last two -- the console and the help
   button were simply not there. Buttons are the one thing in this bar that
   cannot be made smaller: you either press them or you cannot.
   So the controls hold their size, the meters hold theirs, and the identity
   block absorbs whatever is left over and truncates. */
/* Set on the authoritative rules below, not here -- two rules for one
   property is how the first attempt at this quietly did nothing. */

/* 1. The title. You are already inside the game. */
@media (max-width: 1460px) { .brand-name { display: none; } }

/* 2. The weather, which has its own place on the board anyway. */
@media (max-width: 1330px) { .sky-chip span { display: none; } }

/* 3. Meter LABELS go, values stay. FUNDS 340 becomes 340 beside a coin. */
@media (max-width: 1240px) {
  .meter-label { display: none; }
  .meter { padding: 2px 5px; gap: 5px; }
}

/* 4. The township, then the claim name -- the compass chips still say where
      you are and which way home is. */
@media (max-width: 1080px) { .brand-owner { display: none; } }
@media (max-width: 900px) { .brand { display: none; } }

/* 5. The sky goes entirely -- it is drawn on the board above your head, which
      is a better place for weather than a word in a bar. The threshold is not a
      guess: at 820px the bar was 70px over and the chip is 71px wide. */
@media (max-width: 900px) { .sky-chip { display: none; } }

/* 5b. The standing BAND name. The number stays, and the band is still on the
       meter's tooltip -- "18" is the reading, "Newcomer" is the gloss. */
@media (max-width: 1000px) { .rep-band { display: none; } }

/* 6. Meter marks, leaving bare numbers. */
@media (max-width: 760px) { .meter-mark { display: none; } }

/* 7. The town compass. The camp chip is the one that matters -- it is how you
      find your way home. */
@media (max-width: 680px) { .home-chip.is-town { display: none; } }

/* 8. The two slowest numbers on the bar. The day advances once a night and
      standing moves a point at a time; both are in the panel, and neither is a
      thing you check mid-swing. Funds, stamina and the barrow are, so they are
      what is left. Measured: without this the bar was 254px over at 600px wide,
      and the overflow was eating FUNDS off the left-hand end. */
@media (max-width: 700px) {
  .meter.is-day, .meter.is-rep { display: none; }
}

/* Below about 560 this is a phone, and a phone wants a bar designed for a
   thumb rather than this one made smaller. Nothing here pretends otherwise. */
.brand { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.brand-mark { width: 17px; height: 17px; }
/* The two-line identity block becomes one line. The site name is the half you
   need; the game's own name is not news to anybody already playing it. */
/* `flex-direction` has to be named, not merely overridden by a later rule
   that happens to set `display: flex` -- the earlier rule declares `column`
   and nothing here was contradicting it, so the identity block stayed two
   lines tall and took the whole header up with it. */
.brand-text { display: flex; flex-direction: row; align-items: baseline; gap: 8px; min-width: 0; }
.brand-name { font-size: var(--t-sm); }
.brand-line { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.brand-site, .brand-owner { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.meters { margin-left: auto; flex: 0 0 auto; }
.meter { padding: 2px 7px; }

/* `nowrap` on the row AND on the chips inside it. The camp chip reads "no camp"
   and was breaking after "no", which made one child 51px tall and set the
   height of the whole header -- a two-line word in a one-line bar. */
.topbar-actions { display: flex; flex-wrap: nowrap; align-items: center; gap: 5px; flex: 0 0 auto; }
.topbar-actions > * { white-space: nowrap; flex: 0 0 auto; }
.home-chip, .sky-chip, .btn { white-space: nowrap; }
.topbar-actions .btn-icon span:not(.knock-badge) { display: none; }
.topbar-actions .btn-icon { padding-left: 7px; padding-right: 7px; }
@media (max-width: 1240px) {
  .home-chip span:not(.home-arrow) { display: none; }
  .btn-rest span { display: none; }
}

/* THE CREW RAIL IS GONE.
   Not hidden as a compromise -- the information it carried is already on the
   board. `Crew.drawTags` puts a name over every hand where that hand is
   actually standing, which is a better answer to "which of those is Amos" than
   a list on the far side of the screen ever was. A stack of eight cards down
   the left of the play area was the same fact told twice, and the copy that
   was not standing on the ground is the one to drop. */
.crewrail { display: none !important; }

/* The context line stops being a card and becomes a whisper: one line, low,
   and it fades out once you stop moving. It is the least important thing on
   the screen and it was drawn like the most. */
.context {
  top: 10px; left: 12px;
  display: block;
  padding: 4px 10px;
  background: rgba(14, 11, 9, 0.55);
  border: 0;
  border-left: 2px solid rgba(232, 180, 78, 0.4);
  border-radius: 0 3px 3px 0;
  backdrop-filter: blur(3px);
  max-width: min(520px, 60%);
  font-size: var(--t-sm);
  opacity: 0;
  transition: opacity 0.5s ease;
}
.context.is-live { opacity: 1; transition: opacity 0.1s ease; }
.context-layer { font-weight: 700; margin-right: 7px; }
.context-dates { display: none; }
.context-msg { color: var(--ink-dim); border: 0; padding: 0; }

/* Everything still floating over the board sits above the rail's gradient but
   below the panel, and stays clear of the rail on the right. */
.firststeps, .stuck { bottom: 150px; }

/* Everything centred over the board recentres on the board you can still SEE.
   With the panel out, the toolbar was running underneath it and losing the
   dynamite -- and switching tools with a panel open is an ordinary thing to
   want to do, so the toolbar cannot be the thing that gets covered. */
.toolbar-wrap, .firststeps, .stuck, .tool-caption { transition: padding 0.18s ease; }
.toolbar-wrap { padding-right: var(--rail-w); }
#app:has(.panel.is-open) .toolbar-wrap,
#app:has(.panel.is-open) .firststeps,
#app:has(.panel.is-open) .stuck { padding-right: min(412px, calc(100vw - 56px)); }
@media (prefers-reduced-motion: reduce) {
  .toolbar-wrap, .firststeps, .stuck, .tool-caption { transition: none; }
}


/* ---------------------------------------------------------------------------
   The rail's other half: doors
   --------------------------------------------------------------------------- */

/* Pushes everything after it to the bottom of the rail. References at the top,
   places you go at the bottom, and a rule between them so the difference is
   visible before it is explained. */
.rail-gap {
  margin-top: auto;
  height: 1px;
  margin-left: 9px;
  margin-right: 9px;
  background: var(--line);
  flex: 0 0 auto;
}

/* A door never lights up like an open tab, because nothing stays open behind
   it -- you go in and you come back out. */
.tab.is-door { border-right-color: transparent; }
.tab.is-door:hover { color: var(--gold); background: rgba(232, 180, 78, 0.07); }
.tab.is-door.is-active { color: inherit; background: none; border-right-color: transparent; }

/* The badge for people asking to join the dig. It followed the store button
   from the header, and on a 40px rail it sits on the corner of the mark rather
   than beside a word that is no longer there. */
.tab .knock-badge {
  position: absolute;
  top: -5px;
  right: -7px;
}


/* ---------------------------------------------------------------------------
   Rail marks, bigger
   --------------------------------------------------------------------------- */

/* Nineteen pixels was sized for a tab that had a WORD under it -- the mark was
   a decoration on a label. On the rail the mark is the whole button, and it is
   what you aim at, so it takes the size the label used to justify. The stroke
   goes up with it: a 1.35 line scaled onto a 25px glyph reads as a wireframe. */
.tab-mark svg { width: 25px; height: 25px; stroke-width: 1.5; }
.tab.is-active .tab-mark svg { stroke-width: 1.75; }
.tab { padding: 10px 2px; }


/* ---------------------------------------------------------------------------
   BROADCAST MODE

   `?broadcast=1`. Nobody is at the keyboard, so everything that exists to be
   clicked goes: the toolbar, the rail, the panel, the action buttons. What is
   left is what an audience can actually use -- the ground, the crew standing on
   it, and a line saying where and when they are.

   The toasts stay, and they matter more here than anywhere else in the game:
   with no panel to check, they are the entire feed. Somebody found a jar
   sherd, somebody put a boot through a sandal, the other outfit came in the
   night -- on stream that IS the show, so they are given room and held longer.
   --------------------------------------------------------------------------- */

body.is-broadcast .toolbar-wrap,
body.is-broadcast .panel,
body.is-broadcast .topbar-actions,
body.is-broadcast .tool-caption,
body.is-broadcast #firstSteps { display: none !important; }

/* The board takes the lot -- no rail to leave room for. */
body.is-broadcast { --rail-w: 0px; }

/* A caption bar rather than a control bar: who is digging, where, and what day
   it is. Bigger type, because this is being read off a screen across a room
   rather than from two feet away. */
body.is-broadcast .topbar {
  padding: 8px 16px;
  background: linear-gradient(180deg, rgba(20,15,11,.94), rgba(20,15,11,.72));
  border-bottom: 1px solid var(--line);
}
body.is-broadcast .brand { flex: 0 0 auto; }
body.is-broadcast .brand-name { display: block; font-size: var(--t-lg); }
body.is-broadcast .brand-site,
body.is-broadcast .brand-owner { font-size: var(--t-md); }
body.is-broadcast .meters { gap: 10px; }
body.is-broadcast .meter-label { display: block; }
body.is-broadcast .meter-value { font-size: var(--t-lg); }

/* The feed. Wider, and it does not have to compete with a toolbar for the
   bottom of the screen any more -- which is what makes the ticker lane further
   down this file possible. Position and width are both settled there; all that
   survives here is the type size. */
body.is-broadcast .toast { font-size: var(--t-md); }

/* The context whisper is for a player deciding what to click. There is no
   clicking here. */
body.is-broadcast .context { display: none; }


/* The giveaway banner. Broadcast only, top-centre, over the board -- the one
   thing on screen that has to be readable from across a room on a phone. */
.contest {
  position: absolute;
  left: 50%; top: 12px;
  transform: translateX(-50%);
  z-index: 42;
  display: flex; align-items: center; gap: 10px;
  padding: 7px 16px;
  background: linear-gradient(180deg, rgba(58,42,16,0.96), rgba(38,27,12,0.96));
  border: 1px solid var(--gold);
  border-radius: 4px;
  box-shadow: 0 6px 26px rgba(0,0,0,0.5);
  font-size: var(--t-lg);
  font-weight: 700;
  color: var(--gold);
  letter-spacing: 0.02em;
  white-space: nowrap;
  pointer-events: none;
}
.contest[hidden] { display: none; }
.contest.is-won { border-color: var(--good); color: var(--good); }


/* ---------------------------------------------------------------------------
   The dressing room
   --------------------------------------------------------------------------- */

.dresser {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s3, 20px);
  align-items: center;
  padding: 18px;
  margin-bottom: 18px;
  background: linear-gradient(180deg, #241d18, var(--bg-panel));
  border: 1px solid var(--line);
  border-radius: 14px;
}
@media (min-width: 760px) { .dresser { grid-template-columns: 260px 1fr; } }

.dresser-stage {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  min-height: 240px;
  border-radius: 10px;
  /* Ground under her feet rather than a void: the figure is lit warm and reads
     as standing on something. */
  background:
    radial-gradient(ellipse 60% 26% at 50% 84%, rgba(0,0,0,0.45), transparent 70%),
    linear-gradient(180deg, #2f2620, #1d1713);
  box-shadow: inset 0 0 0 1px var(--line);
}
.dresser-figure { image-rendering: pixelated; width: 240px; height: 240px; }
.dresser-purse {
  position: absolute; top: 10px; right: 12px;
  font-weight: 700; color: var(--gold);
  font-variant-numeric: tabular-nums;
}

.dresser-controls { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

.dresser-slots { display: flex; flex-wrap: wrap; gap: 6px; }
.dresser-slot {
  padding: 6px 12px;
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink-faint);
  font: inherit; font-size: var(--t-sm); cursor: pointer;
  transition: color .12s, border-color .12s, background .12s;
}
.dresser-slot:hover { color: var(--ink-dim); }
.dresser-slot.is-active { color: #241a10; background: var(--gold); border-color: var(--gold); font-weight: 700; }

.dresser-pick { display: flex; align-items: center; gap: 12px; min-width: 0; }
.dresser-arrow {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink);
  font-size: 22px; line-height: 1; cursor: pointer;
  transition: border-color .12s, background .12s;
}
.dresser-arrow:hover { border-color: var(--gold); color: var(--gold); }
.dresser-item { flex: 1 1 auto; min-width: 0; }
.dresser-name { font-size: var(--t-lg); font-weight: 700; color: var(--ink); }
.dresser-blurb { font-size: var(--t-sm); color: var(--ink-dim); line-height: 1.5; margin-top: 3px; }
.dresser-where {
  font-size: var(--t-xs); color: var(--ink-faint);
  letter-spacing: .06em; text-transform: uppercase; margin-top: 6px;
}

.dresser-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.dresser-short { font-size: var(--t-sm); color: var(--warn); }


/* ---------------------------------------------------------------------------
   The mast: the one control that survives into broadcast mode
   --------------------------------------------------------------------------- */

/* Lit gold while the mode is on, so the rail says which side of it you are on. */
.tab.is-door.is-live { color: var(--gold); }
.tab.is-door.is-live .tab-mark svg { stroke-width: 1.75; }

/* In broadcast the rail keeps its place and loses everything else.
   No background, no border, and it does not take clicks -- so the board runs
   underneath it and only the mast is live. Faint enough not to be part of the
   stream, findable enough that nobody is stuck in here: a mode you can only
   leave by editing the address bar is a trap rather than a mode. */
/* THE MAST HAS TO BE ON SCREEN.
   `--rail-w: 0` is right for everything that has to leave room for the rail --
   the panel's offset, the toolbar's padding -- and wrong for the rail itself,
   which inherits it as its own width and collapses to nothing. Measured: the
   rail 0px wide at x=1496 on a 1496 viewport, the mast squeezed to 6px and
   hanging off the edge. It gets its own size and sits clear of the corner. */
body.is-broadcast .tabs {
  background: none;
  border-left: 0;
  pointer-events: none;
  justify-content: flex-end;
  width: 56px;
  right: 16px;
  bottom: 16px;
  top: auto;
  padding: 0;
}
body.is-broadcast .tabs > *:not(#btnBroadcast) { display: none !important; }
body.is-broadcast #btnBroadcast {
  pointer-events: auto;
  opacity: 0.3;
  transition: opacity 0.15s;
}
body.is-broadcast #btnBroadcast:hover,
body.is-broadcast #btnBroadcast:focus-visible { opacity: 1; }


/* ---------------------------------------------------------------------------
   Going on air
   --------------------------------------------------------------------------- */

/* The grid can want more width than the dialog has -- a number input has a
   minimum and two of them plus a label do not always fit. Let the columns
   shrink instead of the dialog scrolling sideways. */
.onair-inner { max-width: 780px; overflow-x: hidden; }
.onair-group { min-width: 0; }
.onair-row { flex-wrap: wrap; }
.onair-row input[type=text] { width: 100%; min-width: 0; max-width: 220px; }
.onair-head h2 { margin: 0 0 4px; }
.onair-grid {
  display: grid; grid-template-columns: 1fr; gap: 18px;
  margin: 18px 0;
}
@media (min-width: 660px) { .onair-grid { grid-template-columns: 1fr 1fr; } }

.onair-group {
  display: flex; flex-direction: column; gap: 8px;
  padding: 14px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: 10px;
}
.onair-group h3 {
  margin: 0 0 2px; font-size: var(--t-sm); letter-spacing: .1em;
  text-transform: uppercase; color: var(--bone);
}
.onair-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.onair-row span { font-size: var(--t-sm); color: var(--ink-dim); }
.onair-row input[type=number] { width: 82px; }
.onair-row input[type=text] { width: 190px; }
.onair-row input, .onair-row select {
  padding: 6px 9px;
  background: var(--bg-raise); border: 1px solid var(--line); border-radius: 6px;
  color: var(--ink); font: inherit; font-size: var(--t-sm);
}
.onair-check { display: flex; align-items: flex-start; gap: 9px; font-size: var(--t-sm); color: var(--ink-dim); cursor: pointer; }
.onair-check input { margin-top: 2px; flex: none; }
.onair-note { margin: 2px 0 0; font-size: var(--t-xs); color: var(--ink-faint); line-height: 1.5; }
.onair-note b { color: var(--ink-dim); font-weight: 600; }
.onair-actions { display: flex; justify-content: flex-end; gap: 10px; }


/* ---------------------------------------------------------------------------
   The find card

   Sized to the band the toolbar used to occupy -- the same width and a little
   taller -- because that is the space a stream has going spare and the most
   valuable place on the screen to put the thing everybody came to see.
   --------------------------------------------------------------------------- */


/* Broken is the other half of the story and has to read differently at a
   glance -- somebody in chat is watching their own hand drop it. */




/* ---------------------------------------------------------------------------
   Sign in with Twitch
   --------------------------------------------------------------------------- */

.btn-twitch {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%;
  padding: 11px 16px;
  background: #9146ff;
  border: 1px solid #a970ff;
  border-radius: var(--r);
  color: #fff;
  font: inherit; font-weight: 700; font-size: var(--t-md);
  cursor: pointer;
  transition: background .12s;
}
.btn-twitch:hover { background: #a970ff; }
.btn-twitch svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; }

/* The rule between the two ways in. */
.signin-or {
  display: flex; align-items: center; gap: 10px;
  margin: 16px 0 4px;
  color: var(--ink-faint); font-size: var(--t-xs);
  letter-spacing: .08em; text-transform: uppercase;
}
.signin-or::before, .signin-or::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}


/* ---- the two steps, plainly ---------------------------------------------

   A PLATE WITH THE THING ON IT.

   This was a drawn crest for a while -- a shield with three claw points, its
   own palette, the words set across the field of it. It was the only piece of
   art in the build that stood FOR something rather than being it, and next to
   a screen of sprites that are the real article it read as a sticker somebody
   had put on the game.

   What replaced it is the smallest thing that works: the actual camp sprite,
   the actual mast, on a square plate with a word under it. The picture on the
   button is a picture of what pressing it does.

   (The old rules also chained `animation: badgepulse` at a keyframe set that
   was never written, so the button that exists to be noticed had not been
   blinking at all. That is why it is defined below rather than referenced.)
   ------------------------------------------------------------------------- */

.startstep {
  position: absolute;
  left: 50%;
  bottom: 26px;
  transform: translate(-50%, 10px);
  z-index: 46;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 14px 22px 12px;
  background: #1b1611;
  border: 2px solid var(--gold);
  border-radius: 0;
  box-shadow: none;
  color: var(--gold);
  font: inherit;
  cursor: pointer;
  opacity: 0;
  transition: opacity .25s ease, transform .25s ease, background .12s;
}
.startstep[hidden] { display: none; }
.startstep:hover { background: #241d17; }
.startstep.is-on {
  opacity: 1;
  transform: translate(-50%, 0);
  animation: steppulse 2.4s ease-in-out infinite;
}

.startstep-art { image-rendering: pixelated; display: block; }

/* Under the picture, not across it. On the crest the words had to sit in the
   field of the shield and fight the art for the same pixels; a caption under a
   sprite is legible at any size and needs no shadow to hold it off the
   background. */
.startstep-text {
  font-size: 20px; font-weight: 800;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold);
  pointer-events: none;
}

/* Slow. A fast blink is an alarm; this is an invitation, and it has to be able
   to sit there for a minute without becoming annoying to look at. */
@keyframes steppulse {
  0%, 100% { border-color: var(--gold); box-shadow: 0 0 0 0 rgba(232,180,78,0); }
  50%      { border-color: #f6d089; box-shadow: 0 0 26px 3px rgba(232,180,78,0.32); }
}
@media (prefers-reduced-motion: reduce) {
  .startstep.is-on { animation: none; }
  .startstep { transition: none; }
}


/* ===========================================================================
   THE BROADCAST OVERLAY
   ===========================================================================

   A REDESIGN FOR A DIFFERENT VIEWER.

   Everything above this line was drawn for a person sitting two feet from a
   monitor, at rest, in control of what happens next. Nothing about that
   describes the audience for this build. They are on a phone, or on half a
   monitor beside something else; the picture has been through an encoder at a
   few thousand kilobits; they arrived forty seconds ago; and the person whose
   stream it is is mid-sentence about something else entirely.

   Four things follow from that, and they are the whole of this stylesheet:

   FLAT. No gradients, no blur, no translucency over the board. A video encoder
   spends its bits on what moves, and the dig moves constantly -- so a panel
   with a soft gradient behind text is a panel that will band and smear at the
   exact moment somebody is trying to read a price off it. Solid fills survive.

   BIG. Every number that matters is set at 20px and up. That is absurd on a
   desktop and correct at 320 pixels wide, which is where a great many people
   watch a stream.

   SQUARE. The rest of the game is soft: 13px radii, warm shadows, a cosy thing
   to sit in front of. This is signage. It borrows the board's own colours so it
   belongs to the same world, and none of its geometry, because a sign that
   looks like upholstery does not read from across a room.

   PIXELS. The art is the identity. Every item in the stores is the actual
   sprite, drawn by the same painter that puts it on the ground, at a whole
   number scale so the pixels stay square. No icon fonts and no vector glyphs
   anywhere in here: if you can see it on the dig, you see the same thing in the
   panel, and that correspondence is doing more work than any label could.
   =========================================================================== */

body.is-broadcast {
  --sb-h: 56px;      /* the bar across the top */
  --store-w: 248px;  /* the shop window down the left */
  --chat-w: 320px;   /* chat down the right */
  --plate: #1b1611;  /* flat panel ground: darker than --bg-panel, and solid */
  --plate-hi: #241d17;
  --edge: #4a3d2f;   /* a border that survives compression, unlike --line */
}

/* The single-player chrome, gone. The header was six meters about one
   archaeologist and her stamina and her debts; none of it is true of a dig with
   forty hands on it, and none of it is interesting to somebody watching. */
body.is-broadcast .topbar,
body.is-broadcast .toolbar-wrap,
body.is-broadcast .panel,
body.is-broadcast .topbar-actions,
body.is-broadcast .tool-caption,
body.is-broadcast .crewrail,
body.is-broadcast #firstSteps { display: none !important; }

.streambar[hidden], .storerail[hidden], .chatcol[hidden] { display: none; }


/* ---------------------------------------------------------------------------
   The bar

   Left to right it answers: what is this, how is it going, what can I do, is it
   live. That order is not arbitrary -- it is the order somebody scans the top
   of a stream, and the call to action sits at the end of it because that is the
   last thing you want them holding when they look away.
   --------------------------------------------------------------------------- */

.streambar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--sb-h, 56px);
  z-index: 60;
  display: flex; align-items: stretch; gap: 0;
  background: var(--plate, #1b1611);
}

.sb-mark {
  display: flex; align-items: center; gap: 10px;
  padding: 0 16px;
  border-right: 2px solid var(--edge, #4a3d2f);
}
/* Her face, drawn by the game. The one place the brand is a picture of the
   thing rather than a wordmark for it. */
.sb-face { image-rendering: pixelated; width: 34px; height: 34px; }
.sb-id { display: flex; flex-direction: column; line-height: 1.1; }
/* Sized by height so it sits where the line of type sat, and `pixelated` so
   no browser smooths away the reason it was drawn on a grid. */
.sb-logo { display: block; height: 15px; width: auto; image-rendering: pixelated; }
.sb-id b {
  font-size: 13px; font-weight: 800; letter-spacing: .1em;
  color: var(--ink);
}
.sb-id span {
  font-size: 11.5px; color: var(--ink-faint);
  letter-spacing: .04em;
  max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.sb-stats { display: flex; align-items: stretch; }
.sb-stat {
  display: flex; flex-direction: column; justify-content: center;
  min-width: 84px;
  padding: 0 14px;
  border-right: 1px solid var(--edge, #4a3d2f);
  line-height: 1.05;
}
/* ---------------------------------------------------------------------------
   THE PALETTE HIERARCHY, PROTOTYPED ON THIS ROW AND THE HOST SCREEN.

   The chrome had one accent doing every job: gold appeared 130 times, 76 of
   them as a text colour, on names, headings, tags, chat commands, board tiles,
   prices and titles alike. An accent that means "important" everywhere means
   nothing anywhere -- which is why the blood red had to be invented as a new
   register for the button that starts a race. There was no headroom left in
   gold.

   The wordmark already solved this. It has three colours and each has one job:
   bone is the thing itself, red is the violent part, black is the edge between
   them. So:

     BONE    the subject. Who is standing, what this screen is about.
     BLOOD   carnage, and commitment. Deaths, the eaten count, the one button
             that starts or ends a race. Nothing else.
     GOLD    value, and only value. Money, prices, purses, the pot.
     BROWN   the room. Unchanged, and deliberately: the note by `--bg` argues
             the darks were pulled towards the colour of the dirt because the
             board is ochre and rust, and a cold chrome made the whole game feel
             like a dashboard with a game inside it. That argument still holds.

   This is subtraction more than addition: most of it is gold going back to
   meaning one thing.
   --------------------------------------------------------------------------- */
.sb-stat b {
  font-size: 22px; font-weight: 800;
  color: var(--bone);
  font-variant-numeric: tabular-nums;
}
/* The most dramatic number in a game called Dino Carnage was the same plain
   cream as "day". */
.sb-stat.is-carnage b { color: var(--blood); }
.sb-stat.is-value   b { color: var(--gold); }
.sb-stat.is-quiet   b { color: var(--ink-dim); }
.sb-stat span {
  font-size: 10.5px; color: var(--ink-faint);
  letter-spacing: .1em; text-transform: uppercase;
  margin-top: 2px;
  /* A four-word label under a number, never a paragraph. See `.sb-live`. */
  white-space: nowrap;
}
.sb-stat.is-pot b { color: var(--gold); }
.sb-stat.is-guests b { color: var(--accent); }

/* The call. One command at a time, changing every few seconds, because a list
   of twelve is a list nobody reads and a single line is an instruction. */
.sb-call {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 12px;
  padding: 0 18px;
}
.sb-call b {
  flex: 0 0 auto;
  padding: 4px 10px 5px;
  background: var(--gold);
  color: #241a10;
  font-size: 17px; font-weight: 800; letter-spacing: .02em;
}
.sb-call span {
  font-size: 15px; color: var(--ink-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* It changes on a fade rather than a swap, so movement at the top of the frame
   is never mistaken for something happening on the dig. */
.sb-call b, .sb-call span { transition: opacity .22s; }
.sb-call.is-turning b, .sb-call.is-turning span { opacity: 0; }

/* THE ROOM NAME IS ONE LINE, AND THE BAR IS ONLY FORTY-EIGHT TALL INSIDE.

   The call is the flexible item here -- `flex: 1 1 auto`, `min-width: 0`, an
   ellipsis on its own span -- and it is the only one that should be. But every
   flex item shrinks by default, so when a long call came round the browser took
   its pixels off all of them, and the two that had no `white-space` rule paid
   with a line break instead of with width.

   Measured at 1280 with a long call in the bar: `.sb-live` shrank from 216 to
   135 and "the rehearsal room" became THREE lines, 58px of text stacked inside
   a 48px content box, so the channel name crossed the frame at the top and the
   bottom of the header. `ARE CHAT'S` went to two lines beside it. This is the
   twenty pixels the frame costs the bar arriving where it shows.

   Nowrap fixes it by making the minimum content size honest: the two labels
   cannot get smaller than their text, so the shrink lands on the call, whose
   span was written to ellipsise and does. Re-measured with the same long call:
   one line each, no wrap, and nothing overflows the bar.

   BELOW ABOUT 1050 THE CALL RUNS OUT OF ROOM and its command badge -- which is
   `flex: 0 0 auto`, because a command you cannot read in full is not a command
   -- can sit a few pixels over the divider. At 1024 with a deliberately long
   call that measured seven. Accepted: this is operated at 1280 and watched at
   1920, and a badge nudging a hairline is a smaller fault than a channel name
   printed over the frame at every size. */
.sb-live {
  display: flex; align-items: center; gap: 8px;
  padding: 0 16px;
  border-left: 2px solid var(--edge, #4a3d2f);
  font-size: 12px; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}
.sb-live i {
  width: 9px; height: 9px;
  background: var(--ink-faint);
  border-radius: 999px;
}
.sb-live.is-live { color: var(--bad); }
.sb-live.is-live i { background: var(--bad); animation: sb-pulse 1.9s ease-in-out infinite; }
@keyframes sb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .28; } }
@media (prefers-reduced-motion: reduce) { .sb-live.is-live i { animation: none; } }


/* ---------------------------------------------------------------------------
   The two columns

   Same frame, same head, opposite edges -- the stores and the chat are the two
   halves of one offer (here is what you can have; here are the people having
   it) and they read better as a matched pair than as two designs.
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   THE FRAME, IN ONE PLACE.

   A 16x16 pixel tile drawn at 2x -- `art/ui/frame-gold.png`, 257 bytes -- used
   as a nine-slice border. A brass rail and only that: warm dark outside, brass
   body, the light on the top-left edge and the shadow on the bottom-right, so it
   reads as bevelled metal rather than a drawn line.

   THE FERNS CAME OFF. A first pass grew fronds along the edges. They looked
   right in a 160px swatch and wrong on the chat column, because `border-image`
   REPEATS the middle slice -- down a seven hundred pixel edge the same three
   leaves twenty times over stopped being growth and became wallpaper. A plain
   rail tiles without ever telling you it is tiling.

   DRAWN AT 2x SO IT IS NEVER RESAMPLED. `border-image` scales its slices to the
   border width, so a 5px slice shown at 10px would be interpolated and go soft
   against pixel art that is deliberately not. Ten is the slice's own size and
   the two must move together -- hence one variable for both.

   NO `fill`. The middle slice would paint over the panel's own background, and
   the ground under the text is what keeps it readable.
   --------------------------------------------------------------------------- */
:root {
  /* THE RED FRAME, WHICH IS THE GOLD ONE WITH TWO COLOURS SWAPPED.

     A `border-image` is a picture, so none of the tokens above can reach it and
     a red-themed screen kept gold picture frames around every panel. The tile
     is 32x32 and holds exactly five colours: three near-blacks that are its own
     shadow, and two golds. Only the golds are remapped -- to the site's
     `--blood` and a darker line of it -- so the relief, the corners and the
     rivets are the same pixels they always were.

     `art/ui/frame-gold.png` is kept rather than overwritten. It is the tile
     this was cut from, and a two-colour swap is cheap to redo from it. */
  --frame:   url("img/frame-red.png");
  --frame-w: 10px;
  /* What a framed panel's contents inset by, ON TOP of the frame. The frame is
     a gutter as well as a picture, so the padding inside one is the OLD padding
     minus the frame, plus a few pixels so text is not sitting on the brass.
     Twelve was the old number, so ten of frame would have left two; four leaves
     FOURTEEN, and fourteen is what every framed panel measures. Said plainly
     because this note used to claim twelve and a correction was written against
     it -- the number to check a new panel against is 14, not 12. */
  --frame-pad: 4px;
  /* The tile's own shadow colour, for anything that has to sit against it. */
  --frame-dark: #14100c;
}

.storerail, .chatcol {
  position: fixed;
  top: var(--sb-h, 56px);
  z-index: 55;
  display: flex; flex-direction: column;
  background: var(--plate, #1b1611);
}

/* EVERY FRAMED CARD, and the list is deliberately short: these are the three
   things on screen during a run. `box-sizing` is border-box globally, so the
   frame costs content width and never changes where a panel's outer edge sits
   -- which is what keeps the column lined up with the toolbar beside it. */
.storerail, .leaderrail, .chatcol, .streambar,
.mapcard, .setup-group, .signin-form, .pcard,
.mapmenu-panel, .castmenu-panel {
  border-style: solid !important;
  border-width: var(--frame-w) !important;
  border-image: var(--frame) 10 repeat !important;
  /* A PIXEL FRAME CANNOT BE ROUNDED. The corners are drawn in the tile, so a
     radius would clip the brass and leave the corner looking chewed. Everything
     framed is square, and that is the look rather than a compromise. */
  border-radius: 0 !important;
}


/* THE HEADER IS SHORT, AND THE FRAME COSTS IT TWICE.

   Everything else here is tall enough that twenty pixels of frame is a gutter.
   The bar is 56 and the mark inside it is a 34px face, so a frame top and
   bottom leaves 36 -- two pixels of air around her, which reads as a squeeze
   rather than a frame. So the bar keeps the full rail on its sides and a
   thinner one top and bottom: the same tile, the same brass, drawn from the
   slice's outer edge inwards, and nothing below it moves because `--sb-h` is
   still what it always was. */
.streambar {
  border-width: 4px var(--frame-w) 4px !important;
  border-image-width: 4px var(--frame-w) 4px !important;
}
/* Its own dividers were 2px lines against a 2px bottom rule. Against 4px of
   brass they read as a hairline nobody drew on purpose, so they take the
   frame's dark instead. */
.sb-mark { border-right-color: var(--frame-dark, #1f1913); }
/* The frame replaces the gutter, so the old 12px becomes the frame plus four. */
.storerail .rail-head, .leaderrail .rail-head, .chatcol .rail-head,
.storerail .rail-foot { padding-left: var(--frame-pad); padding-right: var(--frame-pad); }
.leaderlist li { padding-left: var(--frame-pad); padding-right: var(--frame-pad); }
.leadnews { padding-left: var(--frame-pad); padding-right: var(--frame-pad); }
.chatcol-feed { padding-left: var(--frame-pad); padding-right: var(--frame-pad); }
.chatcol { bottom: 0; }

/* THE STORES ARE THREE CARDS, NOT A COLUMN.

   This was `bottom: 0` and the full height of the picture -- thirty-two items
   scrolling past all day down the left-hand side. It was a good shop window and
   it cost a quarter of the screen to be one, permanently, whether or not anybody
   was shopping. Three featured items say the same thing -- a picture, a price,
   and the four characters that buy it -- in a fifth of the height.

   `height: auto` rather than a fixed number, because the card is a 56 pixel
   figure beside two lines of text and the browser knows how tall that is better
   than a magic number does. The head and the foot are the frame; only the three
   cards between them decide the height, so a change to the card never leaves a
   gap or a clipped row. */
.storerail {
  left: 0;
  width: var(--store-w, 248px);
  height: auto;
  max-height: calc(100% - var(--sb-h, 56px) - 12px);
}
.chatcol   { right: 0; width: var(--chat-w, 320px); }

/* THE RUNNING ORDER, directly under the store rail.
   Same width and the same plate as the store so the two read as one column,
   and positioned from the store's own height rather than a magic number, so it
   stays put if the shop window grows a row. */
.leaderrail {
  /* UNDER THE HEADER, NOT ABOVE THE FLOOR.

     > "move the running order box up till it sits below the header but don't
     >  change the layout."

     Only the ANCHOR moves. `top` takes the header's own height so the two can
     never overlap however that height is retuned later, and `bottom` is
     released so the card keeps its own size instead of being stretched between
     two edges. Width, max-height, the borders and the internal flex column are
     all untouched -- it is the same card, higher up. */
  top: var(--sb-h, 56px);
  bottom: auto;
  width: var(--store-w, 248px);
  max-height: 42%;
  display: flex; flex-direction: column;
}
/* THE NEWS STRIP. Fixed to the bottom of the rail so the list above it keeps
   scrolling on its own -- `leaderlist` is the flexing child and this is not, so
   a message never pushes the order out of the card. */
.leadnews {
  flex: 0 0 auto;
  padding: 7px 12px 8px;
  border-top: 1px solid var(--edge, #4a3d2f);
  font-size: 12px; line-height: 1.35;
  color: var(--ink);
  background: rgba(0, 0, 0, 0.18);
  animation: leadnews-in 160ms ease-out;
}
/* The three things worth colouring differently, and no more than three: what is
   coming for you, what somebody bought, and who is gone. */
/* THE ROWS. Colour moved from the strip to the line, because the strip now
   holds up to five of them and they are not all the same kind of news -- a
   death, a purchase and a warning can be on screen together. Older lines fade
   back so the newest reads first without anything moving. */
.leadnews-row { padding: 1px 0; }
.leadnews-row + .leadnews-row { opacity: 0.72; }
.leadnews-row + .leadnews-row + .leadnews-row { opacity: 0.55; }
.leadnews-row + .leadnews-row + .leadnews-row + .leadnews-row { opacity: 0.42; }
.leadnews-row.is-warn { color: var(--gold, #e8b44e); }
.leadnews-row.is-bad  { color: var(--bad, #d98b6a); }
.leadnews.is-warn { color: var(--gold, #e8b44e); }
.leadnews.is-bad  { color: var(--bad, #d98b6a); }
.leadnews b { font-weight: 700; }
@keyframes leadnews-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .leadnews { animation: none; } }

.leaderlist {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  margin: 0; padding: 6px 0 8px; list-style: none;
  counter-reset: place;
}
.leaderlist li {
  counter-increment: place;
  display: flex; align-items: baseline; gap: 8px;
  padding: 3px 12px;
  font-size: 12px; color: var(--ink);
  white-space: nowrap;
}
.leaderlist li::before {
  content: counter(place);
  flex: 0 0 18px; text-align: right;
  color: var(--ink-faint); font-variant-numeric: tabular-nums;
}
/* The three that matter, without turning the list into a rosette. */
.leaderlist li:nth-child(1)::before { color: var(--gold, #e8b44e); font-weight: 800; }
.leaderlist li:nth-child(2)::before,
.leaderlist li:nth-child(3)::before { color: var(--ink); }
.leaderlist .lead-name {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis;
}
.leaderlist .lead-pct {
  flex: 0 0 auto; color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
/* Out of the race: still listed, because vanishing from a list somebody is
   watching themselves on is worse than being shown as out. */
.leaderlist li.is-out .lead-name { color: var(--bad, #c46a5e); text-decoration: line-through; }
.leaderlist li.is-done .lead-pct { color: var(--gold, #e8b44e); }

.rail-head {
  flex: 0 0 auto;
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  padding: 9px 12px 8px;
  background: var(--plate-hi, #241d17);
  border-bottom: 2px solid var(--edge, #4a3d2f);
}
.rail-head b {
  font-size: 13px; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink);
}
.rail-head span {
  font-size: 11px; color: var(--ink-faint);
  letter-spacing: .04em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.rail-foot {
  flex: 0 0 auto;
  padding: 8px 12px;
  border-top: 2px solid var(--edge, #4a3d2f);
  background: var(--plate-hi, #241d17);
  font-size: 12px; color: var(--ink-faint);
}
.rail-foot b { color: var(--gold); font-weight: 800; }


/* ---- the stores ---------------------------------------------------------- */

/* THE MASK AND THE TRANSFORM ARE BOTH GONE, and they went together with the
   scroll. The soft top and bottom edges existed so an item could arrive and
   leave without a hard cut through a figure's head; nothing moves through an
   edge any more, so a gradient mask over three static cards would only be
   fading the top card for no reason. `will-change: transform` on a thing that
   never transforms is a compositor layer kept alive for nothing. */
.storerail-view {
  flex: 0 0 auto;
  overflow: hidden;
  position: relative;
}
.storerail-track { position: relative; }

/* The card that just turned over. A short warm wash rather than a slide: three
   cards in a small panel, and a viewer who was reading one of the other two
   should not have the column move under them. */
.storecard.is-fresh { animation: storecard-in 900ms ease-out 1; }
@keyframes storecard-in {
  from { background: rgba(232, 180, 78, .16); }
  to   { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .storecard.is-fresh { animation: none; }
}

.storecard {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-bottom: 1px solid rgba(74, 61, 47, .55);
}
.storecard-art { image-rendering: pixelated; width: 56px; height: 56px; }
.storecard-body { min-width: 0; }
.storecard-name {
  font-size: 13.5px; font-weight: 700; color: var(--ink);
  line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.storecard-row {
  display: flex; align-items: center; gap: 7px;
  margin-top: 4px;
}
/* The code is the product. It is set larger than the item name, on a solid
   block, because the name is what it IS and the code is what you DO -- and on a
   stream the second one is worth more screen than the first. */
.storecard-code {
  padding: 2px 7px 3px;
  background: var(--gold);
  color: #241a10;
  font-size: 13px; font-weight: 800; letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
}
.storecard-price {
  font-size: 14px; font-weight: 700; color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}
.storecard-slot {
  font-size: 10px; color: var(--ink-faint);
  letter-spacing: .1em; text-transform: uppercase;
}


/* ---- chat ---------------------------------------------------------------- */

.chatcol-feed {
  flex: 1 1 auto; min-height: 0;
  overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 8px 10px 10px;
  gap: 4px;
}
.chatline {
  font-size: 14px; line-height: 1.35;
  color: var(--ink-dim);
  word-wrap: break-word; overflow-wrap: anywhere;
  animation: chat-in .18s ease-out;
}
@keyframes chat-in { from { opacity: 0; transform: translateY(5px); } }
@media (prefers-reduced-motion: reduce) { .chatline { animation: none; } }

.chatline b { font-weight: 800; color: var(--accent); }
/* A subscriber own colour is set inline off their Twitch tags; this is only
   the fallback for somebody who never picked one. */
.chatline.is-sub b { color: var(--gold); }
/* A command reads differently from talking, because half of what makes chat
   join in is seeing that the last person !join did something. */
.chatline.is-cmd { color: var(--ink); }
.chatline.is-cmd i { font-style: normal; font-weight: 700; color: var(--gold); }
/* The game answering. Set apart from the room so it does not read as somebody
   speaking -- the connection is read-only and the game has no voice in chat. */
.chatline.is-game {
  color: var(--ink-faint);
  border-left: 2px solid var(--edge, #4a3d2f);
  padding-left: 8px;
}
/* The dinosaur they are holding, so a name in the column and a name over a
   sprite on the board are visibly the same person. */
.chatline em {
  font-style: normal; font-size: 11px; color: var(--ink-faint);
  letter-spacing: .06em; text-transform: uppercase;
}


/* ---------------------------------------------------------------------------
   The finds

   Two sizes, and the split is the point. A drizzle of four-dollar flakes is
   evidence that everybody dinosaur is working, and it belongs in a corner. A
   two-hundred-dollar jaw out of the Morrison is the reason anybody tuned in,
   and it takes the bottom of the screen for seven seconds.
   --------------------------------------------------------------------------- */

.findfeed {
  position: fixed;
  right: calc(var(--chat-w, 320px) + 18px);
  bottom: 196px;
  z-index: 50;
  width: 292px;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: 6px;
  pointer-events: none;
}
.findfeed:empty { display: none; }

.findbead {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: center; gap: 9px;
  padding: 6px 10px;
  background: var(--plate, #1b1611);
  border: 1px solid var(--edge, #4a3d2f);
  border-left: 3px solid var(--good);
  animation: bead-rise .34s cubic-bezier(.2, .9, .3, 1.1);
  transition: opacity .5s ease, transform .5s ease;
}
.findbead.is-broke { border-left-color: var(--bad); }
.findbead.is-going { opacity: 0; transform: translateY(-14px); }
.findbead-art { image-rendering: pixelated; width: 34px; height: 34px; }
.findbead-body { min-width: 0; }
.findbead-who {
  font-size: 12.5px; color: var(--ink-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.findbead-who b { color: var(--ink); font-weight: 700; }
.findbead-name {
  font-size: 13.5px; font-weight: 700; color: var(--ink-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.findbead-worth {
  font-size: 16px; font-weight: 800; color: var(--good);
  font-variant-numeric: tabular-nums;
}
.findbead.is-broke .findbead-worth { color: var(--bad); }
/* Rising, which is what the whole column does: they come up from where the
   ground is and they leave off the top. */
@keyframes bead-rise { from { opacity: 0; transform: translateY(16px); } }
@media (prefers-reduced-motion: reduce) {
  .findbead { animation: none; transition: opacity .3s; }
  .findbead.is-going { transform: none; }
}

/* The big card, squared off and sat between the two columns rather than centred
   on the window -- centred on the WINDOW is off-centre in the picture once
   there is a shop down one side of it. */

/* A find worth stopping the room for. `is-big` is on every card that gets this
   far -- everything cheaper went past in the corner -- so it earns the flag and
   nothing else. The heavier frame is saved for `is-record` below, because two
   rules both setting the border to 3px would have meant no card ever looked
   different from any other. */


/* ---------------------------------------------------------------------------
   What is left over

   Three things that were positioned against the edges of the window and now
   have a column in the way of them.
   --------------------------------------------------------------------------- */

/* Site news -- a claim jumped, weather in the night.
   ---------------------------------------------------------------------------
   A TICKER ALONG THE BOTTOM, NOT A STACK IN THE PICTURE.

   This used to sit at `bottom: 196px` hard against the store column, which is
   not the edge of anything -- it is a fifth of the way up the middle of the
   board, directly over the ground people are digging. Four stacked cards there
   cover the one part of the frame the whole broadcast exists to show, and they
   cover it for two and a half seconds at a time, all night.

   So it is a lane on the bottom rule of the picture, between the two columns,
   one card high. The middle of the frame is clear.

   NEWEST IN AT THE LEFT, OLDER ONES PUSHED RIGHT AND OFF THE END, which is a
   ticker and is what `row-reverse` buys with no Javascript at all. `toast()`
   appends the new card and drops `firstChild` when there are more than four:
   under `row-reverse` the last child is leftmost and the first child is
   rightmost, so appending enters at the left, everything already there slides
   right, and the one that falls off is the oldest, off the right-hand end.
   `justify-content: flex-end` is what anchors the row to the LEFT edge -- in
   `row-reverse` the main axis runs right to left, so main-end is the left. */
body.is-broadcast .toasts {
  top: auto;
  bottom: 18px;
  left: calc(var(--store-w, 248px) + 18px);
  right: calc(var(--chat-w, 320px) + 18px);
  flex-direction: row-reverse;
  justify-content: flex-end;
  align-items: flex-end;
  flex-wrap: nowrap;
  gap: 8px;
  /* The card that has been pushed past the end is gone from the picture before
     it is gone from the DOM. Without this it hangs over the chat column. */
  overflow: hidden;
}

/* The lane sits on the bottom rule and stays there. It used to step up when
   the big find card was showing; that card is gone -- see `showFind` in
   `ui.js` -- so nothing shares this height any more. */

body.is-broadcast .toast {
  /* Narrower than the stack was, because four of them now sit side by side
     rather than one above another. Four at 300 plus the gaps is 1224, which
     fits the middle column at 1080p with the shop and the chat either side. */
  max-width: 300px;
  /* SIZED TO ITS OWN TEXT, AND NOT SQUEEZED BY ITS NEIGHBOURS.
     A flex item defaults to `flex-shrink: 1`, so four cards in one lane shared
     the width out between them and each came down to about seventy pixels --
     which turned every message into a column of one-word lines four times
     taller than the lane it was in. `0 0 auto` says take the width your text
     wants, up to 300, and let the lane clip whatever runs off the end. Clipping
     the oldest card off the right is the ticker working, not a layout fault. */
  flex: 0 0 auto;
  /* IN FROM THE LEFT, OUT TO THE RIGHT, which is the direction the lane runs.
     The stack version came in from the right because it lived on the right-hand
     edge; here that would have every card arrive travelling against the flow it
     is joining. */
  animation: toast-in-lane 0.22s cubic-bezier(0.2, 0.9, 0.3, 1.2);
  border-radius: 0;
  background: var(--plate, #1b1611);
  border: 1px solid var(--edge, #4a3d2f);
  border-left-width: 3px;
  backdrop-filter: none;
  box-shadow: none;
  font-size: 14px;
}

/* The giveaway banner, under the bar rather than through it. */
body.is-broadcast .contest {
  position: fixed;
  top: calc(var(--sb-h, 56px) + 14px);
  border-radius: 0;
  border-width: 2px;
  background: var(--plate, #1b1611);
  box-shadow: none;
  font-size: 20px;
  padding: 9px 22px;
}

/* The gear, moved out of the bottom right corner entirely.
   Measured there with the columns up: it sat at x 1104-1160 and the find card
   ran to 1158, so the one control that survives into this mode was wedged in a
   two-pixel gap between the card and chat. Under the bar instead -- the bar is
   where settings belong anyway, and the top of the right-hand strip is the only
   part of the frame nothing else has a claim on. */
body.is-broadcast .tabs {
  right: calc(var(--chat-w, 320px) + 16px);
  top: calc(var(--sb-h, 56px) + 12px);
  bottom: auto;
  justify-content: flex-start;
}


/* The one that beat everything else today. A heavier frame and the loud word
   on the flag -- the difference has to be visible from the corner of an eye,
   because the person it is for is talking to somebody at the time. */


/* ---------------------------------------------------------------------------
   THE FOREMAN'S PACK

   A drawer, under the gear, on the right-hand strip -- the one column of the
   frame the stores, chat and the find card have no claim on. Shut it is one
   pack; open it is the toolbar, laid out downwards because that is the
   direction there is room in.
   --------------------------------------------------------------------------- */

.packmenu {
  position: fixed;
  right: calc(var(--chat-w, 320px) + 16px);
  top: calc(var(--sb-h, 56px) + 70px);
  z-index: 58;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
}
.packmenu[hidden] { display: none; }

.packmenu-btn {
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 52px;
  padding: 0;
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  border-radius: 0;
  cursor: pointer;
  transition: border-color .12s;
}
.packmenu-btn:hover,
.packmenu.is-open .packmenu-btn { border-color: var(--gold); }
.packmenu-art { image-rendering: pixelated; display: block; }

.packmenu-tools {
  display: flex; flex-direction: column;
  min-width: 190px;
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  max-height: 58vh;
  overflow-y: auto;
}
.packmenu-tools[hidden] { display: none; }

.packtool {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 12px 6px 8px;
  background: none;
  border: 0;
  border-bottom: 1px solid rgba(74, 61, 47, .55);
  color: var(--ink-dim);
  font: inherit; font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.packtool:last-child { border-bottom: 0; }
.packtool:hover { background: var(--plate-hi, #241d17); color: var(--ink); }
.packtool-art { image-rendering: pixelated; display: flex; flex: 0 0 auto; }
.packtool-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* In her hand. */
.packtool.is-on { color: var(--gold); font-weight: 700; }
.packtool.is-on .packtool-name::after { content: ' ·'; }

/* Not owned -- shown, because knowing a pick exists is half the reason to buy
   one, and clicking it goes to the stores rather than doing nothing. */
.packtool.is-locked { color: var(--ink-faint); }
.packtool.is-locked .packtool-art { opacity: .4; }
.packtool.is-locked .packtool-name::after {
  content: ' — buy';
  color: var(--warn);
  font-size: 12px;
}


/* ---------------------------------------------------------------------------
   THE PARK MAP

   Same drawer as the pack, one place further down the right-hand strip. It
   opens leftward because there is nothing to its left but ground, and the map
   is the widest thing either drawer holds.
   --------------------------------------------------------------------------- */

/* THE RAIL'S SLOTS, 58px apart: 52 of button and the 6px gap the menus already
   use internally. The cast button and the night button were four pixels apart
   and drew on top of each other, which is what "the button is behind the
   character button" was. Order down the rail: kit, map, cast, night, home,
   console, horde. The camp and rest buttons were removed from between them. */
/* THE MAP MOVED UP. It sat a hundred and twenty-eight pixels down the board,
   in a column with five other buttons that have since gone or moved -- so it
   was a lone icon floating in the middle of the picture with nothing to belong
   to. Tucked under the header instead, where the other controls are. */
.mapmenu {
  position: fixed;
  right: calc(var(--chat-w, 320px) + 16px);
  top: calc(var(--sb-h, 56px) + 10px);
  z-index: 58;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
}
.mapmenu[hidden] { display: none; }

.mapmenu-btn {
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 52px;
  padding: 0;
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  border-radius: 0;
  cursor: pointer;
  transition: border-color .12s;
}
.mapmenu-btn:hover,
.mapmenu.is-open .mapmenu-btn { border-color: var(--gold); }
.mapmenu-art { image-rendering: pixelated; display: block; }

.mapmenu-panel {
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  padding: 10px;
  /* The one scroll region in the drawer. Its ceiling is set from JS, which is
     the only side that knows where the drawer's top edge landed. */
  overflow-y: auto;
}
.mapmenu-panel[hidden] { display: none; }

.mapmenu-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin-bottom: 8px;
}
.mapmenu-head b {
  font-size: 12px; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink);
}
.mapmenu-head span {
  font-size: 11px; color: var(--ink-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Pixelated, because it is drawn a cell at a time and a smoothed map of square
   sections is a blurred map of square sections. */
.mapmenu-canvas { image-rendering: pixelated; display: block; }

.mapmenu-key {
  display: flex; gap: 12px; margin-top: 8px;
  font-size: 10.5px; color: var(--ink-faint);
  letter-spacing: .06em; text-transform: uppercase;
}
.mapmenu-key span { display: flex; align-items: center; gap: 5px; }
.mapmenu-key i { width: 9px; height: 9px; display: block; }
.mapmenu-key .mk-you { border: 2px solid #f2e4c8; border-radius: 999px; }
.mapmenu-key .mk-camp { background: var(--gold); }
.mapmenu-key .mk-unseen { background: #6d5f4c; opacity: .42; }

/* ---------------------------------------------------------------------------
   THE PARK PICKER
   ---------------------------------------------------------------------------
   Under the map, in the same drawer, because "which park am I in" and "where
   am I in it" are the same question at two scales.

   Rows are numbered in the intended order and the number is the only ornament:
   the ladder from the Gates to Echo Park IS information -- travel cost and what
   lies at the surface both run the same way -- so a numbered list is telling
   the truth about a sequence rather than decorating a list.

   The list is capped and scrolls. Five parks fit; the cap is there so that a
   sixth does not silently push the map off the top of the screen.
   --------------------------------------------------------------------------- */

.parkpick {
  margin-top: 10px;
  border-top: 1px solid var(--edge, #4a3d2f);
  padding-top: 8px;
}

.parkpick-head {
  font-size: 10.5px; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 6px;
}

/* No max-height and no scroller of its own: the panel above it does the
   scrolling for everything, so the wheel means one thing in this drawer. */
.parkpick-list {
  display: flex; flex-direction: column; gap: 2px;
}

.parkpick-row {
  display: flex; align-items: flex-start; gap: 8px;
  width: 100%;
  padding: 6px 7px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 0;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.parkpick-row:hover:not(:disabled) {
  background: #241d16;
  border-color: var(--edge, #4a3d2f);
}
.parkpick-row:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: -2px;
}

/* The one you are standing in is not a destination. It keeps full contrast --
   dimming it would read as unavailable rather than as current -- and loses the
   pointer instead. */
.parkpick-row.is-current {
  cursor: default;
  background: #241d16;
  border-color: var(--edge, #4a3d2f);
}

.parkpick-row.is-armed {
  background: #2c2116;
  border-color: var(--gold);
}

.parkpick-n {
  flex: 0 0 auto;
  width: 16px;
  font-size: 10.5px; font-weight: 800;
  line-height: 16px;
  text-align: center;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.parkpick-row.is-current .parkpick-n,
.parkpick-row.is-armed .parkpick-n { color: var(--gold); }

.parkpick-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.parkpick-name {
  font-size: 12px; font-weight: 700;
  line-height: 1.25;
}

.parkpick-div {
  font-size: 10px;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-faint);
}
.parkpick-row.is-current .parkpick-div { color: var(--gold); }

/* The prose only appears on the row you have reached for, so it is allowed to
   wrap and to be a comfortable measure. */
.parkpick-blurb {
  margin-top: 3px;
  font-size: 11px; line-height: 1.4;
  color: var(--ink-faint);
  max-width: 30ch;
}

.parkpick-warn {
  margin-top: 5px;
  font-size: 11px; line-height: 1.35; font-weight: 600;
  color: var(--gold);
  max-width: 30ch;
}

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

/* THE HOVER CARD -- a hand, as a trading card.

   Follows the pointer, so it is positioned in JS and only its LOOK is here.
   `pointer-events: none` is load-bearing rather than tidy: a panel that follows
   the cursor and accepts clicks is a panel that steals every one of them, and
   the board underneath is the thing people are actually pointing at.

   Sized in absolutes rather than by content, because a card that changes width
   with the length of somebody's name jitters as the pointer moves between two
   hands and reads as a glitch rather than as a card. */
.pcard {
  position: fixed;
  z-index: 70;
  width: 216px;
  pointer-events: none;
  background: linear-gradient(180deg, #221a12 0%, #171009 100%);
  border: 2px solid var(--gold, #e8c98a);
  border-radius: 3px;
  box-shadow: 0 6px 24px rgba(0,0,0,.6), inset 0 0 0 1px rgba(0,0,0,.5);
  font: 12px ui-monospace, monospace;
  color: #d9c9a8;
  overflow: hidden;
}
.pcard[hidden] { display: none; }

.pcard-head { display: flex; gap: 10px; padding: 10px 10px 8px; align-items: center; }
.pcard-art {
  image-rendering: pixelated;
  width: 72px; height: 72px;
  background: radial-gradient(circle at 50% 62%, #3a2c1c 0%, #1a120a 72%);
  border: 1px solid #4a3d2f;
  flex: 0 0 auto;
}
.pcard-who { min-width: 0; }
.pcard-who b {
  display: block; color: var(--gold, #e8c98a);
  font-size: 13px; letter-spacing: .04em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pcard-who span {
  display: block; color: #9b8a6b; font-size: 11px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pcard-who i {
  display: block; font-style: normal; margin-top: 3px;
  color: #b6a382; font-size: 11px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* One row a stat. A grid rather than flex so the numbers line up down the
   right edge whatever the labels say -- a stat block that does not align is a
   stat block nobody can compare two of. */
.pcard-rows {
  display: grid; grid-template-columns: 1fr auto;
  gap: 2px 10px; padding: 0 12px 8px;
}
.pcard-rows dt, .pcard-rows .k { color: #8a7a5e; }
.pcard-rows .v { color: #e8c98a; text-align: right; font-variant-numeric: tabular-nums; }
.pcard-rows .v.dim { color: #7a6a52; }

.pcard-foot {
  border-top: 1px solid #3a2f22;
  padding: 6px 12px 8px;
  color: #8a7a5e; font-size: 11px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Call it a night. Same plate as the pack and the map, one further down the
   right-hand strip -- the header this used to live in is not drawn here. */
.nightbtn {
  position: fixed;
  right: calc(var(--chat-w, 320px) + 16px);
  top: calc(var(--sb-h, 56px) + 244px);
  z-index: 58;
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 52px;
  padding: 0;
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  border-radius: 0;
  cursor: pointer;
  transition: border-color .12s;
}
.nightbtn[hidden] { display: none; }
.nightbtn:hover { border-color: var(--gold); }
.nightbtn-art { image-rendering: pixelated; display: block; }

/* Send up the dead. Below the console, on the same strip and the same plate --
   it is a thing you DO to the board, like calling it a night, rather than a
   panel that slides out. See `wireHorde`. */
.hordebtn {
  position: fixed;
  right: calc(var(--chat-w, 320px) + 16px);
  top: calc(var(--sb-h, 56px) + 302px);
  z-index: 58;
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 52px;
  padding: 0;
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  border-radius: 0;
  cursor: pointer;
  transition: border-color .12s;
}
.hordebtn[hidden] { display: none; }
/* Green rather than gold on hover, because green is what the board does when
   this is pressed. See `FOG_HORDE` and `HORDE_TINT` in `render.js`. */
.hordebtn:hover { border-color: #6fbf72; }
.hordebtn-art { image-rendering: pixelated; display: block; }

/* Back to the tent. Fourth on the right-hand strip. */
.homebtn {
  position: fixed;
  right: calc(var(--chat-w, 320px) + 16px);
  top: calc(var(--sb-h, 56px) + 302px);
  z-index: 58;
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 52px;
  padding: 0;
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  border-radius: 0;
  cursor: pointer;
  transition: border-color .12s;
}
.homebtn[hidden] { display: none; }
.homebtn:hover { border-color: var(--gold); }
.homebtn-art { image-rendering: pixelated; display: block; }

/* The operator's console. Fifth and last on the right-hand strip.

   The header button (#btnAdmin) is still the role-gated one; this is the
   broadcast-mode door to the same screen, because the header is not drawn on a
   stream and the console was otherwise unreachable there. */
.adminbtn {
  position: fixed;
  right: calc(var(--chat-w, 320px) + 16px);
  top: calc(var(--sb-h, 56px) + 244px);
  z-index: 58;
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 52px;
  padding: 0;
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  border-radius: 0;
  cursor: pointer;
  transition: border-color .12s;
}
.adminbtn[hidden] { display: none; }
.adminbtn:hover { border-color: var(--gold); }
.adminbtn-art { image-rendering: pixelated; display: block; }

/* Chat's money. A ledger, so it is read down a column rather than across a
   row: the purse is the number anybody actually came here for. */
.bank-tot {
  display: flex; flex-wrap: wrap; gap: 18px;
  padding: 10px 14px; margin-bottom: 12px;
  background: var(--plate, #1b1611);
  border: 1px solid var(--edge, #4a3d2f);
}
.bank-tot div { display: flex; flex-direction: column; gap: 2px; }
.bank-tot b { font-size: 17px; font-variant-numeric: tabular-nums; }
.bank-tot span {
  font-size: 10px; letter-spacing: .07em; text-transform: uppercase;
  opacity: .62;
}
.bank-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.bank-table th {
  text-align: left; padding: 6px 10px;
  font-size: 10px; letter-spacing: .07em; text-transform: uppercase;
  opacity: .62; border-bottom: 1px solid var(--edge, #4a3d2f);
}
.bank-table td {
  padding: 5px 10px;
  border-bottom: 1px solid rgba(74, 61, 47, .45);
}
.bank-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.bank-table .purse { color: var(--gold, #e8b44e); font-weight: 600; }
.bank-table .dim { opacity: .5; }
.bank-who { display: flex; align-items: center; gap: 7px; }
.bank-sub {
  font-size: 9px; letter-spacing: .06em; text-transform: uppercase;
  padding: 1px 5px; border: 1px solid var(--gold, #e8b44e);
  color: var(--gold, #e8b44e);
}
.bank-out {
  font-size: 9px; letter-spacing: .06em; text-transform: uppercase;
  padding: 1px 5px; border: 1px solid #b4553f; color: #d8735a;
}

/* THE BROADCAST FURNITURE GETS OUT OF THE WAY OF A FULL SCREEN.

   The overlay is fixed-position and sits at z-index 55-60 so it rides over the
   board; a `.screen` is at 40, because screens were written when the only thing
   under them was the board. Open the console on a stream and the top bar, the
   stores, the chat column and the whole right-hand strip stood on top of it --
   the console's own heading was behind the bar.

   Hidden rather than re-stacked. A ledger is read, not glanced at over the top
   of a game, and the buttons underneath it do nothing useful while it is up. */
body.console-open #streamBar,
body.console-open #storeRail,
body.console-open #chatCol,
body.console-open #findCard,
body.console-open #packMenu,
body.console-open #mapMenu,
body.console-open #nightBtn,
body.console-open #homeBtn,
body.console-open #adminBtn { display: none; }

/* Fill the chat with a fake room.

   Directly under GO LIVE, in the same slot, because it is the same decision
   made the other way. Deliberately quiet against that button: one of these is
   what the game is for and the other is a rehearsal of it, and they should not
   look like equals. */
.rehearse {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 22px;
  z-index: 57;
  padding: 6px 14px;
  background: rgba(27, 22, 17, .82);
  border: 1px solid var(--edge, #4a3d2f);
  color: var(--ink-dim, #a2947c);
  font: inherit;
  font-size: 11px;
  letter-spacing: .07em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.rehearse[hidden] { display: none; }
.rehearse:hover { color: var(--gold, #e8b44e); border-color: var(--gold, #e8b44e); }

/* THE TRAY, GRADED.

   A find card used to look the same whatever was on it. These three bands are
   the same card with different weight -- no new elements, nothing added to
   read, just the difference between "an object" and "look at this".

   Set on the card rather than on the swatch so the whole row lifts together;
   a heavier picture in a lighter frame reads as a mistake. */
.find-card[data-worth="notable"] {
  border-color: rgba(232, 180, 78, .34);
  background: linear-gradient(90deg, rgba(232, 180, 78, .05), transparent 42%);
}
.find-card[data-worth="notable"] .price { color: var(--gold, #e8b44e); }

.find-card[data-worth="remarkable"] {
  border-color: var(--gold, #e8b44e);
  background: linear-gradient(90deg, rgba(232, 180, 78, .12), transparent 55%);
  box-shadow: inset 3px 0 0 var(--gold, #e8b44e);
}
.find-card[data-worth="remarkable"] .find-title {
  font-size: 15px;
  letter-spacing: .01em;
  color: #f4e2bd;
}
.find-card[data-worth="remarkable"] .price {
  color: var(--gold, #e8b44e);
  font-weight: 700;
  font-size: 15px;
}

/* FEEDING TIME: the dig's own chrome gets out of the way.

   The pack, the map, the rest button and the compass sit over the middle of the
   board -- fine on a dig, where the middle of the board is a trench you are not
   looking at, and wrong the moment something crosses it. Watched on a stream:
   the bull walked behind those four buttons and stayed there, and for one whole
   frame the only part of the animal on screen was its name plate, which is the
   picture exactly the wrong way round.

   Hidden rather than moved, and for the same reason the console hides them --
   none of them does anything useful during a hunt. You cannot rest, there is
   nothing to change in the pack, and the compass points at a camp nobody is
   walking to. They come back at dawn.

   `#storeRail` and `#chatCol` stay. Chat is the thing the mode is for and the
   stores are how a viewer spends what surviving paid them; neither is over the
   board.

   `#packMenu` AND `#mapMenu` USED TO BE ON THIS LIST AND SHOULD NOT HAVE BEEN.

   The rule was written to clear dig chrome that was drawing over the board and
   covering the animal. Those two are not that. `#packMenu` is the store -- the
   thing a viewer spends a night's wage in, and the reason surviving pays at all
   -- and `#mapMenu` is the map. Hiding them did not clear the board, it took the
   streamer store out of the game for the whole of every night, which is most of
   the time the game is being watched.

   The test for this list is "does it draw over the board and do nothing during
   a hunt", and it has to be both. A panel that is merely useless is not in the
   way; a panel that is in the way is only removable if it is also useless. */
body.is-night #nightBtn,
body.is-night #homeBtn,
body.is-night #adminBtn { display: none; }

/* ---- the cast window ----------------------------------------------------
   Which characters the house is offering. Built on the map menu's shape --
   a button on the right-hand strip with a panel hanging off it -- because
   that is the pattern the overlay already uses for "open it, look, close it".
   Sits one slot below the map so the strip stays in one column. */

.castmenu {
  position: fixed;
  right: calc(var(--chat-w, 320px) + 16px);
  top: calc(var(--sb-h, 56px) + 186px);
  z-index: 58;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
}
.castmenu[hidden] { display: none; }

.castmenu-btn {
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 52px;
  padding: 0;
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  cursor: pointer;
}
.castmenu-btn:hover { border-color: var(--gold); }
.castmenu.is-open .castmenu-btn { border-color: var(--gold); }
.castmenu-art { image-rendering: pixelated; }

.castmenu-panel {
  background: var(--plate, #1b1611);
  border: 2px solid var(--edge, #4a3d2f);
  padding: 10px;
  width: 300px;
  /* Tall lists scroll rather than running off the bottom of a 1080 frame. */
  max-height: min(60vh, 520px);
  overflow-y: auto;
}
.castmenu-panel[hidden] { display: none; }

.castmenu-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin-bottom: 6px;
}
.castmenu-head b { color: var(--bone); letter-spacing: 0.04em; }
.castmenu-head span { font-size: var(--t-sm); color: var(--ink-faint); }

.castmenu-note {
  margin: 0 0 8px;
  font-size: var(--t-sm);
  line-height: 1.35;
  color: var(--ink-faint);
}

.castmenu-tools { display: flex; gap: 6px; margin-bottom: 8px; }

.castmenu-list { display: flex; flex-direction: column; gap: 1px; }

.castrow {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 4px 6px;
  cursor: pointer;
}
.castrow:hover { background: rgba(232, 180, 78, 0.09); }
.castrow-name { font-size: var(--t-sm); }
/* The rung rather than the price: the host is deciding what the stream is
   about, and a tier name says what a character DOES. */
.castrow-tier { font-size: var(--t-sm); color: var(--ink-faint); }
/* A switched-off row stays legible -- it is a list you are editing, not a
   list of failures -- but plainly not in play. */
.castrow.is-off .castrow-name,
.castrow.is-off .castrow-tier { color: var(--ink-faint); opacity: 0.55; }

/* ---- the balance pages --------------------------------------------------
   The same window, three more pages: every number a character or an animal
   or the night is made of, with what source says beside it.

   IT IS USED OVER A LIVE BOARD, which is the constraint that shaped all of
   this. Forty hands and a dozen animals are running behind it, so nothing in
   here animates, nothing transitions, and nothing has a shadow or a blur on
   it -- a panel that costs a millisecond a frame to composite is a panel that
   costs the show a millisecond a frame for as long as it is open. Colour and
   a one-pixel edge do the whole job. */

.castmenu-panel { width: 360px; }

.castmenu-head-sub { margin-top: 2px; }

.balreset {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-faint);
  background: none;
  border: 1px solid var(--edge, #4a3d2f);
  padding: 1px 6px;
  cursor: pointer;
}
.balreset:hover:not(:disabled) { color: var(--gold); border-color: var(--gold); }
.balreset:disabled { opacity: 0.35; cursor: default; }

.castmenu-tabs {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  margin-bottom: 8px;
}
.casttab {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-faint);
  background: none;
  border: 0;
  /* The lit edge is on the BOTTOM only. A full box around the live tab reads
     as a button rather than as a page you are on, and four boxed buttons in a
     row read as four things to press rather than as one thing with four
     pages. */
  border-bottom: 2px solid var(--edge, #4a3d2f);
  padding: 4px 2px;
  cursor: pointer;
}
.casttab:hover { color: var(--gold); }
.casttab.is-on { color: var(--gold); border-bottom-color: var(--gold); }

.castpage[hidden] { display: none; }

.ballist { display: flex; flex-direction: column; gap: 10px; }

.balgroup { border-top: 1px solid var(--edge, #4a3d2f); padding-top: 6px; }
.balgroup-head {
  display: flex; align-items: baseline; gap: 8px;
  margin-bottom: 4px;
}
.balgroup-head b { color: var(--bone); letter-spacing: 0.04em; }
.balgroup-note {
  font-size: var(--t-sm);
  color: var(--ink-faint);
  flex: 1 1 auto;
  min-width: 0;
}
.balgroup-undo {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-faint);
  background: none;
  border: 0;
  border-bottom: 1px dotted currentColor;
  padding: 0;
  cursor: pointer;
}
.balgroup-undo:hover { color: var(--gold); }

.balgrid { display: flex; flex-direction: column; gap: 1px; }

.balnote {
  margin: 5px 0 0;
  font-size: var(--t-sm);
  line-height: 1.35;
  color: var(--ink-faint);
}
.balnote-lead { margin: 0 0 8px; }

/* One field: what it is, what it is set to, what SOURCE says, and the way
   back. The authored value is always on screen when it differs, because an
   edited field that does not look edited is how a host loses an evening. */
.balfield {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 68px 40px 20px;
  align-items: center;
  gap: 6px;
  padding: 2px 4px;
}
.balfield:hover { background: rgba(232, 180, 78, 0.09); }
.balfield-name {
  font-size: var(--t-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.balfield-in {
  font: inherit;
  font-size: var(--t-sm);
  color: inherit;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid var(--edge, #4a3d2f);
  padding: 1px 4px;
  width: 100%;
  min-width: 0;
}
.balfield-in:focus { outline: none; border-color: var(--gold); }
/* WHAT SOURCE SAYS. Never in the gold -- gold is what the host has changed,
   and the shipped number is the quiet half of the pair. */
.balfield-was {
  font-size: var(--t-sm);
  color: var(--ink-faint);
  opacity: 0.7;
  text-align: right;
  overflow: hidden;
}
.balfield-undo {
  font: inherit;
  font-size: var(--t-sm);
  line-height: 1;
  color: var(--ink-faint);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.balfield-undo:hover:not(:disabled) { color: var(--gold); }
.balfield-undo:disabled { opacity: 0.2; cursor: default; }

/* THIS ONE IS THE HOST'S. */
.balfield.is-over .balfield-in { border-color: var(--gold); color: var(--gold); }
.balfield.is-over .balfield-name { color: var(--gold); }
/* And this one is not the host's but is not shipped either -- a character
   carrying a rung the host moved. A different state, and it says so. */
.balfield.is-inherited .balfield-in { border-color: rgba(232, 180, 78, 0.4); }
/* Refused, and only while it is being typed. `change` puts the box back. */
.balfield.is-bad .balfield-in { border-color: #b8543a; color: #e2846a; }

/* An animal is a name with two numbers under it, so the name gets its own
   line with the derived hit points on the end of it. Squeezed onto one row
   the name would have thirty pixels and every dinosaur would read as an
   ellipsis. */
.baldino {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin-top: 5px;
  padding: 0 4px;
}
.baldino-name {
  font-size: var(--t-sm);
  color: var(--bone);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.balgrid-dino .balfield { padding-left: 12px; }
.balfield-derived {
  font-size: var(--t-sm);
  color: var(--ink-faint);
  opacity: 0.7;
  text-align: right;
  white-space: nowrap;
}

/* The twenty-nine faces, folded away under their rung. */
.balcast { margin-top: 6px; display: flex; flex-direction: column; gap: 0; }
.balcast-row {
  display: flex; align-items: baseline; gap: 6px;
  padding: 1px 4px;
}
.balcast-row:hover { background: rgba(232, 180, 78, 0.09); }
.balcast-name {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-faint);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-align: left;
  flex: 1 1 auto;
}
.balcast-name::before { content: '\203A\00a0'; }
.balcast-row.is-open .balcast-name::before { content: '\2304\00a0'; }
.balcast-name:hover { color: var(--gold); }
.balcast-row.is-open .balcast-name { color: var(--gold); }
.balcast-tag { font-size: var(--t-sm); color: var(--ink-faint); opacity: 0; }
.balcast-tag.is-on { color: var(--gold); opacity: 1; }
.balcast-body[hidden] { display: none; }
.balcast-body {
  margin: 0 0 4px 10px;
  border-left: 1px solid var(--edge, #4a3d2f);
  padding-left: 6px;
}

/* The five sound channel switches, in the on-air panel under the volume
   sliders. A diagnostic rather than a setting -- see `CHANNELS` in sound.js. */
.onair-chans {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .85rem;
  margin: .55rem 0 .3rem;
}
.onair-chan {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: .82rem;
  cursor: pointer;
  user-select: none;
}
.onair-chan input { margin: 0; cursor: pointer; }
.onair-chan span { opacity: .85; }
.onair-chan input:not(:checked) + span { opacity: .45; text-decoration: line-through; }

/* The bits call: a circular portrait of whatever a cheer just sent in, bottom
   right, alongside the roar. See `beastCall` in ui.js. It sits above the toast
   lane rather than in it -- a toast scrolls away with the chatter and this is
   the receipt for money somebody spent. */
.beastcall {
  position: absolute;
  /* Clear of the chat column. `stage-wrap` spans the whole width, so a plain
     `right: 1.1rem` puts this UNDER the chat -- measured: the icon rendered at
     x 1455 and `elementFromPoint` came back with a chat line on top of it.
     `--chat-w` is the column's own variable, so this follows it if it moves and
     falls back to the same 320px the column defaults to. */
  right: calc(var(--chat-w, 320px) + 1.1rem);
  bottom: 1.1rem;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: .7rem;
  pointer-events: none;
  opacity: 0;
  transform: translateY(10px) scale(.94);
  transition: opacity .34s ease, transform .34s cubic-bezier(.2,.9,.3,1.2);
}
.beastcall.is-in { opacity: 1; transform: none; }

.beastcall-ring {
  width: 112px;
  height: 112px;
  border-radius: 50%;
  overflow: hidden;
  background: radial-gradient(circle at 50% 38%, #3a2a1c 0%, #1a120c 78%);
  border: 3px solid #e8b44e;
  box-shadow: 0 0 0 2px rgba(0,0,0,.55), 0 6px 18px rgba(0,0,0,.5);
  display: grid;
  place-items: center;
}
.beastcall-ring canvas {
  display: block;
  image-rendering: pixelated;
}

.beastcall-say {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  padding: .45rem .7rem;
  border-radius: 4px;
  background: rgba(20,14,9,.82);
  font-size: .84rem;
  color: #e8dcc4;
  max-width: 16rem;
}
.beastcall-say b { color: #e8b44e; }

@media (prefers-reduced-motion: reduce) {
  .beastcall { transition: opacity .2s linear; transform: none; }
  .beastcall.is-in { transform: none; }
}

/* THE SOUND BOARD. One row an action: what it is, what plays, how loud.
   A grid rather than flex so the dropdowns and the sliders line up down the
   panel -- sixteen rows of controls that do not align is a list nobody can
   scan for the one they came to change. */
.adm-row .adm-pick {
  background: #17120d; color: #d9c9a8;
  border: 1px solid #4a3d2f; border-radius: 0;
  font: 12px ui-monospace, monospace; padding: 4px 6px;
  max-width: 240px;
}
.adm-row .adm-vol { width: 110px; accent-color: var(--gold, #e8c98a); }


/* ==========================================================================
   THE FRONT OF HOUSE — map select, stream setup, countdown, scoreboard
   ==========================================================================

   Four blocks for the five states in `js/race.js`; the fourth state is the race
   itself and it has no chrome of its own, which is the point of it.

   `.is-front` rides on top of `.screen` and does two things the store and the
   claims office do not need. It sits ABOVE the rail and the panel (z-index 60
   against their 40) because these screens are the game rather than a drawer
   pulled out of it — a rail poking through the map select would offer you the
   store before you have chosen a course. And it is fully opaque rather than a
   94%-black blur over the board: the board behind it is the wrong world (it is
   whatever section boot happened to open) and letting it show through was
   confusing on the first pass — you could see a canyon that was not any of the
   three on offer.
   ========================================================================== */

.screen.is-front {
  z-index: 60;
  background: var(--bg);
  backdrop-filter: none;
}

/* And the game's own furniture goes away with it. The screens are opaque and
   sit above everything, so this is not about what can be SEEN -- it is about
   what can be reached: the rail, the toolbar and the crew cards are still in the
   tab order behind an opaque page, so a person navigating the map select by
   keyboard walks off the end of it into a store button they cannot see.
   `visibility: hidden` takes an element out of the tab order; `display: none`
   would too and would also relayout the stage, which is a canvas that has to be
   resized and repainted when it changes size. */
body.is-front .tabs,
body.is-front .panel,
body.is-front .toolbar-wrap,
body.is-front .crewrail,
body.is-front .startstep,
body.is-front .rehearse { visibility: hidden; pointer-events: none; }

/* ---- map select ---------------------------------------------------------- */

.maps-inner { max-width: 1180px; }

/* AUTO-FIT AND NOT THREE COLUMNS. There are three presets today and the table
   in `coursegen.js` is a table — a fourth duration is a row in it, and a grid
   hard-coded to three would silently drop the card off the end of the row. */
.mapcards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 18px;
  align-items: stretch;
}

.mapcard {
  display: flex;
  flex-direction: column;
  text-align: left;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg-panel);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  overflow: hidden;
  /* `border-color` was the third thing in this list and there is nothing left
     for it to animate -- the frame paints over it. */
  transition: transform .14s, box-shadow .14s;
}
/* The lift and the shadow only. This used to turn the border gold and drop the
   outline on the strength of it; the frame paints over the border colour, so
   both halves of that went dead at once and hovering a course did nothing you
   could see. The tint and the focus ring that replace them are at the foot of
   this file, with the rest of the frame's second sweep. */
.mapcard:hover, .mapcard:focus-visible {
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}

/* THE OVERVIEW PICTURE. A fixed height rather than a fixed aspect, because the
   three courses measured 205x132, 283x360 and 256x651 squares and
   `Levels.thumbnail` draws into a SQUARE canvas at a whole-number scale. A tall
   course therefore uses the middle third of its canvas and a wide one fills it.
   Letting each card size itself to its own picture would give a menu of three
   cards of three different heights, which reads as a bug. */
.mapcard-art {
  /* SQUARE, because the canvas is. `Levels.thumbnail` draws into a square at a
     whole-number scale, so a 210px-tall box on a card 300px wide showed a square
     picture in the middle of a letterbox with ninety pixels of gradient either
     side of it. Matching the box to the drawing puts every pixel of the card to
     work and makes the three cards the same shape whatever their courses are. */
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  background: radial-gradient(120% 90% at 50% 15%, #2b221a 0%, #1b1511 70%);
  border-bottom: 1px solid var(--line);
}
.mapcard-art canvas {
  image-rendering: pixelated;
  max-width: 100%;
  max-height: 100%;
}

/* THE VOTE, ON TOP OF THE PICTURE.
   Both sit in the same grid cell as the canvas -- `.mapcard-art` is a one-cell
   grid -- so they overlay it without a wrapper and without taking it out of the
   flow that centres it. `place-self` puts each in its own corner. */
.mapcard-slot,
.mapcard-votes { grid-area: 1 / 1; pointer-events: none; }

/* The numeral to type. Big enough to read off a stream at bitrate, which is the
   whole job: a viewer is looking at a compressed video of this. */
.mapcard-slot {
  place-self: start start;
  margin: 10px;
  min-width: 46px;
  padding: 2px 10px;
  font: 700 34px/1.25 var(--face-num, inherit);
  text-align: center;
  color: #f4ead6;
  background: rgba(12, 9, 7, 0.72);
  border: 1px solid rgba(244, 234, 214, 0.28);
  border-radius: 10px;
}

/* And what the room has said. Tabular figures so a count going 9 -> 10 does not
   shove the box sideways. */
.mapcard-votes {
  place-self: start end;
  margin: 10px;
  min-width: 40px;
  padding: 4px 9px;
  font: 700 20px/1.3 var(--face-num, inherit);
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: #cbbfa8;
  background: rgba(12, 9, 7, 0.72);
  border: 1px solid rgba(244, 234, 214, 0.18);
  border-radius: 999px;
  transition: color 120ms ease, border-color 120ms ease, background-color 120ms ease;
}
.mapcard-votes.is-lead {
  color: #0f0c08;
  background: #d8b46a;
  border-color: #d8b46a;
}

/* Take the room's answer. Beside `Roll new seeds`, and the primary of the two. */
.btn-go {
  background: var(--blood);
  border-color: var(--blood-line);
  color: var(--bone);
  font-weight: 700;
  box-shadow: 0 3px 0 var(--blood-deep);
}
.btn-go:hover { background: #e83544; }
.btn-go:active { transform: translateY(3px); box-shadow: 0 0 0 var(--blood-deep); }

.mapcard-body { padding: 14px 15px 16px; display: flex; flex-direction: column; gap: 9px; flex: 1; }
.mapcard-name { font-size: var(--t-lg); font-weight: 650; line-height: 1.2; }

/* The duration is the headline stat — the user asked for "stats about time"
   first and everything else second — so it is the only thing here at display
   size and the only thing in gold. */
.mapcard-time {
  font-size: 30px;
  font-weight: 700;
  line-height: 1;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.mapcard-time small { font-size: var(--t-sm); font-weight: 500; color: var(--ink-faint); margin-left: 5px; }

.mapcard-stats { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: var(--t-xs); color: var(--ink-faint); }
.mapcard-stats b { color: var(--ink-dim); font-weight: 600; font-variant-numeric: tabular-nums; }
.mapcard-note { font-size: var(--t-sm); color: var(--ink-dim); line-height: 1.5; margin: 0; }
.mapcard-seed { font: var(--t-xs)/1 ui-monospace, monospace; color: var(--ink-faint); letter-spacing: .04em; }
.mapcard-go { margin-top: auto; padding-top: 6px; font-size: var(--t-sm); color: var(--gold); font-weight: 600; }

.maps-foot { margin-top: 20px; font-size: var(--t-xs); color: var(--ink-faint); }
.ink-a { color: var(--good); }
.ink-b { color: var(--bad); }

/* ---- stream setup -------------------------------------------------------- */

.setup-inner {
  max-width: 1180px;
  /* Room under the last card for the sticky action bar to sit over. Without it
     the bottom of whichever group is last scrolls under START THE RACE and
     cannot be reached -- on the first pass that was the giveaway's explanation
     of how the prize rises, which is the one note on the page somebody actually
     has to read. */
  padding-bottom: 96px;
}
.setup-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px;
  align-items: start;
}
.setup-group {
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 14px 15px 15px;
}
.setup-group h3 { margin: 0 0 10px; font-size: var(--t-sm); letter-spacing: .07em; text-transform: uppercase; color: var(--ink-faint); }
.setup-rule { border: 0; border-top: 1px solid var(--line); margin: 13px 0; }

/* The map you picked, restated across the full width at the top. */
.setup-map { grid-column: 1 / -1; }
.setup-mapbox { display: flex; gap: 16px; align-items: center; }
.setup-thumb {
  width: 128px; height: 128px;
  flex: 0 0 auto;
  display: grid; place-items: center;
  background: #1b1511;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.setup-thumb canvas { image-rendering: pixelated; max-width: 100%; max-height: 100%; }
.setup-mapfacts { display: flex; flex-direction: column; gap: 5px; }
.setup-mapfacts b { font-size: var(--t-xl); font-weight: 650; }
.setup-mapfacts span { font-size: var(--t-sm); color: var(--ink-dim); }
.setup-seed { font: var(--t-xs)/1.4 ui-monospace, monospace; color: var(--ink-faint); }

/* GO LIVE, which is off until it is on. Red would be wrong — it is not a
   warning, it is a broadcast light — so it is the gold that everything else
   important on this screen is, and it goes green once armed. */
.btn-live {
  width: 100%;
  margin: 4px 0 8px;
  font-weight: 700;
  letter-spacing: .08em;
  border-color: var(--outline);
  color: var(--gold);
}
.btn-live.is-on { background: var(--good); border-color: var(--outline); color: #14200f;
                  box-shadow: 0 3px 0 #3f6b42; }
.btn-live.is-on:active { transform: translateY(3px); box-shadow: 0 0 0 #3f6b42; }

.setup-actions {
  position: sticky;
  bottom: 0;
  margin-top: 22px;
  padding: 14px 0 4px;
  display: flex;
  align-items: center;
  gap: 14px;
  justify-content: flex-end;
  background: linear-gradient(to top, var(--bg) 62%, rgba(25, 20, 16, 0));
}
.setup-ready { font-size: var(--t-sm); color: var(--ink-faint); }
.btn-big { padding: 12px 28px; font-size: var(--t-md); font-weight: 700; letter-spacing: .06em; }

/* ---- the countdown ------------------------------------------------------- */

/* Over the board, never instead of it. See the note in `play.html`. */
.racecount {
  position: absolute;
  inset: 0;
  z-index: 25;
  /* A SCRIM, BECAUSE THE FIELD IS STANDING UNDER THE NUMBER.
     `lineUp` puts every hand on point A, and point A is in the middle of the
     shot, and every hand carries a nameplate. Measured on the first run: the
     "3" sat inside a block of thirty overlapping labels and read as part of
     them. A drop shadow was not enough — it darkens the gap between glyphs and
     the labels are IN that gap. This darkens the middle of the board instead,
     which is also the right instinct for a start line: the ten seconds are
     about the number, and the board comes back the instant it is over. */
  background: radial-gradient(46% 42% at 50% 48%, rgba(12, 9, 7, .72) 0%, rgba(12, 9, 7, .45) 55%, rgba(12, 9, 7, 0) 100%);
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 2px;
  pointer-events: none;
  text-shadow: 0 3px 18px rgba(0, 0, 0, .85), 0 1px 3px rgba(0, 0, 0, .9);
}
.racecount[hidden] { display: none; }
.racecount-map { font-size: var(--t-lg); font-weight: 650; color: var(--ink); letter-spacing: .04em; }
.racecount-n {
  font-size: 140px;
  line-height: 1;
  font-weight: 800;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
  /* Re-keyed on every change by `ui.js` so each number gets its own beat. A CSS
     animation on a number that only changes once a second is free; a JS tween
     is a timer that has to be cancelled when the race starts. */
  animation: rc-beat .5s cubic-bezier(.2, .8, .3, 1);
}
.racecount.is-go .racecount-n { font-size: 108px; color: var(--good); letter-spacing: .05em; }
.racecount-sub { font-size: var(--t-sm); color: var(--ink-dim); letter-spacing: .16em; text-transform: uppercase; }
@keyframes rc-beat {
  from { transform: scale(1.5); opacity: .15; }
  to   { transform: scale(1);   opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .racecount-n { animation: none; } }

/* THE LOBBY'S OWN TWO LINES. See the note in `play.html`.

   The count of who is on the line sits ABOVE the controls and below the number,
   because it is the thing a host is actually watching while they talk -- the
   buttons are what they reach for once it stops climbing.

   A lobby clock reads `3:04` where a countdown read `10`, so the number needs
   room for four characters at 140px. `tabular-nums` is already on it, which is
   what stops the colon jittering as the digits change. */
/* `!join` IS A WORD WHERE THAT SLOT HOLDS A NUMERAL.

   140px is sized for one or two digits and `!join` is five characters, so at
   that size it runs into the edges of a 16:9 shot before it runs into anything
   else. It also should not pulse: the beat exists to mark a second passing, and
   nothing is passing while the lobby is open. */
.racecount.is-open .racecount-n {
  font-size: 84px;
  letter-spacing: .02em;
  animation: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.racecount.is-open .racecount-joined { font-size: var(--t-md); }

.racecount-joined {
  font-size: var(--t-sm);
  color: var(--gold);
  letter-spacing: .1em;
  text-transform: uppercase;
  margin-top: 2px;
}
.racecount-joined[hidden] { display: none; }
.racecount-ctl {
  display: flex;
  gap: 10px;
  margin-top: 22px;
  /* The scrim above is `pointer-events: none` territory for the board beneath;
     these have to be clickable through it. */
  pointer-events: auto;
}
.racecount-ctl[hidden] { display: none; }

/* ---- the scoreboard ------------------------------------------------------ */

.board-inner { max-width: 1180px; }
.board-h-note { text-transform: none; letter-spacing: 0; color: var(--ink-faint); font-weight: 400; }
.board-h { margin: 26px 0 10px; font-size: var(--t-sm); letter-spacing: .07em; text-transform: uppercase; color: var(--ink-faint); }

.board-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.board-tile {
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 13px 14px;
}
.board-tile b { display: block; font-size: 26px; line-height: 1.1; font-variant-numeric: tabular-nums; }
.board-tile span { font-size: var(--t-xs); color: var(--ink-faint); text-transform: uppercase; letter-spacing: .07em; }
.board-tile.is-gold b { color: var(--gold); }
.board-tile.is-bad b { color: var(--bad); }
.board-tile.is-good b { color: var(--good); }

/* The giveaway strip. Full width and gold-washed because it is the one line on
   this page that somebody in chat is waiting on.
   It was gold-EDGED as well, until the frame went round it and painted over the
   border colour. The wash, the gold GIVEAWAY and the gold handle turned out to
   carry it on their own -- checked on a board with a winner drawn in it -- so
   the dead `border: 1px solid var(--gold)` came off rather than being replaced
   with a second way of saying the same thing. */
.board-give {
  margin-top: 16px;
  padding: 14px 16px;
  border-radius: var(--r);
  background: rgba(232, 180, 78, .07);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 12px;
}
.board-give[hidden] { display: none; }
.board-give .bg-k { font-size: var(--t-xs); letter-spacing: .09em; text-transform: uppercase; color: var(--gold); }
.board-give .bg-who { font-size: var(--t-lg); font-weight: 700; }
.board-give .bg-what { font-size: var(--t-sm); color: var(--ink-dim); }

.board-podium { margin-top: 18px; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.board-place {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg-panel);
}
.board-place .bp-n { font-size: 28px; font-weight: 800; color: var(--ink-faint); font-variant-numeric: tabular-nums; min-width: 1.6em; }
/* First place used to say so here, with `border-color: var(--gold)`. The frame
   paints over that, so the declaration is gone rather than left sitting where
   the next person would read it as the live rule. What marks the winner now is
   the tint at the foot of this file. */
.board-place.is-1 .bp-n { color: var(--gold); }
.board-place .bp-who { display: flex; flex-direction: column; min-width: 0; }
.board-place .bp-who b { font-size: var(--t-md); }
.board-place .bp-who span { font-size: var(--t-xs); color: var(--ink-faint); }

/* The long table. `max-height` and its own scroll: a hundred and forty rows is
   a normal field, and a scoreboard that pushes its own buttons off the bottom
   of the page is one you cannot leave. */
.board-table {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg-panel);
  max-height: 46vh;
  overflow-y: auto;
}
.board-row {
  display: grid;
  /* Six, not seven: the digger column came off and its width went to the handle,
     which is the column anybody is actually reading. See `boardRow`. */
  grid-template-columns: 3.2em minmax(9em, 3fr) 5em 5em 6em 6em;
  gap: 10px;
  align-items: center;
  padding: 7px 14px;
  font-size: var(--t-sm);
  border-bottom: 1px solid rgba(61, 50, 39, .5);
}
.board-row:last-child { border-bottom: 0; }
.board-row.is-head {
  position: sticky; top: 0; z-index: 1;
  background: var(--bg-raise);
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ink-faint);
}
.board-row .br-n { font-variant-numeric: tabular-nums; color: var(--ink-faint); }
.board-row .br-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-row .br-nick { color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-row .br-num { font-variant-numeric: tabular-nums; text-align: right; color: var(--ink-dim); }
.board-row.is-done { background: rgba(142, 203, 146, .06); }
.board-row.is-done .br-n { color: var(--good); }
.board-row.is-out { color: var(--ink-faint); }
.board-row.is-out .br-name { text-decoration: line-through; }
.br-tag { font-size: var(--t-xs); letter-spacing: .05em; text-transform: uppercase; }
.br-tag.is-done { color: var(--good); }
.br-tag.is-out { color: var(--bad); }

/* A field of one is a normal state on a first boot, and an empty table with
   headings over it looks broken. */
.board-empty { padding: 22px 16px; font-size: var(--t-sm); color: var(--ink-faint); }


/* THE STREAM HEADER'S OWN CONTROLS.
   Sound, the operator's console and help, carried over when the first build's
   header was removed. Pushed to the right of the live light so the facts on the
   left read as a group and the controls as another. */
.sb-actions {
  display: flex; align-items: center; gap: 6px;
  margin-left: 8px;
}
.sb-actions .btn { padding: 4px 8px; }
.sb-actions .btn-icon svg { width: 15px; height: 15px; }

/* ---------------------------------------------------------------------------
   THE FRAME IS ALSO THE PADDING.

   Anything framed gives back most of its own gutter, so a card measures about
   what it measured before and the brass is what does the spacing. Fourteen is
   the number -- ten of frame and four of air -- and every framed card is on it.

   AT THE END OF THE FILE ON PURPOSE, and this is the whole reason the block
   moved. It first sat up beside the frame's own rule, which is where it belongs
   by subject, and two of the four silently lost: `.setup-group` and the two menu
   panels are defined LATER, at equal specificity, so the later padding won and
   those cards sat at a 25px and 20px inset against everybody else's 14. It
   measured wrong on screen before it was found in the cascade. Corrections to
   rules that come later have to come later still.
   --------------------------------------------------------------------------- */
.setup-group { padding: 4px 5px 5px; }
.mapmenu-panel, .castmenu-panel { padding: 4px; }
.signin-form { padding: 4px; }

/* ---------------------------------------------------------------------------
   DIFFICULTY GOES LAST, AND GOES WIDE.

   > "Difficulty tab can be at the bottom and wide. It makes the other panels
   >  too low."

   It is a dozen dials with a paragraph of reasoning under each, in a grid whose
   rows are as tall as their tallest cell -- so a column of prose was setting the
   height of the row and leaving Going live and The day sitting in a metre of
   empty panel. `order` rather than moving it in the markup, because the reading
   order of the document is still the right one: the course, then how to go live,
   then the knobs. This is a layout opinion and it belongs in the layout.

   The dials themselves go into columns, or a full-width panel is one narrow
   ribbon of controls with a thousand pixels of nothing beside it. `columns`
   rather than a grid because each dial is a label, a slider and a note of
   unpredictable length: column layout keeps a dial and its explanation together
   and breaks between them, which is exactly the rule wanted here.
   --------------------------------------------------------------------------- */
.setup-dials {
  grid-column: 1 / -1;
  order: 99;
}
#suDials {
  columns: 3 300px;
  column-gap: 26px;
}
/* Nothing may be split down the middle of itself. A dial whose slider is at the
   foot of one column and whose note is at the head of the next reads as two
   half-controls. */
#suDials > * {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
}
/* The group heads inside the dial list start a fresh column rather than landing
   halfway down one, so a band of dials is never split across the fold. */
#suDials > .onair-note b { display: inline-block; }

/* ---------------------------------------------------------------------------
   THE SCOREBOARD, FRAMED.

   The field table, the podium places and the giveaway note take the full rail.
   The six stat tiles take a thinner one, for the reason the header does: they
   are about sixty pixels tall around a 26px number, and ten of frame top and
   bottom would leave the number wearing the panel rather than sitting in it.
   Same tile, same brass, less of it -- which is the rule this file has settled
   on for anything short.

   Padding comes off each of them by the width of the frame it gained, so a card
   measures what it measured before and the brass does the spacing. The table's
   inset lives on its ROWS rather than on the table, because the rows are what
   have a left edge; taking it off the table would have moved the header cells
   and the body cells apart from each other.
   --------------------------------------------------------------------------- */
.board-table, .board-place, .board-give {
  border-style: solid !important;
  border-width: var(--frame-w) !important;
  border-image: var(--frame) 10 repeat !important;
  border-radius: 0 !important;
}
.board-tile {
  border-style: solid !important;
  border-width: 4px !important;
  border-image: var(--frame) 10 repeat !important;
  border-image-width: 4px !important;
  border-radius: 0 !important;
  padding: 9px 10px;
}
.board-place { padding: 2px 4px; }
.board-give  { padding: 4px 6px; }
.board-row   { padding-left: var(--frame-pad); padding-right: var(--frame-pad); }

/* ---------------------------------------------------------------------------
   DIFFICULTY IS HIDDEN, NOT REMOVED.

   > "Let's hide the difficulty panel for now. I like the settings the way they
   >  are right now and it's too much information for the user."

   A dozen dials with a paragraph of reasoning under each is the right amount of
   detail for whoever is tuning the game and far too much for somebody about to
   start a race. Every dial keeps its current value and every one of them still
   applies -- `Tuning` is read live by the code that uses it, not by this panel
   -- and they are all still reachable in the operator's console, which is why
   that button was moved out of this group before this rule was written.

   `display: none` rather than deleting the markup, because the panel is wanted
   again the moment somebody wants to tune from the start line: this is one line
   to take out. It also keeps `renderSetupDials` honest -- the dials are still
   built and still hold the live values, so nothing has to be re-taught when it
   comes back.
   --------------------------------------------------------------------------- */
.setup-dials { display: none; }

/* FIRST PLACE, NOW THAT THE BORDER CANNOT SAY IT.

   `.board-place.is-1` marked the winner by turning its border gold. A
   `border-image` paints over `border-color` entirely, so that rule went on
   existing and stopped being visible the moment the podium was framed -- every
   place identical, which is the one thing a podium must not be.

   The numeral was already gold and still is, so the distinction was never lost
   completely; this puts it back at a glance rather than on inspection. A tint
   rather than a second frame, because the frame is the thing every card shares
   and the winner should differ INSIDE it, not by wearing a different one. */
.board-place.is-1 {
  background: rgba(232, 180, 78, .10);
}

/* ---------------------------------------------------------------------------
   GOING LIVE IS THE SCREEN NOW.

   > "Really the only thing that is really needed on this screen is the volume
   >  and then the go live tab should be larger."

   So it takes the full width and the door goes inside it. Everything else on
   this screen is a setting somebody changes once; this is the thing a host is
   actually waiting on -- the room filling up -- and it was a third of a row.

   `order` keeps the course card first, because what you are about to run is
   still the first thing worth reading.
   --------------------------------------------------------------------------- */
/* ORDER IS SET FOR ALL OF THEM OR FOR NONE OF THEM.

   Giving Going live `order: 1` while everything else sat at the default 0 put
   it LAST, which is the opposite of what was wanted and is the trap in `order`:
   it is not a nudge, it is a sort key, and an item that opts in is sorted
   against every item that did not. So the group carries the default and the two
   that lead say where they are: the course you are about to run, then the door
   people come in through, then the settings. */
.setup-group { order: 2; }
.setup-map   { order: 0; }
.setup-live {
  grid-column: 1 / -1;
  order: 1;
}
/* The channel row and the door side by side while there is room for it, and
   stacked when there is not -- the door is the half that must survive a narrow
   window, so it is the one that grows. */
.setup-live { display: grid; grid-template-columns: minmax(260px, 1fr) minmax(300px, 2fr);
              gap: 18px 22px; align-items: start; }
.setup-live h3 { grid-column: 1 / -1; }
.setup-live .joinbox { grid-column: 2; grid-row: 2 / span 3; }
@media (max-width: 900px) {
  .setup-live { grid-template-columns: 1fr; }
  .setup-live .joinbox { grid-column: 1; grid-row: auto; }
}

.joinbox {
  display: flex; flex-direction: column; min-height: 190px;
  background: rgba(0, 0, 0, .18);
  border: 1px solid var(--frame-dark, #1f1913);
}
.joinbox-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--frame-dark, #1f1913);
}
/* THE SUBJECT OF THIS SCREEN, so bone rather than gold. Nothing is being
   bought or won here; the count is simply the thing you are looking at. */
.joinbox-head b { font-size: var(--t-md); color: var(--bone); }
.joinbox-cue { font-size: var(--t-sm); color: var(--ink-faint); }
.joinbox-cue b { color: var(--ink); font-weight: 700; }

/* Two columns of handles, newest first, so a full room reads as a crowd rather
   than as a scrollbar. It fills downwards and the box does not grow: the count
   above is the true number and this is the last few who arrived. */
.joinlist {
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  margin: 0; padding: 8px 10px; list-style: none;
  columns: 2; column-gap: 16px;
  font-size: var(--t-sm); color: var(--ink-dim);
}
.joinlist li {
  break-inside: avoid;
  padding: 1px 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.joinlist li:first-child { color: var(--bone); }


/* ===========================================================================
   THE FRAME, SECOND SWEEP -- what the first pass did not reach
   ===========================================================================

   Two failures were already known when this was written and both are named in
   the notes above: a padding correction that LOST THE CASCADE to a later rule
   at equal specificity, and a rule that said something with `border-color` on
   a box that now has a `border-image` painted over it. Both had more of
   themselves left. This block is the rest of them, in one place, at the end,
   because the end is the only position that cannot be lost to a later rule.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   FOUR MORE PADDINGS THAT LOST THE CASCADE.

   `.leaderlist li`, `.leadnews` and `.chatcol-feed` were each corrected up at
   the frame block -- `padding-left: var(--frame-pad)` and its right -- and each
   correction was then overwritten, a few dozen lines further down, by the rule
   that owns the element and sets `padding` as a SHORTHAND. Same specificity,
   later in the file, so the shorthand wins and puts the old number back.
   Nothing errors, nothing warns; the panel simply measures wrong.

   Measured in the running page before this rule existed:

     rail head          14   (`.storerail .rail-head`, two classes, survived)
     running-order row  22   (`.leaderlist li` :: `padding: 3px 12px`)
     death strip        22   (`.leadnews`     :: `padding: 7px 12px 8px`)
     chat line          20   (`.chatcol-feed` :: `padding: 8px 10px 10px`)
     store card         22   (`.storecard`    :: never corrected at all)

   So every rail on screen had its heading at fourteen and its contents eight
   or six further in -- which is the exact shape of the thing that was already
   caught once on the map cards, and just as hard to see and just as wrong.

   THE CORRECTION IS THE FULL SHORTHAND, not `padding-left`/`padding-right`.
   A longhand here would be one more thing for the next shorthand to beat. The
   vertical numbers are copied from the rules above unchanged: this moves the
   sides in to meet the frame and nothing else.

   `.storecard` is included although the store rail is hidden today (see
   `renderStoreRail` -- `store.hidden = true`, the catalogue moved to
   `store.html`). The rail is left in the markup deliberately, as one line to
   put back, and it should not come back eight pixels out.
   --------------------------------------------------------------------------- */
.leaderlist li { padding: 3px var(--frame-pad); }
.leadnews      { padding: 7px var(--frame-pad) 8px; }
.chatcol-feed  { padding: 8px var(--frame-pad) 10px; }
.storecard     { padding: 8px var(--frame-pad); }

/* THE RUNNING ORDER LOST A ROW TO ITS OWN FRAME.

   `max-height: 42%` is measured on the border box, so the twenty pixels of
   brass came out of the ten names rather than out of the air around them. At
   720 the list needs 268 and was given 242: the tenth name sat below the fold
   of a panel nobody watching a stream can scroll.

   Give back exactly what the frame took and no more. 42% is a considered
   number about how much of the picture a leaderboard may cover and it is not
   this rule's to retune -- the panel is the same size it was the day before
   the frame, which is all that is being claimed here. A short window still
   scrolls, as it did then. */
.leaderrail { max-height: calc(42% + (var(--frame-w) * 2)); }

/* ---------------------------------------------------------------------------
   TWO MORE GOLD BORDERS THAT STOPPED BEING GOLD.

   `.board-place.is-1` was found and fixed. It was not the only one. Anything
   that marked itself out by turning its own border gold is invisible under a
   `border-image`, and the tell is always the same: a rule that still exists,
   still matches, and no longer does anything.

   MAP CARDS, HOVER AND FOCUS. `.mapcard:hover` set `border-color: var(--gold)`
   and killed the outline, because the gold edge WAS the focus ring. Framed,
   the card now answers a pointer with `translateY(-2px)` and a shadow that is
   invisible against this background -- so hovering three cards on the course
   menu looks like hovering nothing, and tabbing through them looks like
   nothing at all, which is the worse half.

   A tint inside the frame, the same answer the podium settled on: the card
   warms where the body is, the picture keeps its own ground, and the brass is
   the brass on every card. `background-image` rather than `background`, so it
   layers over `--bg-panel` instead of replacing it.

   Focus gets a real ring back on top of the tint. `outline` and not a border:
   it is drawn OUTSIDE the box, so it cannot fight the frame, and `2px` of
   offset keeps it off the brass rather than on it. Keyboard only -- a mouse
   already has the tint and the lift.

   THE GIVEAWAY STRIP. `border: 1px solid var(--gold)` on `.board-give`, dead
   the same way. Left off rather than replaced: unlike the podium, this strip
   already carries `rgba(232, 180, 78, .07)` behind it and a gold GIVEAWAY and
   a gold handle in front of it, and it is the only thing on the page shaped
   like that. Checked on the board with a winner drawn in it -- it still reads
   first. The comment above it that promises a gold edge is corrected there.
   --------------------------------------------------------------------------- */
.mapcard:hover, .mapcard:focus-visible {
  background-image: linear-gradient(rgba(232, 180, 78, .07), rgba(232, 180, 78, .07));
}
.mapcard:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   THE BUTTON STRIP POKING THROUGH THE FULL-SCREEN PAGES.

   Two lists in this file take the game's own furniture away when a page is put
   in front of it -- `body.console-open` by display, `body.is-front` by
   visibility -- and both were written before the cast menu and the horde
   button existed. Neither was ever added to either list.

   On the operator's console this is VISIBLE: two 56px buttons at z-index 58
   float over a z-index 55 page, halfway down the ledger. Screenshot with the
   console open and there they are, on top of the accounts table.

   On the front screens it is not visible -- `.screen.is-front` is z-index 60
   and opaque -- and it is the same failure the `is-front` list was written to
   fix, spelled out in its own comment: they are still BUTTONS, still visible
   to the tab order, so a person keyboarding the course menu walks off the end
   of it into a cast panel they cannot see. `visibility: hidden` for the same
   reason it is used there: it leaves the tab order without relaying out the
   stage canvas.

   The running order goes with them on the console. It is currently covered by
   DOM order alone -- both are z-index 55 -- which is not a thing to rely on.
   --------------------------------------------------------------------------- */
body.console-open #castMenu,
body.console-open #hordeBtn,
body.console-open #leaderRail { display: none; }

body.is-front #packMenu,
body.is-front #mapMenu,
body.is-front #castMenu,
body.is-front #hordeBtn { visibility: hidden; pointer-events: none; }

/* ---------------------------------------------------------------------------
   THE SCROLLBARS INSIDE THE BRASS.

   The running order, the scoreboard's table and the cast panel all scroll, and
   every one of them drew the browser's default light scrollbar -- a white
   track and a grey thumb -- hard up against the inside edge of a gold frame on
   a dark brown panel. It read as a piece of somebody else's application
   sitting in the corner of the shot, and on the cast panel it was the
   brightest thing on the screen.

   `color-scheme` rather than `::-webkit-scrollbar` rules. One line tells the
   engine what kind of page this is, and the scrollbars, the `<select>` on the
   setup screen, its dropdown, and every checkbox in the cast panel and the
   sound row come back dark together. Hand-painting scrollbar parts would have
   fixed the scrollbars and left the white checkboxes.

   Nothing here depends on a default background: `html` and `body` both paint
   their own, so the only thing this changes is the furniture.

   `accent-color` finishes the job the console started -- `.adm-row .adm-vol`
   already asked for gold and was the only control that did. Inherited from the
   root, every checkbox and slider in the game matches the frame instead of
   coming up in the operating system's blue.
   --------------------------------------------------------------------------- */
:root {
  color-scheme: dark;
  accent-color: var(--gold, #e8b44e);
}

/* ---------------------------------------------------------------------------
   THE HOVER CARD IS 216 PIXELS WIDE AND ALWAYS WAS.

   Nothing shows this card today -- the hover mechanic came out of `main.js`
   and the note there says the element and its styles stay for the next thing
   that wants to point at a person. Corrected anyway, and measured by unhiding
   it, for the same reason as the store card above: a box that is kept on
   purpose should not be kept eight pixels out.

   `.pcard` is sized in absolutes on purpose -- the note above it explains that
   a card which resizes to the name under the pointer jitters between two hands
   and reads as a glitch. It has no padding of its own; its three children do,
   and they were written against a 2px border. Ten pixels of frame later they
   measured 20, 22 and 22 from the brass, and the name they exist to show had
   sixteen fewer pixels to fit in before it ellipsised.

   So the children take the house number instead: fourteen on every edge that
   touches the frame, which is what every other framed panel measures and which
   costs this one four pixels of width against the card it was before. Only the
   OUTER edges move -- the eight between the head and the rows is the gap
   between a picture and a stat block and has nothing to do with a border.

   Not "the old padding minus ten", which is the rule the panels use and is
   wrong here: it would have left the foot's last line sitting flat on the
   inside edge of the brass, because the foot's old eight was measured against
   a 2px line and there is nothing left of it after ten comes off. Frame plus
   four, every time, is the rule that does not have that failure in it.
   --------------------------------------------------------------------------- */
.pcard-head { padding: var(--frame-pad) var(--frame-pad) 8px; }
.pcard-rows { padding: 0 var(--frame-pad) 8px; }
.pcard-foot { padding: 6px var(--frame-pad) var(--frame-pad); }


/* ---------------------------------------------------------------------------
   THE TEXT FIELDS, SQUARED OFF WITH THE REST.

   Five screens styled their own inputs and every one was a rounded one-pixel
   box, which is the shape the buttons just stopped being. Only the frame is
   changed; width and alignment stay where each screen sets them.

   AT THE END OF THE FILE, AND THAT IS THE WHOLE FIX. This was first written up
   beside the buttons, four hundred lines in, where all five per-screen rules
   came later and won -- so it did nothing and the screenshot looked the same
   either way. An override has to sit after what it overrides.
   --------------------------------------------------------------------------- */
.twitch-row input, .signin-field input, .door-invite input,
.party-add input, .party-add select {
  border-radius: 0;
  border: 2px solid var(--line);
  background: #14100c;
}
.twitch-row input:focus-visible, .signin-field input:focus-visible,
.door-invite input:focus-visible, .party-add input:focus-visible,
.party-add select:focus-visible {
  outline: none;
  border-color: var(--gold);
}


/* ---------------------------------------------------------------------------
   THE HEADER'S SOUND PANEL

   Anchored to the header rather than to the button, because the button is the
   last of three in a row and a panel hung off it would run past the right edge
   of the screen on a narrow window. Right-aligned to the bar instead, which is
   where there is always room.
   --------------------------------------------------------------------------- */
/* FIXED TO THE VIEWPORT, NOT TO THE BUTTON THAT OPENS IT.

   Anchored inside `.sb-actions` the panel hung off the last control in the
   header -- and on a narrow window the header itself overflows, taking the
   button and the panel off the right-hand edge with it. Measured at 800px: the
   controls were past the edge and the panel was half visible.

   Pinned to the window instead, under the bar's own height, so it is on screen
   at every width whatever the header is doing. `--sb-h` is the bar's height
   and already drives four other rules, so this moves with it. */
.sndpop {
  position: fixed;
  top: calc(var(--sb-h, 56px) + 8px);
  right: 10px;
  z-index: 70;
  width: 232px;
  padding: 13px 14px 11px;
  background: var(--bg-panel);
  border: 2px solid var(--blood-line);
  box-shadow: 0 4px 0 rgba(0, 0, 0, .55);
}
.sndpop[hidden] { display: none; }
.sndpop-row { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; }
.sndpop-row span { font-size: var(--t-xs); color: var(--ink-dim); min-width: 52px; }
.sndpop-row input[type=range] { flex: 1; min-width: 0; }
.sndpop-check { display: flex; align-items: center; gap: 7px; font-size: var(--t-sm); color: var(--ink-dim); }
.sndpop-note { margin: 9px 0 0; font-size: var(--t-xs); color: var(--ink-faint); }


/* The two room switches on the operator's console. See the note beside them in
   `play.html`: they live here because this console opens from the header at
   any moment, and the screen they came from could only be seen before a race. */
.admin-switches {
  background: var(--bg-panel);
  border: 2px solid var(--blood-line);
  padding: 14px 16px 15px;
  margin-bottom: 16px;
}
.admin-switches h3 {
  margin: 0 0 10px;
  font: 700 var(--t-xs)/1 ui-monospace, monospace;
  letter-spacing: .12em; text-transform: uppercase; color: var(--gold);
}
.admin-switches .onair-check { display: flex; align-items: center; gap: 8px; margin-top: 7px; }


/* THE TWO OPERATOR TOOLS SIT UNDER THE MAP, so the rail reads as one short
   column rather than as icons scattered down the board at the heights their
   old neighbours happened to leave them at. Both are hidden until the operator
   key is given -- see `setOperator` in `ui.js`. */
.castmenu { top: calc(var(--sb-h, 56px) + 68px) !important; }
.hordebtn { top: calc(var(--sb-h, 56px) + 126px) !important; }
