/* Ages Idle -- a deliberately text-first look: parchment on slate, one
 * accent for gain and one for loss, no art. Everything is sized in rem so
 * the whole thing scales with the browser font size. */

:root {
  --bg: #14120f;
  --bg-panel: #1c1a16;
  --bg-row: #211e19;
  --line: #332e26;
  --ink: #ded5c4;
  --ink-dim: #9a9081;
  --ink-faint: #6d6558;
  --good: #8fbf6a;
  --bad: #cf7a63;
  --accent: #d8a657;
  --radius: 4px;
  /* The light step of the accent, for readings and full stores. Most
   * themes have one accent and this simply follows it; Nocturne is the
   * theme with a ramp, and sets its own. */
  --accent-hi: var(--accent);
  /* The face, as a variable rather than a literal on body, so a theme can
   * change it. Nocturne does; it is the only one. */
  --font: "Iowan Old Style", "Palatino Linotype", Georgia, serif;

  /* Interactive surfaces. Separate from the panel colours because a button
   * has to read as pressable against a panel, which is a different job from
   * a panel reading as distinct from the page. */
  --btn: #2a2620;
  --btn-hover: #353027;
  --btn-line: #4a4238;
  --raised: #332d22;
  --raised-hover: #3f3728;
  --sunken: #221f19;
  --scroll: #3a342b;
  --alt-line: #4a5a6a;
  --alt-line-hover: #6c8098;

  /* Log strip. One row is 0.85rem of text at line-height 1.5 plus 0.12rem
   * of padding top and bottom. Height and the page's bottom reserve are
   * both derived from --ticker-rows, so this one number is the whole size.
   *
   * THREE, EVERYWHERE. It used to key off posture -- seven on a desktop,
   * five on a phone, three on a phone in landscape -- because the question
   * was how much screen the strip could afford to eat. That was the wrong
   * question. The strip is a glance at what just happened; everything it
   * has ever said is kept in full under Records, so there is nothing to be
   * gained by showing more of it here and a good deal of screen to be lost.
   * A fixed three needs no breakpoints, and the six that used to set this
   * are gone rather than left at values nothing reads. */
  --ticker-rows: 3;
  /* 0.85rem of text at line-height 1.5 is 1.275rem, plus 0.24rem of
   * padding, which is 1.515rem in principle -- but a row measures 25px
   * against a 16px root, so the true figure is nearer 1.5625rem once the
   * browser has rounded the line box. At seven rows that shortfall was
   * five pixels of scroll nobody noticed; at three it is the difference
   * between a strip that fits and one with a pointless scrollbar. Rounded
   * up, so three rows always fit without one. */
  --ticker-row: 1.6rem;
  --ticker-pad: 0.55rem;
  --ticker-h: calc(var(--ticker-rows) * var(--ticker-row) + var(--ticker-pad) * 2);
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  /* One multiplier for the whole interface. Everything is sized in rem so
   * that this single number moves all of it -- which is the entire reason
   * the game was written in rem in the first place. */
  --text-scale: 1;
}

/* ============================================================== themes ==
 * Only the colour tokens change -- no layout, no sizes, no rules that exist
 * in one theme and not another -- because a theme that changed the shape of
 * the game would need testing as a separate interface, and there are five
 * of them. Set on <html> by src/theme.js before the first paint.
 *
 * ONE DELIBERATE EXCEPTION, Nocturne. The redesign handoff that introduced
 * it defines its corners, its face and its heading type as part of the
 * theme, so it also sets --radius and --font here and has two scoped type
 * rules at the end of this file. That makes it the one theme that has to be
 * checked as an interface of its own, at every width. (Wacky's single rule
 * below switches a meaning off; it changes no shape.)
 */

/* The same design with the values inverted, rather than a different design.
 * Parchment moves from the ink to the page and the ink darkens to match. */
:root[data-theme="light"] {
  --bg: #f4efe3;
  --bg-panel: #ebe3d2;
  --bg-row: #e2d9c4;
  --line: #cbbfa6;
  --ink: #241f18;
  --ink-dim: #554c3d;
  --ink-faint: #7d7260;
  --good: #38661f;
  --bad: #98351d;
  /* #8a5c12 until 2026-09-30: 4.13:1 on a building row, where it is the
   * "+ room" fact. 8% darker clears 4.6 on the row and 5.1 on a panel. */
  --accent: #7f5511;
  --btn: #ded4bf;
  --btn-hover: #d2c6ac;
  --btn-line: #b6a888;
  --raised: #e6dcc6;
  --raised-hover: #dcd0b6;
  --sunken: #e8e0cd;
  --scroll: #bdb096;
  --alt-line: #7d93aa;
  --alt-line-hover: #5b7a96;
}

/* Okabe-Ito. The default gain/loss pair is green and red, which is exactly
 * the pair a red-green colour blind player cannot separate -- so this
 * substitutes blue for gain and vermillion for loss, which differ in hue
 * AND in lightness and therefore survive any of the common forms. Yellow
 * takes the accent because gold sits too close to vermillion. */
:root[data-theme="cb"] {
  --bg: #12141a;
  --bg-panel: #191c24;
  --bg-row: #1f232c;
  --line: #333a47;
  --ink: #e8ecf2;
  --ink-dim: #a9b2c0;
  --ink-faint: #798394;
  --good: #56b4e9;
  /* Okabe-Ito vermillion is #d55e00, which is 4.07:1 on a building row --
   * every upkeep line in the theme meant for readability fell short. The
   * same hue 7% brighter: 4.61 on a row, 5.00 on a panel. */
  --bad: #e46500;
  --accent: #f0e442;
  --btn: #232833;
  --btn-hover: #2d3340;
  --btn-line: #475061;
  --raised: #2a3140;
  --raised-hover: #343d4e;
  --sunken: #1b1f28;
  --scroll: #3f4757;
  --alt-line: #56b4e9;
  --alt-line-hover: #7fc8f0;
}

/* Nocturne -- the redesign's palette (handoff direction 3a). A fifth mode
 * rather than a replacement, so nothing existing changes appearance unless
 * it is chosen. Gain and loss are the only two values not taken straight
 * from the Nocturne sheet, which has no semantic green or red: they were
 * derived in OKLCH at the accent's own lightness, so they stay quiet beside
 * it and apart from each other. The colour-blind theme is untouched. */
:root[data-theme="nocturne"] {
  --bg: #161826;
  --bg-panel: #1b1d2e;
  --bg-row: #232532;
  --line: #292b31;
  --ink: #e9e9ed;
  --ink-dim: #9397ab;
  --ink-faint: #75798c;
  --good: #7fd4a8;
  --bad: #e08a8a;
  --accent: #9184d9;
  --accent-hi: #b5abfc;
  --btn: transparent;
  --btn-hover: rgba(145, 132, 217, 0.12);
  --btn-line: #3f424d;
  --raised: transparent;
  --raised-hover: rgba(145, 132, 217, 0.12);
  --sunken: #1f2130;
  --scroll: #3f424d;
  --alt-line: #5d5294;
  --alt-line-hover: #796cbf;
  --radius: 8px;
  /* Inter first, for anyone who has it installed or once it is bundled;
   * the system sans-serif otherwise. Never fetched: the game is an offline
   * PWA and a remote font request would break that promise. */
  --font: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Buttons are outlined here, never filled -- `--btn` is transparent -- so
   * their outline has to be the neutral step rather than the hairline, or a
   * button disappears into the ground. A token rather than a `button` rule:
   * a rule would out-rank every class that sets its own border, and the
   * tabs and the selected buy size would lose theirs. */
  --btn-border: var(--btn-line);
}

/* High contrast (2026-09-15). White on black; yellow for the accent, which
 * is what can be pressed or is selected; lines and button borders strong
 * enough to see without colour; and a heavy outline on keyboard focus. */
:root[data-theme="contrast"] {
  --bg: #000000;
  --bg-panel: #000000;
  --bg-row: #0f0f0f;
  --line: #9a9a9a;
  --ink: #ffffff;
  --ink-dim: #e8e8e8;
  --ink-faint: #cfcfcf;
  --good: #7dffa0;
  --bad: #ff9a8f;
  --accent: #ffdd00;
  --btn: #000000;
  --btn-hover: #222222;
  --btn-line: #ffffff;
  --raised: #111111;
  --raised-hover: #2a2a2a;
  --sunken: #0a0a0a;
  --scroll: #cfcfcf;
  --alt-line: #8fd8ff;
  --alt-line-hover: #c2ebff;
  --btn-border: #ffffff;
}
:root[data-theme="contrast"] :focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
:root[data-theme="contrast"] .panel { border-color: var(--line); }

/* Wacky takes its colours from inline custom properties written by
 * theme.js, so there is nothing to declare here -- only the things that
 * must STOP meaning what they meant. Cost colouring is red for
 * unaffordable and white for affordable, and with the palette rolled at
 * random red no longer carries that meaning, so it is switched off and
 * the figures simply read as text. */
:root[data-theme="wacky"] .cost-part.short,
:root[data-theme="wacky"] .cost-part {
  color: inherit;
}

/* Motion, when it has been asked to stop. */
:root[data-motion="reduced"] * {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
  scroll-behavior: auto !important;
}

* { box-sizing: border-box; }

/* iOS enlarges text in landscape unless told not to; the layout is already
 * sized in rem and scales on its own. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  /* calc() on the root size, so --text-scale moves every rem in the game
   * at once. This is what all that rem sizing was for. */
  font: calc(15px * var(--text-scale, 1))/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
}

button {
  font: inherit;
  color: var(--ink);
  background: var(--btn);
  /* The hairline, unless a theme names a border for buttons of its own. */
  border: 1px solid var(--btn-border, var(--line));
  border-radius: var(--radius);
  padding: 0.35rem 0.7rem;
  cursor: pointer;

  /* Three separate things iOS does to a button being tapped quickly, and
   * three separate fixes:
   *   manipulation      -- no double-tap-to-zoom, and no 300ms wait for one
   *   touch-callout     -- no "Copy / Look Up" sheet on a long press
   *   user-select       -- no blue word selection when a press runs long
   *   tap-highlight     -- no grey flash on every one of ten taps a second
   * Holding a gather button is a supported way to play, so none of the
   * text-handling gestures should ever fire on one. */
  touch-action: manipulation;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
button:hover:not(:disabled) { background: var(--btn-hover); border-color: var(--btn-line); }
button:disabled { opacity: 0.4; cursor: default; }

#app {
  max-width: 1100px;
  margin: 0 auto;
  /* Bottom padding clears the fixed log strip, so the last row of content
   * is reachable instead of sitting under it. */
  padding: 1.2rem calc(1.2rem + var(--safe-r)) calc(var(--ticker-h) + 1.4rem + var(--safe-b))
           calc(1.2rem + var(--safe-l));
}

/* ---------------------------------------------------------------- header */

.top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  /* Wraps rather than overflows. `.corner` is `flex: none` so it will not
   * shrink, and on a very narrow screen -- a folding phone's cover display,
   * a 240px window -- the clock and the speed buttons were pushed 14px past
   * the right edge with no way to reach them. With wrap they take a line of
   * their own instead. Costs nothing at any width where both already fit. */
  flex-wrap: wrap;
  gap: 1rem;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.7rem;
  margin-bottom: 1.1rem;
  /* Pinned, so the clock and the speed control are reachable from anywhere
   * in a long list. The tab bar pins directly beneath it using --top-h,
   * which the interface measures after each rebuild. */
  position: sticky;
  top: 0;
  z-index: 6;
  background: var(--bg);
  /* The top safe area: with viewport-fit=cover and a translucent status bar,
   * a home-screen web app or the iOS build draws under the clock and the
   * Dynamic Island. The header takes the inset in its own padding, so its
   * background fills behind the status bar and every offset measured from
   * its height (tabs, strip, drawer) moves down with it. 0 everywhere else. */
  padding-top: calc(0.6rem + var(--safe-t));
}

/* The corner: the date, and how fast it is passing.
 *
 * Wraps internally as well as within the header. The date string and three
 * speed buttons come to about 230px, so below roughly 260px of viewport
 * they do not fit on one line even with the header wrapped -- which is not
 * only a folding phone's cover display but any browser at 400% zoom, where
 * a 960px window becomes 240 CSS pixels. */
.corner { display: flex; align-items: center; gap: 0.6rem; flex: 0 1 auto; flex-wrap: wrap; }
.speeds { display: flex; gap: 0.2rem; }
.speed-pick {
  padding: 0.12rem 0.42rem;
  font-size: 0.78rem;
  color: var(--ink-faint);
  background: transparent;
  border-color: var(--line);
  font-variant-numeric: tabular-nums;
}
.speed-pick:hover:not(:disabled) { color: var(--ink); }
.speed-pick.on { color: var(--accent); border-color: var(--accent); background: var(--sunken); }
.brand { display: flex; align-items: baseline; gap: 0.8rem; flex-wrap: wrap; }
.brand h1 {
  margin: 0;
  font-size: 1.5rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
}
.tag { color: var(--ink-faint); font-style: italic; font-size: 0.9rem; }
.clock { color: var(--ink-faint); font-variant-numeric: tabular-nums; font-size: 0.85rem; }

/* ---------------------------------------------------------------- layout */

.layout { display: grid; grid-template-columns: 15.5rem 1fr; gap: 1.4rem; align-items: start; }
/* Grid items default to min-width:auto, which refuses to shrink below their
 * min-content width. On a phone that let the ledger's own column grid
 * (three tracks it had sized for a wider screen) push a 322px container out
 * to 521px and scroll the whole page sideways. Zero lets it shrink, and the
 * ledger then picks a track count that actually fits. */
.layout > * { min-width: 0; }
@media (max-width: 760px) { .layout { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------- ledger */

.ledger {
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.6rem 0.7rem;
  /* Stays put while the page scrolls, and if the ledger itself is taller
   * than the screen -- which it is by the late ages, with sixty resources
   * -- it scrolls inside itself rather than dragging the page along. */
  position: sticky;
  /* Under the pinned header, not under the top of the window. */
  top: calc(var(--top-h, 0px) + 0.4rem);
  max-height: calc(100vh - var(--top-h, 0px) - var(--ticker-h) - 2rem);
  max-height: calc(100dvh - var(--top-h, 0px) - var(--ticker-h) - 2rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.ledger::-webkit-scrollbar { width: 6px; }
.ledger::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 3px; }
.ledger-row {
  display: grid;
  /* `minmax(0, 1fr)`, not `1fr`: a bare `1fr` carries an automatic MINIMUM
   * of the content's own width, so the name refused to shrink and pushed
   * the amount out of the box -- "1418 / 14", sheared mid-number. The name
   * may be abbreviated; the figure may not. */
  grid-template-columns: minmax(0, 1fr) auto;
  /* Two lines rather than three, with nothing dropped: the name and the
   * amount on top, the rate and the time-to-full beneath them. It used to
   * give the rate and the fill note a line each of their own, which cost a
   * third of the panel's height to say the same four things. */
  grid-template-areas: "name value" "rate fill";
  gap: 0 0.5rem;
  padding: 0.16rem 0;
  font-variant-numeric: tabular-nums;
}
/* In a column this narrow a long name or a big number would wrap and make
 * one row twice the height of its neighbours. They truncate instead -- the
 * full text is in the row's tooltip either way -- so every row is the same
 * height and the count of what fits stays predictable. */
.ledger-name {
  grid-area: name;
  color: var(--ink-dim);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ledger-value { white-space: nowrap; }
.ledger-value { grid-area: value; text-align: right; }
.ledger-rate { grid-area: rate; text-align: left; font-size: 0.74rem; }
.ledger-row.full .ledger-value { color: var(--accent); }
.ledger-row.warn .ledger-name { color: var(--bad); }
.ledger-sep { height: 1px; background: var(--line); margin: 0.3rem 0; }
.people-row .ledger-name { color: var(--ink); }
/* Contentment is not a resource -- it has no amount and no rate -- so it
 * reads in the accent rather than as another line of the ledger. */
.mood-row .ledger-name { color: var(--ink); }
.mood-row .ledger-value { color: var(--good); }
.mood-row.full .ledger-value { color: var(--accent); }

.good { color: var(--good); }
.bad { color: var(--bad); }
.muted { color: var(--ink-faint); }

/* ------------------------------------------------------------------ tabs */

.tabs {
  display: flex;
  gap: 0.4rem;
  margin-bottom: 0.9rem;
  flex-wrap: wrap;
  /* Full width, above the two columns: inside the content column it had
   * 190px on a phone and showed three tabs of seven. Sticky so it stays
   * put -- against the page on a desktop, against the scrolling middle on
   * a handset. */
  position: sticky;
  top: var(--top-h, 0px);
  z-index: 5;
  background: var(--bg);
  flex: none;
  padding: 0.5rem 0.4rem 0.45rem;
  margin-left: -0.4rem;
  margin-right: -0.4rem;
  border-bottom: 1px solid var(--line);
}
.tab { background: transparent; border-color: transparent; color: var(--ink-dim); padding: 0.3rem 0.8rem; }
.tab:hover:not(:disabled) { background: var(--bg-panel); }
.tab.on { color: var(--accent); border-color: var(--line); background: var(--bg-panel); }

/* ---------------------------------------------------------------- panels */

.panel {
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  margin-bottom: 1.1rem;
}
.panel h2 {
  margin: 0 0 0.6rem;
  font-size: 0.8rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
}
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.panel-head h2 { margin-bottom: 0.6rem; }
.actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.action { background: var(--raised); border-color: var(--btn-line); padding: 0.5rem 1.1rem; }
.action:hover:not(:disabled) { background: var(--raised-hover); }
.ghost { background: transparent; color: var(--ink-dim); font-size: 0.85rem; }
.ghost.danger:hover:not(:disabled) { color: var(--bad); border-color: var(--bad); }
.mults { display: flex; gap: 0.3rem; margin-bottom: 0.6rem; }
.mult { padding: 0.15rem 0.5rem; font-size: 0.8rem; color: var(--ink-faint); }
.mult.on { color: var(--accent); border-color: var(--accent); }
/* `auto` used to sit in this row; it is its own button now -- see .auto-btn. */

/* ------------------------------------------------------------------ rows */

.row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.6rem 0.1rem;
  border-top: 1px solid var(--line);
}
.row:first-of-type { border-top: none; }
.row-main { flex: 1; min-width: 0; }
.row-title { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.name { font-size: 1.02rem; }
.count { color: var(--accent); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.strain { font-size: 0.78rem; }
.desc { color: var(--ink-faint); font-size: 0.86rem; font-style: italic; margin: 0.1rem 0 0.25rem; }
.facts { display: flex; gap: 0.9rem; flex-wrap: wrap; font-size: 0.8rem; color: var(--ink-dim); }
/* A `.fact` is a comma-joined list ("0.5 food/s, 0.02 wood/s"), not one
 * atom, so it may wrap between its commas. Held on one line it could
 * not fit a narrow column, and nothing clipped it -- a long one pushed
 * the whole page 20px wider than the window, on a DESKTOP. */
.fact { white-space: normal; }
/* GOLD IS ROOM. Green is made each second and red is paid each second, so
 * storage and housing -- which are neither, being somewhere to keep a
 * thing rather than a flow of it -- take the accent instead. Every theme
 * defines all three, so this needs no per-theme rule of its own. */
.fact.room { color: var(--accent); }
/* The key above the building list. Quieter than the rows it explains: it
 * is read once and then never again. */
.facts.key {
  margin: 0.5rem 0 0.2rem;
  font-size: 0.72rem;
  color: var(--ink-faint);
  row-gap: 0.15rem;
}
.cost { font-size: 0.83rem; color: var(--ink-dim); margin-top: 0.25rem; font-variant-numeric: tabular-nums; }
/* Each part of a cost stands on its own: what you have reads plainly, what
 * you are short of reads red. The old whole-line red said that something
 * was missing without saying which. */
/* ------------------------------------------------- the fifth batch ---- */

/* A building's name opens the arithmetic, so it has to read as something
 * that can be pressed without becoming a button in the visual sense: the
 * row already has two of those and a third would compete with them. */
/* Underlined on the text rather than bordered on the box: a border sits
 * at the bottom of the button, which in a baseline-aligned row reads as a
 * stray rule under the name instead of as a link. */
.as-link {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  text-decoration: underline dotted var(--ink-faint);
  text-underline-offset: 3px;
}
.as-link:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* Payback is an opinion about the other facts, so it is set apart from
 * them: the same size, in the faint ink, unless it is saying something
 * worth acting on. */
.fact.pay { color: var(--ink-faint); font-style: italic; }
.fact.pay.good { color: var(--good); font-style: normal; }
.fact.pay.bad { color: var(--bad); font-style: normal; }

.bld-sort { align-items: center; }
.bld-find {
  background: var(--sunken); color: var(--ink); border: 1px solid var(--btn-line);
  border-radius: var(--radius); padding: 0.25rem 0.5rem; font: inherit; font-size: 0.85rem;
  min-width: 8rem; max-width: 12rem; flex: 1 1 8rem;
}
.bld-find:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

/* When a store fills, or when it runs out. Third line of the chip, in the
 * smallest type the chip uses, because it is the least urgent of the
 * three until it is the most. */
.chip-when { font-size: 0.68rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.chip-when.bad { color: var(--bad); }

.pause-pick { font-size: 0.85rem; }
:root.paused .top { box-shadow: inset 0 -2px 0 var(--accent); }

.guide-row { display: flex; gap: 0.4rem; align-items: baseline; margin: 0.25rem 0; }
.guide-dot { color: var(--accent); }
.guide-text { font-size: 0.92rem; }

.season-strip {
  margin: 0.4rem 0 0.2rem; font-size: 0.82rem; color: var(--ink-dim);
  border-left: 3px solid var(--line); padding-left: 0.5rem;
}
.season-strip.good { border-left-color: var(--good); }
.season-strip.bad { border-left-color: var(--bad); }

.floor-add, .standing-add { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.5rem; }
.floor-pick {
  background: var(--sunken); color: var(--ink); border: 1px solid var(--btn-line);
  border-radius: var(--radius); padding: 0.25rem 0.4rem; font: inherit; font-size: 0.85rem;
  max-width: 10rem;
}

/* One reading to a cell, so a phone gets two columns and a desktop four
 * without either being told a number. */
.life-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr)); gap: 0.5rem 0.9rem; }
.life-cell { border-top: 1px solid var(--line); padding-top: 0.3rem; }
.life-name { font-size: 0.75rem; color: var(--ink-faint); }
.life-val { font-size: 1rem; color: var(--ink); font-variant-numeric: tabular-nums; }

.check-row { display: flex; gap: 0.5rem; align-items: baseline; font-size: 0.85rem; padding: 0.15rem 0; }
.check-row.good .check-mark { color: var(--good); }
.check-row.bad .check-mark { color: var(--bad); }
.check-row.bad .check-name { color: var(--bad); }
.check-note { color: var(--ink-faint); font-size: 0.78rem; }

.advice { font-size: 0.92rem; }
.advice-now { color: var(--ink); }
.advice-then { color: var(--accent); }
.advice-note { color: var(--ink-dim); font-size: 0.85rem; margin-top: 0.2rem; }
.advice-good { color: var(--good); margin-top: 0.2rem; }
.advice-bad { color: var(--bad); margin-top: 0.2rem; }

.whatif h3 { margin: 0.7rem 0 0.2rem; font-size: 0.9rem; }

/* A line's heading in the store: quieter than a panel heading, because it
 * is a subject inside one rather than a section of its own. */
.line-head {
  margin: 0.7rem 0 0.1rem; font-size: 0.72rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-faint);
}

.cost-part { color: var(--ink); }
.cost-part.short { color: var(--bad); }
/* The comma lives here so that a phone, which gives each cost a line of
 * its own, can simply not have one. */
.cost-part + .cost-part:not(.cost-note)::before { content: ', '; color: var(--ink-dim); }
.buy { min-width: 6.5rem; }
.row.known { opacity: 0.55; }
.empty { color: var(--ink-faint); font-style: italic; }

.stepper { display: flex; gap: 0.25rem; }
.step { padding: 0.3rem 0.55rem; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- people */

.summary { color: var(--ink-dim); font-size: 0.92rem; }
.bar { height: 4px; background: var(--btn); border-radius: 2px; margin: 0.6rem 0 0.5rem; overflow: hidden; }
.bar-fill { height: 100%; background: var(--accent); width: 0; transition: width 0.2s linear; }
.note { font-size: 0.86rem; }
.note.warn { color: var(--accent); }

/* --------------------------------------------------------------- records */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: 0.8rem; }
.stat-label { color: var(--ink-faint); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em; }
.stat-value { font-size: 1.2rem; font-variant-numeric: tabular-nums; }
.io {
  width: 100%;
  height: 5rem;
  margin-top: 0.7rem;
  background: var(--bg);
  color: var(--ink-dim);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.5rem;
  font: 12px/1.4 ui-monospace, Consolas, monospace;
  resize: vertical;
}

/* ------------------------------------------------------------------- log */

.log-row { display: flex; gap: 0.6rem; padding: 0.12rem 0; font-size: 0.85rem; }
.log-time { color: var(--ink-faint); font-variant-numeric: tabular-nums; flex: none; font-size: 0.78rem; padding-top: 0.1rem; }
.log-text { color: var(--ink-dim); }
.log-row.good .log-text { color: var(--good); }
.log-row.bad .log-text { color: var(--bad); }
.log-row.unlock .log-text { color: var(--accent); }
.log.full { max-height: 26rem; overflow-y: auto; }

.ticker {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  /* Solid, not fading to transparent: at seven rows a gradient let the
   * content behind show through the older entries and both became hard to
   * read. */
  background: var(--bg);
  border-top: 1px solid var(--line);
  padding: var(--ticker-pad) max(calc(1.2rem + var(--safe-r)), calc(50vw - 550px + 1.2rem))
           calc(var(--ticker-pad) + var(--safe-b))
           max(calc(1.2rem + var(--safe-l)), calc(50vw - 550px + 1.2rem));
  max-height: calc(var(--ticker-h) + var(--safe-b));
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
/* One entry per line. Long entries used to wrap to two or three lines
 * each, so a strip sized for five showed three -- the row count and the
 * line count have to be the same thing for the size to mean anything.
 * Scoped to .ticker: the Records tab's full log still wraps and shows
 * every word. */
.ticker .log-row {
  flex-wrap: nowrap;
  align-items: baseline;
}
.ticker .log-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Newest first, so the top of the strip is the live edge. */
.ticker::-webkit-scrollbar { width: 6px; }
.ticker::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 3px; }
.ticker .log-row:first-child .log-text { color: var(--ink); }

/* ----------------------------------------------------------------- toast */

/* One column for all of them, so two at once stack instead of overlapping.
 * The column itself lets clicks through; each toast takes its own. */
.toasts {
  position: fixed;
  right: 1.2rem;
  bottom: 4.5rem;
  max-width: 24rem;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.45rem;
  pointer-events: none;
  /* Above an open dialog (50): "copied", the undo bar and the new-version
   * notice were drawn under its dim layer, which also took their clicks.
   * Still below the other-tab screen and the press tip. */
  z-index: 60;
}
.toast {
  pointer-events: auto;
  background: var(--bg-row);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
  font-size: 0.88rem;
  cursor: pointer;
  transition: opacity 0.8s;
}
.toast.go { opacity: 0; }

/* ============================================================ eras & scale
 * Added when the game grew from one age to eighteen. */

.meter { text-align: center; padding: 0.2rem 0 0.4rem; }
.meter-value { font-size: 1.5rem; color: var(--accent); letter-spacing: 0.04em; }
.meter-era { font-size: 0.9rem; color: var(--ink-dim); }
.meter-watts { font-size: 0.75rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }

.era-head {
  display: flex; align-items: baseline; gap: 0.55rem;
  padding: 0.5rem 0.15rem 0.4rem; margin-top: 0.5rem;
  border-top: 1px solid var(--line); cursor: pointer; user-select: none;
}
.era-head:hover .era-name { color: var(--accent); }
.era-mark { color: var(--ink-faint); width: 0.7rem; font-family: ui-monospace, monospace; }
.era-name { font-size: 0.82rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-dim); }
.era-k { font-size: 0.72rem; color: var(--ink-faint); }
.era-count { margin-left: auto; font-size: 0.72rem; color: var(--ink-faint); }
.era-body { padding-left: 0.15rem; }
.era-body .row:first-of-type { border-top: none; }

/* Buildings, two or three abreast. A building row is compact enough to
 * read in a column; auto-fill picks the count from whatever width there
 * is, so this needs no breakpoint of its own and collapses to one column
 * on a phone by itself. */
.era-body.cols {
  display: grid;
  /* `min(19rem, 100%)` and not a bare 19rem: a bare floor wider than the
   * container cannot be honoured, so the track overflows and takes the
   * page with it -- which is exactly what a 305px phone did. */
  grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr));
  gap: 0 1.4rem;
  align-items: start;
}
/* Stacked, the first row's missing border is right. In columns every row
 * starts a column of its own, so they all need one. */
.era-body.cols .row:first-of-type { border-top: 1px solid var(--line); }
.era-body.cols .row { padding: 0.55rem 0.1rem; }
/* Grid items refuse to shrink below their content, and a `.fact` is one
 * unbreakable run of text ("1.2 machinery/s"). One long enough would
 * inflate its whole track and scroll the page sideways -- the same trap
 * the ledger fell into. Zero lets the track win; the facts wrap instead. */
.era-body.cols > .row { min-width: 0; }
.era-body.cols .row-main { min-width: 0; }
/* Buttons must never collapse the text beside them. A flex item will not
 * shrink below its longest word, so in a narrow column a button wins the
 * width and `.row-main`, which may shrink to nothing, gets none -- the
 * building list rendered one word per line. Letting the row wrap puts the
 * buttons underneath instead, and the floor keeps the text readable. */
.era-body.cols .row { flex-wrap: wrap; }
.era-body.cols .row-main { flex: 1 1 60%; min-width: 9rem; }
.era-body.cols .buy { flex: 1 1 auto; min-width: 0; }

.row.milestone .name { color: var(--accent); }
.row.stance.on { background: var(--sunken); }

.kbig { font-size: 2.4rem; color: var(--accent); letter-spacing: 0.03em; line-height: 1.2; }
.ladder { margin-top: 0.9rem; border-top: 1px solid var(--line); }
.ladder-row {
  display: grid; grid-template-columns: 5.5rem 1fr auto; gap: 0.7rem;
  padding: 0.22rem 0.1rem; font-size: 0.85rem; border-bottom: 1px solid var(--line);
}
.ladder-k { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.ladder-row.past { opacity: 0.45; }
.ladder-row.past .ladder-name { text-decoration: line-through; }
.ladder-row.now { background: var(--sunken); }
.ladder-row.now .ladder-name { color: var(--accent); }
.ladder-row.now .ladder-req { color: var(--accent); }
.ladder-row.future .ladder-name { color: var(--ink-dim); }
.ladder-req { color: var(--ink-faint); font-size: 0.78rem; }

.offer { display: flex; gap: 0.6rem; flex-wrap: wrap; margin: 0.8rem 0 0.5rem; }
.buy.big { min-width: 14rem; padding: 0.55rem 1rem; }
.buy.big.alt { border-color: var(--alt-line); }
.buy.big.alt:hover:not(:disabled) { border-color: var(--alt-line-hover); }
/* The second exit on a building row, shown only beside an Upgrade that
 * cannot be pressed. It has to read as the lesser offer of the two, so it
 * takes the same alternative border the big buttons use rather than a
 * colour of its own. */
.buy.alt { border-color: var(--alt-line); }
.buy.alt:hover:not(:disabled) { border-color: var(--alt-line-hover); }

/* --------------------------------------------------------------- awards */
.row.award { padding: 0.4rem 0.1rem; }
.row.award.locked { opacity: 0.45; }
.row.award.locked .name { color: var(--ink-dim); }
.row.award .name { color: var(--accent); }
.row.award.locked .desc { font-style: normal; }
.log-row.era .log-text { color: var(--accent); font-weight: 600; }

/* What an award pays. Dim while locked -- it is a promise then, not a
 * holding -- and in the good colour once it is actually yours. */
.award-boon { color: var(--ink-faint); font-size: 0.78rem; }
.facts.held .award-boon { color: var(--good); }
.award-total { color: var(--accent); font-size: 0.84rem; margin-top: 0.2rem; }


/* -------------------------------------------------------------- calendar */
.calendar { text-align: center; padding: 0.2rem 0 0.1rem; }
.cal-date { font-size: 0.95rem; color: var(--ink); letter-spacing: 0.02em; }
.cal-season { font-size: 0.78rem; color: var(--ink-faint); }

.ledger-fill {
  grid-area: fill;
  text-align: right;
  font-size: 0.7rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ledger-fill.at-cap { color: var(--accent); }


/* ------------------------------------------------------------- rate gates */
.gate { font-size: 0.83rem; margin-top: 0.25rem; }
.gate.good { color: var(--good); }
.gate.holding { color: var(--accent); }
.gate.bad { color: var(--ink-faint); }
.gate-bar {
  height: 3px; background: var(--btn); border-radius: 2px;
  margin: 0.3rem 0 0.1rem; overflow: hidden; max-width: 26rem;
}
.gate-fill { height: 100%; width: 0; transition: width 0.3s linear; }
.gate-fill.done { background: var(--good); }
.gate-fill.holding { background: var(--accent); }
.gate-fill.stalled { background: var(--bad); }


/* ========================================================= small screens
 * Three sizes: desktop as written above, tablet (one column, ledger flowing
 * into however many columns fit), and phone (tighter type, scrolling tabs,
 * full-width buttons). Everything keys off max-width except the touch
 * rules, which key off `pointer: coarse` -- a touch laptop deserves the
 * bigger targets and an iPad with a trackpad does not.
 *
 * The old single breakpoint was 760px, which left an iPad in portrait
 * (768px) on the two-column desktop layout with a 248px sidebar and barely
 * 470px of content beside it. 880px puts it on the tablet layout instead. */

/* The shell applies when the screen is narrow OR short -- a comma is OR in
 * a media query list. Keyed to width alone it missed a phone in landscape,
 * which is about 960px wide and 440px tall: too wide for the rule, far too
 * short for the scrolling page it therefore got. */
@media (max-width: 880px), (max-height: 600px) {
  /* APP SHELL ------------------------------------------------------------
   * One screen tall, and the middle scrolls.
   *
   * `position: fixed` is not dependable on iOS: fixed elements sit against
   * the layout viewport, the dynamic toolbar means that is not the visible
   * one, and the log strip ended up stranded mid-screen with the page
   * scrolling past its own content beneath it. Nothing here is fixed, so
   * there is nothing to misplace. */
  html, body { height: 100%; overflow: hidden; }

  #app {
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    padding-bottom: 0;   /* nothing to reserve for: the strip is in the flow */
  }

  .top { position: static; flex: none; }

  /* The one thing that scrolls. `min-height: 0` is required, or a flex item
   * refuses to shrink below its content and the box grows instead of
   * scrolling. */
  .layout { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }

  .ticker {
    position: static;
    flex: none;
    max-height: calc(var(--ticker-h) + var(--safe-b));
    padding-left: 0;
    padding-right: 0;
  }

  /* The tab bar is its own row of the shell now, so it is simply always
   * there; only the resource pane still needs to pin, within the middle. */
  .tabs { position: static; margin-bottom: 0; }

  /* UNDER THE THUMB, for anyone who asks for it.
   *
   * The shell is a flex column -- header, tabs, the scrolling middle, the
   * log strip -- so moving the strip is a matter of order and nothing
   * else: no positioning, no reserved height, nothing to misplace on iOS.
   * Off by default, because a hand used to reaching up should not have to
   * relearn where the game lives. */
  :root.tabs-bottom #app > .top { order: 0; }
  :root.tabs-bottom #app > .layout { order: 1; }
  :root.tabs-bottom #app > .tabs {
    order: 2;
    border-top: 1px solid var(--line);
    border-bottom: 0;
    padding-top: 0.25rem;
  }
  :root.tabs-bottom #app > .ticker { order: 3; }
  /* The strip then sits between the log and the notices, and the notices
   * were drawn behind it: the Undo bar's button was under the tabs and
   * could not be pressed (measured at 375x812: bar 662-730, tabs 695-744).
   * They sit above the strip instead. More specific than the phone rule
   * for `.toasts` further down, so it holds at every width this applies. */
  :root.tabs-bottom .toasts {
    bottom: calc(var(--ticker-h) + var(--tabs-h, 3rem) + 0.8rem + var(--safe-b));
  }
  .ledger {
    top: 0;
    /* Whatever the window has left once the header, the tab bar and the log
     * strip have taken theirs. */
    max-height: calc(100vh - var(--top-h, 3.5rem) - var(--tabs-h, 0px) - var(--ticker-h) - 2rem);
    max-height: calc(100dvh - var(--top-h, 3.5rem) - var(--tabs-h, 0px) - var(--ticker-h) - 2rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

@media (max-width: 880px) {
  /* PINNED AT THE SIDE, at every width.
   *
   * It used to stack on top below 880px, which put sixty-odd resources
   * between the top of the page and the game and then scrolled them away
   * the moment you started playing -- so the thing you most want to watch
   * was the thing least often on screen. It keeps its column instead:
   * narrower here, pinned, and scrolling inside itself when there is more
   * than fits. */
  /* Measured: a row wants up to 111px of content, and later ages want
   * more as the numbers grow. 30% of a 375px phone left a 97px content
   * box and clipped four rows out of eight. */
  .layout { grid-template-columns: minmax(8.5rem, 38%) 1fr; gap: 0.6rem; }
  .ledger { padding: 0.5rem 0.55rem; }
}

/* Below any real phone, and where 400% zoom lands: the corner takes a row
 * of its own and its own contents wrap within it. */
@media (max-width: 300px) {
  .corner { flex: 1 1 100%; justify-content: space-between; }
  .clock { font-size: 0.78rem; }
}

@media (max-width: 560px) {
  body { font-size: 14px; }
  #app { padding-top: 0.9rem; }

  .top { flex-wrap: wrap; gap: 0.4rem; }
  .brand h1 { font-size: 1.25rem; }
  .tag { display: none; }          /* the subtitle is the first thing to go */

  /* One scrolling strip beats three wrapped rows of tabs eating the screen
   * before the game starts. */
  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    margin-left: calc(-1 * (0.9rem + var(--safe-l)));
    margin-right: calc(-1 * (0.9rem + var(--safe-r)));
    padding: 0 calc(0.9rem + var(--safe-l));
  }
  .tabs::-webkit-scrollbar { display: none; }
  /* The scrollbar is hidden on purpose, which leaves nothing to say there
   * are more tabs along. A fade on whichever side still has tabs behind it
   * does the job without spending any height. The classes are set by
   * measurement in showActiveTab(), so a strip that fits gets neither. */
  .tabs.scrolls-r {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent);
  }
  .tabs.scrolls-l {
    -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 2rem), transparent);
    mask-image: linear-gradient(to left, #000 calc(100% - 2rem), transparent);
  }
  .tabs.scrolls-l.scrolls-r {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 2rem, #000 calc(100% - 2rem), transparent);
    mask-image: linear-gradient(to right, transparent, #000 2rem, #000 calc(100% - 2rem), transparent);
  }
  /* Snap, so a swipe lands on whole tabs rather than halfway through one.
   *
   * NOT `scroll-behavior: smooth`: that makes every scrollLeft assignment
   * an animation, so putting the selected tab on screen becomes something
   * that happens later rather than something that has happened -- and it
   * never happens at all while the page is in the background. The
   * correction has to be instant to be reliable. */
  /* No scroll snapping. Snap points fight programmatic placement: after
   * the selected tab is scrolled into view the browser re-snaps to centre
   * whichever tab is nearest, which put the chosen one back off the edge at
   * the far end of the strip. Being able to see the tab you are on beats
   * a swipe that lands tidily. */
  /* Seven tabs in 365px: tighten them until they all fit rather than
   * leaving two over the edge to be discovered by swiping. */
  .tab { flex: none; padding: 0.3rem 0.26rem; font-size: 0.8rem; letter-spacing: 0; }
  .tabs { gap: 0.15rem; }

  .panel { padding: 0.75rem 0.8rem; }

  /* With the ledger holding its column, a building row has about 190px to
   * work in, and its description wrapped to six or seven lines -- 280px per
   * building, seven thousand pixels of Build tab. Clamped to two lines it
   * still says what the thing is, and the list becomes navigable. The full
   * text is in the row's tooltip. */
  .era-body .desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* ONE FACT TO A LINE ON A PHONE, and not a clamped box.
   *
   * The clamp was here for height, and it cost two things to get it. A
   * `-webkit-box` ignores the flex `gap` that separated the facts, so they
   * ran together: a Seed Vault read "houses 400.00T humansbuilds +9.00M
   * units/s" with nothing between the two. And two facts sharing a line
   * re-wrap whenever either of them changes width, which on one column
   * moves every row below.
   *
   * A line each cannot do either. It costs a little height -- and gets
   * most of it back, because the labels in front of the numbers are gone
   * and what is left is short enough to fit a 375px screen unwrapped. */
  .era-body .facts { display: block; }
  .era-body .fact { display: block; }
  /* A line each costs height, so the lines themselves are set tighter --
   * these are short readings, not prose, and at 1.35 a stack of four
   * still reads as four separate things. It takes back most of what the
   * stacking spent. */
  .era-body .fact, .era-body .cost-part { line-height: 1.35; }
  /* Stacked, what it does and what it costs are eight lines in the same
   * type; the gap is what says where one ends and the other begins. */
  .era-body .cost { margin-top: 0.5rem; }
  /* Likewise the cost: three resources on one wrapped line was a blob,
   * and it re-wrapped every time a digit was added to any of them. */
  .era-body .cost-part { display: block; }
  .era-body .cost-part + .cost-part:not(.cost-note)::before { content: none; }


  /* The title and the 1/10/100/max buttons share one flex row that did not
   * wrap, so on a 320px screen the buttons ran a few pixels past the edge
   * and took the whole page sideways with them. */
  .panel-head { flex-wrap: wrap; gap: 0.3rem 0.6rem; }
  .mults { flex-wrap: wrap; }

  /* A buy button next to the text leaves neither enough room, so it moves
   * under the row and takes the full width -- easier to hit as well. */
  .row { flex-wrap: wrap; gap: 0.5rem 0.8rem; }
  .buy, .buy.big { width: 100%; min-width: 0; }

  /* And the same for the stepper, which is the one that actually bit: its
   * buttons are 44px each to be thumb-sized, so it asked for 259px of a
   * 307px row. The text column may shrink to nothing (it must, or a long
   * word would push the page sideways), so flex squeezed it to 32px instead
   * of wrapping -- "Knows which valley", one word per line, in a row 590px
   * tall. Here the row-main takes the width and the controls go beneath. */
  /* "WANDERING  Type 0  6" is four things in a flex row with the count
   * pushed right; in a 143px column it simply hung off the page. It wraps
   * now, and the age name gives way first. */
  .era-head { flex-wrap: wrap; row-gap: 0.1rem; }
  .era-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .era-count { margin-left: auto; }

  .row-main { flex: 1 1 100%; }
  /* The stepper is six buttons at 44px apiece -- 264px of thumb targets in
   * a column half that wide. It has to wrap, or it simply hangs off the
   * side of the page. */
  .stepper { width: 100%; flex-wrap: wrap; }
  .mults { flex-wrap: wrap; }
  .offer { flex-direction: column; }
  .offer .buy.big { width: 100%; }

  /* One narrow column at the side, pinned and scrolling inside itself.
   *
   * The rows were 43px each -- 21px for the name and amount, 17px for the
   * rate and the time-to-full, plus padding, nothing wrapped, simply loose.
   * At 30px the same four figures fit and half again as many are on screen.
   *
   * The height is what is left of the window under the header and above the
   * log strip, so it fills its column exactly and scrolls for the rest. */
  .ledger {
    /* dvh, not vh: on iOS Safari 100vh is the height with the toolbar
     * HIDDEN, so anything measured against it is taller than what you
     * can see and the page scrolls past its own content. */
    max-height: calc(100vh - var(--top-h, 3.5rem) - var(--tabs-h, 0px) - var(--ticker-h) - 2rem);
    max-height: calc(100dvh - var(--top-h, 3.5rem) - var(--tabs-h, 0px) - var(--ticker-h) - 2rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .ledger-row { padding: 0.06rem 0; line-height: 1.2; }
  .ledger-name, .ledger-value { font-size: 0.82rem; }
  .ledger-rate, .ledger-fill { font-size: 0.66rem; }
  .meter-value { font-size: 1.25rem; }
  .meter-era { font-size: 0.8rem; }
  .cal-date { font-size: 0.85rem; }
  .kbig { font-size: 2rem; }
  .stats { grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr)); }

  /* 5.5rem of label plus a name plus a requirement does not fit; the
   * requirement moves to its own line under the other two. */
  .ladder-row {
    grid-template-columns: 4.2rem 1fr;
    grid-template-areas: "k name" ". req";
    gap: 0.15rem 0.6rem;
  }
  .ladder-k { grid-area: k; }
  .ladder-name { grid-area: name; }
  .ladder-req { grid-area: req; }
  /* On a very small handset the content column is about 143px, and an age
   * name beside its Kardashev band beside its requirement does not fit in
   * that. They give way rather than hang off the side. */
  .ladder-k, .ladder-name, .ladder-req {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .ladder-name { white-space: nowrap; }

  .toasts {
    left: calc(0.8rem + var(--safe-l));
    right: calc(0.8rem + var(--safe-r));
    max-width: none;
    align-items: stretch;
    bottom: calc(var(--ticker-h) + 0.8rem + var(--safe-b));
  }
}

/* Touch: Apple's 44pt minimum, and 16px on the one text field in the game --
 * iOS zooms the whole page in when you focus an input smaller than that. */
@media (pointer: coarse) {
  button { min-height: 2.75rem; }
  .tab, .step, .mult, .ghost { min-height: 2.75rem; }
  .step, .mult { min-width: 2.75rem; }
  .io { font-size: 16px; }
  .era-head { padding-top: 0.7rem; padding-bottom: 0.6rem; }
}


/* The log strip used to be sized by posture here -- seven rows on a
 * desktop, five on a phone, three on a phone in landscape, keyed off
 * orientation and height because width alone cannot tell a phone in
 * landscape from a tablet in portrait. All of it is gone: the strip is
 * three rows on every device now, which no breakpoint can improve on.
 * See --ticker-rows. */


/* A screen with no height to spare: the chrome gets smaller rather than
 * going away.
 *
 * A phone in landscape is about 440px tall. Full-size header, tagline and a
 * 44px tab bar took 250 of that and left the game 110px -- two rows of a
 * list, under a masthead announcing the name of a game there was no longer
 * room to play. Everything here is still present and still pinned; it is
 * simply sized for the screen it is on, which returns about 280px.
 *
 * It comes after the touch rules on purpose, to win against the 44px
 * minimum. That is a guideline for a comfortable target, and 34px is still
 * a comfortable target; a tab bar with nothing under it is not. */
@media (max-height: 600px) {
  #app { padding-top: 0.4rem; }

  /* The tagline is the first thing to go: it is charm, and charm is what
   * you can afford when there is room for it. */
  .tag { display: none; }
  .brand h1 { font-size: 1.15rem; }
  .top {
    padding-top: calc(0.35rem + var(--safe-t));
    padding-bottom: 0.35rem;
    margin-bottom: 0.4rem;
  }
  .clock { font-size: 0.78rem; }
  .speed-pick { padding: 0.1rem 0.36rem; font-size: 0.72rem; min-height: 1.9rem; }

  .tabs { padding: 0.22rem 0.4rem 0.2rem; }
  .tab { padding: 0.2rem 0.62rem; min-height: 2.1rem; font-size: 0.86rem; }
}


/* ============================================================ settings ==
 * The settings rows reuse the building-row shape so the page reads like
 * the rest of the game rather than like a preferences dialog.
 */
.set-row { align-items: center; }
.set-row .row-title { font-size: 1rem; margin-bottom: 0.1rem; }
.set-row .desc { margin: 0; }

/* The dedication leads the credits and is the only thing in the game set
 * larger than a heading. It should read as meant, not as a footnote. */
.dedication {
  font-style: italic;
  font-size: 1.05rem;
  color: var(--accent);
  padding: 0.4rem 0 0.9rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 0.8rem;
}
.credit {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: 0.2rem 1rem;
  padding: 0.18rem 0;
}
.credit-role { color: var(--ink-faint); font-size: 0.86rem; }
.credit-who { color: var(--ink); }

/* On a phone the two columns become two lines rather than squeezing a name
 * into forty pixels. */
@media (max-width: 560px) {
  .credit { grid-template-columns: minmax(0, 1fr); gap: 0; padding: 0.3rem 0; }
  .credit-role { font-size: 0.78rem; }
}

/* The pixel window -- a 136x64 grid of square cells above the ledger.
 *
 * `image-rendering: pixelated` is the whole trick: the canvas is 136x64
 * actual pixels and the browser scales it up with nearest-neighbour, so a
 * cell stays a hard-edged square at any size. Without it the upscale is
 * smoothed and the art turns to soup.
 *
 * Width is driven by the column and the height follows from the aspect,
 * so it fits the ledger on a desktop and the full width of a phone
 * without a media query. NOTHING HERE CHANGED WHEN THE GRID DOUBLED from
 * 68x32: 136:64 is the same shape, so the window is the same size on
 * screen and every cell is simply half as wide. */
.pixelwin {
  margin: 0 0 0.6rem;
  padding: 4px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: 4px;
  line-height: 0;
}
.pixelwin canvas {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

/* A held gather button keeps looking held.
 *
 * The press itself already survives -- the hold state lives outside the DOM
 * and the pointer is captured on the root element -- but the BUTTON does
 * not: buildShell() replaces every node whenever the revision bumps, which
 * gathering itself causes. The replacement is a brand new element with no
 * :active state, so the button visibly popped back up while still firing,
 * which reads as the game dropping the press.
 *
 * The root carries the id of whatever is being held, so the replacement
 * matches on its own key and comes back already looking pressed. */
/* No pan gesture may start on a gather button.
 *
 * `touch-action: manipulation` on the base button rule still permits
 * panning, and a pan is exactly what steals a long press: the browser
 * takes the pointer, fires pointercancel, and the hold stops a second or
 * two in. These are the only buttons meant to be held, so they are the
 * only ones that need it. */
button.action,
button[data-hold-key] {
  touch-action: none;
}

button.action:active,
button.holding {
  background: var(--btn-hover);
  border-color: var(--accent);
}

/* Status headings in the Neighbours tab. Quieter than a panel because they
 * label the panels beneath rather than being one. */
section.panel.group-head {
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  padding-bottom: 0.35rem;
  margin-bottom: 0.35rem;
}
section.panel.group-head h2 { color: var(--accent); }

/* ============================================================ redesign 3a
 * The 2026-09-10 UI redesign (design_handoff_ages_idle_ui, direction 3a).
 * Everything in this section is layout and structure and reads its colours
 * from the tokens, so it holds in all five themes; Nocturne, the palette the
 * redesign was drawn in, is just one of them. Kept together at the end so it
 * is one place to read, and so its media-scoped rules come after the handset
 * rules they refine. Sizes are the handoff's pixel values as rem against the
 * 16px root -- nothing sets a root size, so rem is the browser's 16px. */

/* Focus is always visible, and always the accent. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ------------------------------------------------------------ the tabs
 * Underlined rather than boxed: the one you are on in full ink over a 2px
 * accent line, the rest faint. Geometry is left to the handset rules.
 *
 * The hover rules name their borders outright. The base `button:hover`
 * out-ranks `.tab` and paints a full border, which on an underlined tab
 * drew a box on hover and turned the active tab's accent line grey. */
.tab { border: 1px solid transparent; border-bottom-width: 2px; border-radius: 0;
       background: transparent; color: var(--ink-faint); }
.tab:hover:not(:disabled) { background: transparent; color: var(--ink); border-color: transparent; }
.tab.on { background: transparent; color: var(--ink); border-color: transparent;
          border-bottom-color: var(--accent); }
.tab.on:hover:not(:disabled) { border-color: transparent; border-bottom-color: var(--accent); }

/* Speed pills: an accent outline for the running speed, a neutral one for
 * the rest -- and the running one keeps its accent under the pointer. */
.speed-pick { background: transparent; border-color: var(--btn-line); color: var(--ink-faint); }
.speed-pick.on { background: transparent; border-color: var(--accent); color: var(--accent); }
.speed-pick.on:hover:not(:disabled) { border-color: var(--accent); }

/* ----------------------------------------------------- the pixel window
 * One bordered frame holding the picture and, under a hairline, the age: its
 * name, a thin bar, and how many of its techs are known. The picture now
 * runs edge to edge inside the frame, which clips its corners. */
.pixelwin { border: 1px solid var(--btn-line); border-radius: var(--radius);
            background: var(--sunken); overflow: hidden; padding: 0; position: relative; }
/* What the pointer is over, named along the top of the picture. */
.pixel-tip { position: absolute; left: 4px; right: 4px; top: 4px; z-index: 1;
             padding: 0.15rem 0.4rem; border: 1px solid var(--line); border-radius: 3px;
             background: var(--bg-panel); color: var(--ink);
             font-size: 0.625rem; line-height: 1.35; opacity: 0; pointer-events: none;
             transition: opacity 0.12s; }
.pixel-tip.on { opacity: 0.94; }
:root[data-motion="reduced"] .pixel-tip { transition: none; }
/* `aspect-ratio: auto` so the canvas takes its shape from its own 136x118
 * rather than from any ratio written for the old 136x64. */
.pixelwin canvas { display: block; width: 100%; height: auto; aspect-ratio: auto;
                   image-rendering: pixelated; }
/* Its own line height: the frame sets `line-height: 0` so the canvas sits
 * with no gap under it, and the footer's text would inherit that and fold
 * flat. */
.pixel-foot { display: flex; align-items: center; gap: 0.525rem; line-height: 1.4;
              padding: 0.35rem 0.525rem; border-top: 1px solid var(--line); }
.pixel-age { flex: none; max-width: 45%; overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; font-size: 0.625rem; letter-spacing: 0.1em;
             text-transform: uppercase; color: var(--ink-dim); }
.pixel-bar { flex: 1; min-width: 0; height: 3px; border-radius: 2px;
             background: var(--line); overflow: hidden; }
.pixel-fill { height: 100%; width: 0; background: var(--accent); }
.pixel-count { flex: none; font-size: 0.625rem; color: var(--accent-hi);
               font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------ the ledger */
.meter-value { font-size: 1.5rem; color: var(--accent-hi); letter-spacing: 0.02em; }
.meter-era { font-size: 0.75rem; color: var(--ink-dim); }
.meter-watts { font-size: 0.6875rem; color: var(--ink-faint); }
/* A hairline that fades in and out at both ends rather than stopping dead. */
.ledger-sep { background: linear-gradient(to right, transparent, var(--line) 24px,
              var(--line) calc(100% - 24px), transparent); }

/* THE CHIPS. Two columns; each chip a slightly raised cell with its state on
 * a 2px left edge -- filling, full, short -- and a plain edge when the store
 * is neither rising nor falling. One step up from the panel (`--bg-row`)
 * rather than the panel colour itself, or a chip would vanish into the
 * ledger it sits in. `min-width: 0` is what lets a long figure wrap instead
 * of forcing the whole page wider. */
.chips { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
         gap: 0.35rem; margin: 0.2rem 0 0.35rem; }
.chip { min-width: 0; background: var(--bg-row); border-left: 2px solid var(--line);
        border-radius: 0 6px 6px 0; padding: 0.35rem 0.525rem; line-height: 1.35;
        font-variant-numeric: tabular-nums; }
.chip.fill { border-left-color: var(--good); }
.chip.full { border-left-color: var(--accent); }
.chip.short { border-left-color: var(--bad); }
.chip-name { font-size: 0.6875rem; color: var(--ink-dim); white-space: nowrap;
             overflow: hidden; text-overflow: ellipsis; }
/* The figure wraps at its " / " if it must; it is never cut. */
.chip-val { font-size: 0.8125rem; color: var(--ink); overflow-wrap: anywhere; }
.chip.full .chip-val { color: var(--accent-hi); }
.chip-rate { font-size: 0.625rem; color: var(--ink-dim); white-space: nowrap; }
.chip.fill .chip-rate { color: var(--good); }
.chip.full .chip-rate { color: var(--accent); }
.chip.short .chip-rate { color: var(--bad); }
.chip-head { display: flex; align-items: center; gap: 0.35rem; padding: 0.35rem 0 0.15rem;
             font-size: 0.625rem; letter-spacing: 0.12em; text-transform: uppercase;
             color: var(--ink-faint); cursor: pointer;
             -webkit-user-select: none; user-select: none; }
.chip-head:hover { color: var(--ink-dim); }
.chip-mark { width: 0.7rem; font-family: ui-monospace, monospace; letter-spacing: 0; }
.chip-count { margin-left: auto; font-variant-numeric: tabular-nums; letter-spacing: 0; }
.chip-legend { display: flex; flex-wrap: wrap; gap: 0.2rem 0.7rem; margin: 0.15rem 0 0.1rem;
               font-size: 0.625rem; color: var(--ink-faint); }
.chip-swatch.fill { color: var(--good); }
.chip-swatch.full { color: var(--accent); }
.chip-swatch.short { color: var(--bad); }

/* ---------------------------------------------------- the next-unlock strip
 * Name, bar and countdown to the nearest thing being saved for, directly
 * under the tab bar. Pinned there on a desktop; a plain row of the shell on
 * a handset, where the tabs are one too. */
.next-strip { display: flex; align-items: center; gap: 0.7rem; min-width: 0;
              padding: 0.525rem 1.4rem; background: var(--bg-panel);
              border-bottom: 1px solid var(--line); }
.next-kicker { flex: none; font-size: 0.625rem; letter-spacing: 0.12em;
               text-transform: uppercase; color: var(--ink-faint); }
.next-name { flex: none; max-width: 40%; overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; font-size: 0.8125rem; color: var(--ink); }
.next-strip.empty .next-name { color: var(--ink-dim); }
.next-bar { flex: 1; min-width: 0; height: 3px; border-radius: 2px;
            background: var(--line); overflow: hidden; }
.next-fill { height: 100%; width: 0; background: var(--accent); }
.next-note { flex: none; font-size: 0.75rem; color: var(--accent-hi); white-space: nowrap;
             font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ auto
 * An action, not a buy size: a full-width outlined button of its own. */
.auto-btn { display: block; width: 100%; margin: 0 0 0.7rem; padding: 0.7rem 1.05rem;
            border: 1px solid var(--accent); border-radius: var(--radius);
            background: transparent; color: var(--accent);
            font-weight: 500; font-size: 0.875rem; text-align: center; }
.auto-btn:hover:not(:disabled) { border-color: var(--accent);
            background: color-mix(in srgb, var(--accent) 12%, transparent); }
.auto-btn:active:not(:disabled) { background: color-mix(in srgb, var(--accent) 30%, transparent); }

/* ------------------------------------------------------- Nocturne's type
 * Headings never past weight 500; the hierarchy is carried by size and
 * space. Scoped to the theme, like its face, so every other theme keeps the
 * serif and its own proportions. */
:root[data-theme="nocturne"] .brand h1 { font-size: 1.0625rem; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; }
:root[data-theme="nocturne"] h2 { font-size: 1.1875rem; font-weight: 500; }

/* ------------------------------------------------------------ by posture
 * A desktop: the strip pins under the tabs, and the ledger clears the tab
 * bar and the strip as well as the header when it sticks. It used to clear
 * only the header, so the pinned tab bar sat over its top. */
@media (min-width: 881px) and (min-height: 601px) {
  .tabs { gap: 1.4rem; padding: 0.35rem 1.4rem 0; }
  .tab { padding: 0.525rem 0; font-size: 0.8125rem; margin-bottom: -1px; }
  .next-strip { position: sticky; z-index: 5; margin: -0.9rem -0.4rem 0.9rem;
                top: calc(var(--top-h, 0px) + var(--tabs-h, 0px)); }
  .ledger {
    top: calc(var(--top-h, 0px) + var(--tabs-h, 0px) + var(--next-h, 0px) + 0.4rem);
    max-height: calc(100vh - var(--top-h, 0px) - var(--tabs-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 2rem);
    max-height: calc(100dvh - var(--top-h, 0px) - var(--tabs-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 2rem);
  }
}
/* A handset, or a window too short to pin anything: the strip is a row of
 * the shell like the tab bar, and the ledger's height makes room for it. */
@media (max-width: 880px), (max-height: 600px) {
  .next-strip { flex: none; margin: 0; padding: 0.4rem 0.6rem; gap: 0.5rem; }
  .ledger {
    max-height: calc(100vh - var(--top-h, 3.5rem) - var(--tabs-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 2rem);
    max-height: calc(100dvh - var(--top-h, 3.5rem) - var(--tabs-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 2rem);
  }
}
/* A ledger this narrow cannot hold two columns of figures, and a strip this
 * narrow cannot hold its kicker: one column, and the countdown may shorten. */
@media (max-width: 560px) {
  .chips { grid-template-columns: minmax(0, 1fr); }
  .next-kicker { display: none; }
  .next-note { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
}

/* ============================================================ features 2026-09-14
 * The build queue, wonders, age cards, charts, the flow and account
 * dialogs (src/panels.js). Colours from the tokens, so all five themes. */

.chip { cursor: pointer; }
.queue-btn { margin-left: 0.35rem; }

.age-card .facts { margin-bottom: 0.3rem; }
.good-note { color: var(--good); }
.bad-note { color: var(--bad); min-height: 0; }
.bad-note:empty { display: none; }

.wonder-bar { height: 4px; border-radius: 2px; background: var(--line); overflow: hidden;
              margin: 0.45rem 0 0.25rem; }
.wonder-fill { height: 100%; width: 0; background: var(--accent); }
.wonder .actions { margin-top: 0.4rem; }

.diplomacy { border-color: var(--accent); }

.chart { margin-top: 0.35rem; }
.chart-svg { display: block; width: 100%; height: auto; max-height: 14rem;
             background: var(--sunken); border-radius: var(--radius); }
.spark { display: block; width: 100%; height: 4rem; background: var(--sunken);
         border-radius: var(--radius); margin: 0.5rem 0 0.2rem; }
.chart-grid { stroke: var(--line); stroke-width: 1; }
.chart-now { fill: none; stroke: var(--accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
.chart-past { fill: none; stroke: var(--ink-faint); stroke-width: 1; opacity: 0.55;
              vector-effect: non-scaling-stroke; }

/* Padded clear of the notch and the home bar, and the box sized to what
 * is left. `calc(100vh - 2rem)` was the toolbar-hidden height on iOS, so a
 * tall dialog ran off both ends, and its Close sat under the status bar
 * in the installed app. */
.modal-back { position: fixed; inset: 0; z-index: 50; background: rgba(0, 0, 0, 0.55);
              display: flex; align-items: center; justify-content: center;
              padding: max(1rem, var(--safe-t)) max(1rem, var(--safe-r)) max(1rem, var(--safe-b)) max(1rem, var(--safe-l)); }
.modal { width: 100%; max-width: 32rem; max-height: 100%; overflow-y: auto; overflow-x: hidden;
         min-width: 0;
         background: var(--bg-panel); border: 1px solid var(--line); border-radius: var(--radius);
         padding: 0.9rem 1rem 1rem; }
.modal h3 { margin: 0.8rem 0 0.3rem; font-size: 0.9rem; color: var(--ink-dim); font-weight: 500; }
.flow-row { display: flex; gap: 0.6rem; align-items: baseline; padding: 0.2rem 0;
            border-bottom: 1px solid var(--line); font-size: 0.85rem; }
.flow-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.flow-amt { font-variant-numeric: tabular-nums; }
.flow-share { width: 3rem; text-align: right; color: var(--ink-faint); font-variant-numeric: tabular-nums; }

.auth { display: flex; flex-direction: column; gap: 0.6rem; }
.auth-field { display: flex; flex-direction: column; gap: 0.25rem; }
.auth-label { font-size: 0.8rem; color: var(--ink-dim); }
.auth input { font: inherit; font-size: 16px; padding: 0.5rem 0.6rem; color: var(--ink);
              background: var(--sunken); border: 1px solid var(--btn-line); border-radius: var(--radius); }
/* The sync code, to be read and copied by hand: large, fixed-width, and
   selectable with one click. */
.sync-code { font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace; font-size: 1.25rem;
             letter-spacing: 0.06em; text-align: center; padding: 0.6rem; margin-bottom: 0.6rem;
             background: var(--sunken); border: 1px solid var(--btn-line); border-radius: var(--radius);
             user-select: all; overflow-wrap: anywhere; color: var(--ink); }
.conflict { border: 1px solid var(--accent); border-radius: var(--radius); padding: 0.6rem 0.7rem;
            margin: 0.5rem 0; }

/* ============================================================ features 2 (2026-09-14)
 * Disasters, districts, the cabinet, pickers, shared cards (src/panels2.js). */

.warning { border-color: var(--bad); }
.picker { flex-wrap: wrap; margin: 0.25rem 0 0.5rem; }

.district-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.35rem;
                 margin: 0.5rem 0; width: 100%; max-width: 26rem; }
.district { min-width: 0; min-height: 3rem; padding: 0.3rem; font-size: 0.72rem; line-height: 1.2;
            overflow-wrap: anywhere; color: var(--ink-faint); background: var(--sunken);
            border: 1px dashed var(--btn-line); border-radius: var(--radius); }
.district.on { color: var(--ink); background: var(--bg-row); border: 1px solid var(--accent); }

.cabinet { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 0.45rem;
           margin-top: 0.4rem; }
.artefact { padding: 0.5rem 0.6rem; border: 1px dashed var(--line); border-radius: var(--radius);
            color: var(--ink-faint); min-width: 0; }
.artefact.found { border: 1px solid var(--accent); color: var(--ink); background: var(--bg-row); }
.artefact .fact { display: inline-block; margin-top: 0.3rem; }

/* Contrast, measured in all five themes: an empty district cell read 2.86:1,
 * an unchosen picker button 2.62:1, and a found artefact's description and
 * effect 2.89:1 in Dark and 1.85:1 in Wacky -- all under the 3:1 floor. Each
 * moves one step up the theme's own ink. */
.district { color: var(--ink-dim); }
.picker .mult { color: var(--ink-dim); }
.artefact.found .desc { color: var(--ink-dim); }
.artefact.found .fact, .artefact.found .fact.award-boon.held { color: var(--ink); }
.artefact .found-at { margin-top: 0.3rem; font-size: 0.75rem; color: var(--ink-dim); }
.artefact-set { margin-top: 0.8rem; }
.artefact-set + .facts { margin: 0.2rem 0 0.4rem; }
/* The welcome-back dialog's list of what happened. */
.away-event { padding: 0.25rem 0; border-bottom: 1px solid var(--line); font-size: 0.9rem; }
.away-event.bad { color: var(--bad); }
.away-event.good { color: var(--good); }
.away-more > summary { cursor: pointer; padding: 0.2rem 0; }

/* The third batch: planner, age table, palette, glance, friends. */
.plan-step .desc, .row.friend .desc { font-style: normal; }
.table-wrap { overflow-x: auto; }
table.compare { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
table.compare th, table.compare td { text-align: left; padding: 0.3rem 0.5rem;
  border-bottom: 1px solid var(--line); white-space: nowrap; }
table.compare tr:first-child th { color: var(--ink-dim); font-weight: normal; }
table.compare th[scope="row"] { font-weight: normal; color: var(--ink-dim); }
.modal.palette { max-width: 34rem; }
.palette-input { width: 100%; box-sizing: border-box; padding: 0.5rem 0.6rem; font: inherit; color: var(--ink);
  background: var(--sunken); border: 1px solid var(--btn-line); border-radius: var(--radius, 4px); }
.palette-list { display: flex; flex-direction: column; gap: 0.15rem; margin-top: 0.5rem; }
.palette-item { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  width: 100%; text-align: left; background: transparent; border: 1px solid transparent; }
.palette-item.on { border-color: var(--accent); background: var(--bg-row); }
.palette-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.palette-hint { color: var(--ink-dim); font-size: 0.8rem; white-space: nowrap; }
.kbd { display: inline-block; min-width: 8.5rem; font-family: ui-monospace, Consolas, monospace; color: var(--accent); }
.row.found-flash { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The Search button at the end of the tab strip: a tab's size, pushed to
 * the far end where there is room, and never mistaken for the current tab. */
.tab-search { margin-left: auto; flex: 0 0 auto; background: transparent; color: var(--ink-dim);
  border: 1px dashed var(--line); padding: 0.3rem 0.8rem; white-space: nowrap; }
.tab-search:hover:not(:disabled) { color: var(--ink); border-color: var(--accent); }

/* The fourth batch: countdowns, photo, chronicle. */
.log-row.countdown .log-time { color: var(--accent); }
.log-row.countdown .log-text { color: var(--ink-dim); }
canvas.photo { display: block; max-width: 100%; height: auto; image-rendering: pixelated;
  border: 1px solid var(--line); margin: 0.2rem 0 0.6rem; }
.pixelwin canvas[data-photo] { cursor: zoom-in; }
.journal-row { display: flex; gap: 0.6rem; padding: 0.15rem 0; font-size: 0.9rem; }
.journal-year { color: var(--ink-dim); min-width: 5.5rem; white-space: nowrap; }
.journal-text { min-width: 0; }
.journal-row.bad .journal-text { color: var(--bad); }
.journal-row.good .journal-text { color: var(--good); }
.journal-row.era .journal-text, .journal-row.leader .journal-text { color: var(--ink); }

.shared .facts { margin: 0.35rem 0; }

/* ============================================================ phones (2026-09-14)
 * Measured before: on a 440x956 phone the resource column took 153px and
 * left the game 239px and 651px of height; on 375x667, 191px wide and 362px
 * tall; on 320x568, 136px and 284px. Every panel was a tall sliver and the
 * Build tab ran to nine thousand pixels.
 *
 * At phone widths the game gets the whole width. The stores become a drawer
 * opened from the header, the header is one row with the clock beneath, the
 * log is two rows, and no more than two toasts show at once. Last in the
 * file on purpose, so these win over the older handset rules above. */

.stores-toggle { display: none; }

/* Foldable panels (src/panels.js, `foldable`): the heading folds its panel.
 * At every width, since a folded panel is also tidier on a desktop. */
.foldable > .panel-head { cursor: pointer; -webkit-user-select: none; user-select: none; }
.foldable > .panel-head h2::before { content: '▾ '; color: var(--ink-faint); }
.foldable.folded > .panel-head h2::before { content: '▸ '; }
.panel.folded > :not(.panel-head) { display: none; }
.panel.folded > .panel-head { margin-bottom: 0; }

@media (max-width: 560px) {
  #app { position: relative; }

  .layout { grid-template-columns: minmax(0, 1fr); }
  .ledger { display: none; }

  :root.ledger-open .ledger {
    display: block;
    position: absolute;
    z-index: 40;
    left: calc(0.5rem + var(--safe-l));
    right: calc(0.5rem + var(--safe-r));
    top: calc(0.9rem + var(--top-h, 3rem) + var(--tabs-h, 0px) + var(--next-h, 0px) + 0.25rem);
    max-height: calc(100vh - 0.9rem - var(--top-h, 3rem) - var(--tabs-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 1rem);
    max-height: calc(100dvh - 0.9rem - var(--top-h, 3rem) - var(--tabs-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 1rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--bg-panel);
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    box-shadow: 0 14px 36px rgba(0, 0, 0, 0.55);
    padding: 0.7rem 0.8rem;
  }

  .stores-toggle { display: inline-flex; align-items: center; justify-content: center;
                   order: 1; padding: 0.2rem 0.7rem; font-size: 0.85rem; }
  :root.ledger-open .stores-toggle { border-color: var(--accent); color: var(--accent); }
  .stores-toggle.alert::after { content: ''; width: 0.45rem; height: 0.45rem; margin-left: 0.4rem;
                                border-radius: 50%; background: var(--bad); }

  /* One row: the name, then Stores and the speeds; the clock underneath. */
  .corner { display: contents; }
  .top { flex-wrap: wrap; align-items: center; column-gap: 0.35rem; row-gap: 0.15rem; }
  .brand { flex: 1 1 auto; min-width: 0; }
  .brand h1 { font-size: 1.05rem; white-space: nowrap; }
  .speeds { order: 2; }
  .clock { order: 3; flex: 1 1 100%; font-size: 0.72rem; }

  :root { --ticker-rows: 2; }

  /* The picture's own row at the top of the content (ui.js, buildShell).
   * Beside it, the Kardashev type and the population; it scrolls away with
   * the tab. Shorter phones get a smaller picture. */
  .phone-hero { display: flex; align-items: center; gap: 0.75rem; margin: 0 0 0.6rem; }
  .phone-hero .pixelwin { flex: none; width: 8rem; margin: 0; }
  .hero-text { min-width: 0; }
  .hero-type { font-size: 1.3rem; color: var(--accent-hi); line-height: 1.2; }
  .hero-line { font-size: 0.78rem; color: var(--ink-dim); margin-top: 0.15rem; }
}
@media (max-width: 560px) and (max-height: 700px) {
  .phone-hero .pixelwin { width: 6.5rem; }
  .hero-type { font-size: 1.1rem; }
}
/* A phone on its side: wide enough for the side column, but 440px tall or
 * less. Measured at 956x440, the header, tabs, strip and a two-row log took
 * 186px and left the game 241px, and the column showed only the picture --
 * not one store. The log goes to one row, the picture is drawn small so the
 * stores show under it, and one toast at a time. */
@media (max-height: 500px) and (min-width: 561px) {
  :root { --ticker-rows: 1; }
  .ledger .pixelwin { max-width: 7.5rem; margin-left: auto; margin-right: auto; }
  .toasts .toast:nth-last-child(n+2) { display: none; }
  /* Under the picture, the age's blurb wrapped to four lines and the watts
   * took a fifth, and still no store showed. The type stays; the rest is on
   * the Ascent tab. */
  .meter-era, .meter-watts { display: none; }
  .meter-value { font-size: 1.1rem; }
  /* And the picture goes BESIDE the type, contentment and people rather
   * than above them: those five rows are about the picture's own height, so
   * side by side they give back ~130px of a 248px column to the stores. */
  .ledger { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr);
            column-gap: 0.6rem; align-content: start; }
  .ledger > * { grid-column: 1 / -1; }
  .ledger > .pixelwin { grid-column: 1; grid-row: 1 / span 5; max-width: none; margin: 0; align-self: start; }
  .ledger > .meter, .ledger > .mood-row, .ledger > .people-row,
  .ledger > .ledger-sep { grid-column: 2; }
  /* Beside the picture the column is ~88px, and a name and its value side
   * by side clipped to "C" and "H" (measured). Stacked, both fit. */
  .ledger > .mood-row, .ledger > .people-row {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0; min-width: 0;
  }
  .ledger > .mood-row > *, .ledger > .people-row > * { max-width: 100%; text-align: left; margin-left: 0; }

  /* The newest two, each at most three lines. Four stacked toasts covered
   * half a 440px-wide screen (measured). */
  .toasts .toast:nth-last-child(n+3) { display: none; }
  .toast { font-size: 0.8rem; padding: 0.5rem 0.7rem;
           display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
}

/* ------------------------------------------------- the sixth batch ---- */

/* A pin sits at the right of the row title, quiet until it is on.
 *
 * Out of the flow, over the title's right edge, so it can be a proper
 * target without its size having any say over the title line: inline, at
 * the size of the glyph, it was a 16px button under a mouse (the sweep's
 * floor is 18). The title keeps the room with padding. */
.row-main { position: relative; }
.row-title.pinnable { padding-right: 2.3rem; }
.pin {
  position: absolute; top: -0.45rem; right: -0.3rem;
  width: 2.2rem; height: 2.2rem; min-height: 0; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0;
  font: inherit; font-size: 1rem; line-height: 1; color: var(--ink-faint); cursor: pointer;
}
@media (pointer: coarse) {
  .pin { width: 2.75rem; height: 2.75rem; min-height: 0; top: -0.7rem; right: -0.55rem; }
}
.pin.on { color: var(--accent); }
.pin:hover { color: var(--accent); }
.pinned > h2 { margin-bottom: 0.3rem; }

/* When a price can be paid. One line always -- it is clipped, never
 * wrapped, so its text changing cannot change the row's height. */
.ready-in {
  /* The dim ink, not the faint one: it is something to act on, and the
   * faint ink is 3.0:1 on a panel in the default theme. */
  font-size: 0.76rem; color: var(--ink-dim); font-style: italic;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-height: 1.1em; margin-top: 0.1rem;
}
.ready-in:empty { display: none; }

/* What a technology opens: plain ink, since it is information rather than
 * a flow in or out. */
.fact.opens { color: var(--ink-dim); }

.auto-mode { margin: 0.35rem 0 0.1rem; }

/* The folded earlier ages: the same header, a touch quieter. */
.era-head.earlier .era-name { color: var(--ink-dim); }

/* Press and hold to read. Above everything, including dialogs. */
.press-tip {
  position: fixed; z-index: 100000; max-width: min(22rem, calc(100vw - 16px));
  background: var(--bg-row); color: var(--ink); border: 1px solid var(--accent);
  border-radius: var(--radius); padding: 0.5rem 0.65rem; font-size: 0.85rem;
  white-space: pre-line; box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45); pointer-events: auto;
}
/* A long press on a phone otherwise offers Copy / Look Up over the words
 * it is about to explain. Callout only: text can still be selected. */
@media (pointer: coarse) {
  .layout [title], .ledger [title], .top [title] { -webkit-touch-callout: none; }
}

/* The undo bar is a toast with a button in it, so it keeps its row shape
 * even where phone toasts are clamped to a few lines. */
.toast.undo-bar {
  display: flex !important; align-items: center; gap: 0.7rem; cursor: default;
  -webkit-line-clamp: unset; overflow: visible;
}
.undo-text { font-variant-numeric: tabular-nums; }

.log-filter { margin: 0.2rem 0 0.5rem; }

.rel-box { margin: 0.4rem 0 0.7rem; }
.rel-spark { display: block; width: 100%; height: auto; max-height: 4.5rem; }
.rel-floor { stroke-dasharray: 4 4; }
.count.good { color: var(--good); }
.count.bad { color: var(--bad); }

.answer-pick {
  background: var(--sunken); color: var(--ink); border: 1px solid var(--btn-line);
  border-radius: var(--radius); padding: 0.25rem 0.4rem; font: inherit; font-size: 0.85rem;
  max-width: 100%; min-width: 0;
}
.plan-live { margin-top: 0.4rem; }

.err-row { display: flex; flex-direction: column; gap: 0.1rem; padding: 0.35rem 0;
  border-top: 1px solid var(--line); font-size: 0.82rem; }
.err-when { color: var(--ink-faint); font-size: 0.74rem; }
.err-msg { color: var(--bad); overflow-wrap: anywhere; }

@media (max-width: 560px) {
  .answer-pick { width: 100%; }
}

/* Standing down for another tab: everything behind it is frozen. */
.onetab {
  position: fixed; inset: 0; z-index: 99990;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.72); padding: 1rem;
}
.onetab-box {
  max-width: 26rem; background: var(--bg-panel); color: var(--ink);
  border: 1px solid var(--accent); border-radius: var(--radius); padding: 1.2rem 1.3rem;
}
.onetab-box h2 { margin: 0 0 0.5rem; }
.onetab-box p { margin: 0 0 1rem; color: var(--ink-dim); }

/* ====================================================== the banner layout
 * (2026-10-07). The picture across the top of the content, drawn as wide as
 * it is shown (src/pixel.js); a strip of the stores worth watching under
 * it; the full ledger as a drawer at every width; and the tabs as icons --
 * down a rail on anything wider than a phone, along a bar at the bottom of
 * one, with the four least-used behind More. Everything here is under
 * `:root.layout-banner`, set by src/theme.js, so Classic (Settings, Layout)
 * is the stylesheet above exactly as it was. */

.tab-ico, .tab-more, .more-sheet { display: none; }
:root.layout-banner .tab-ico { display: block; line-height: 0; }
:root.layout-banner .tab-ico svg { width: 1.25rem; height: 1.25rem; fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
:root.layout-banner .tab-label { display: block; line-height: 1.2; }

/* --- the content has the width; the ledger is a drawer everywhere ------ */
:root.layout-banner .layout { grid-template-columns: minmax(0, 1fr); }
:root.layout-banner .ledger { display: none; }
:root.layout-banner.ledger-open .ledger {
  display: block; position: fixed; z-index: 40;
  top: calc(var(--top-h, 3rem) + 1rem); bottom: auto;
  left: auto; right: calc(0.75rem + var(--safe-r));
  width: min(22rem, calc(100vw - 1.5rem - var(--safe-l) - var(--safe-r)));
  max-height: calc(100vh - var(--top-h, 3rem) - var(--ticker-h) - 2.5rem);
  max-height: calc(100dvh - var(--top-h, 3rem) - var(--ticker-h) - 2.5rem);
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--bg-panel); border: 1px solid var(--accent); border-radius: var(--radius);
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.55); padding: 0.7rem 0.8rem;
}
:root.layout-banner .stores-toggle { display: inline-flex; align-items: center; justify-content: center;
  padding: 0.2rem 0.7rem; font-size: 0.85rem; }
:root.layout-banner.ledger-open .stores-toggle { border-color: var(--accent); color: var(--accent); }
:root.layout-banner .stores-toggle.alert::after { content: ''; width: 0.45rem; height: 0.45rem;
  margin-left: 0.4rem; border-radius: 50%; background: var(--bad); }

/* --- the banner --------------------------------------------------------- */
.banner { margin: 0 0 0.5rem; }
.banner .pixelwin { margin: 0; }
/* The reading the ledger used to open with, over the sky. Under the line
 * that names what the pointer is over, which sits above it when shown. */
.banner-over { position: absolute; left: 0.5rem; top: 0.5rem; max-width: calc(100% - 1rem);
  pointer-events: none; line-height: 1.25; padding: 0.3rem 0.6rem 0.35rem;
  background: color-mix(in srgb, var(--bg-panel) 84%, transparent);
  border: 1px solid var(--line); border-radius: var(--radius); }
.banner-type { font-size: 1.15rem; color: var(--accent-hi); }
.banner-line { font-size: 0.72rem; color: var(--ink-dim); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }

/* --- the stores strip --------------------------------------------------- */
.store-strip { display: flex; align-items: center; gap: 0.4rem; position: sticky; top: 0; z-index: 4;
  background: var(--bg); padding: 0.35rem 0; margin: 0 0 0.7rem; border-bottom: 1px solid var(--line); }
.store-chips { flex: 1 1 auto; min-width: 0; display: flex; gap: 0.35rem; overflow-x: auto;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 1.5rem), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 1.5rem), transparent); }
.store-chips::-webkit-scrollbar { display: none; }
.store-chip { flex: none; display: inline-flex; align-items: center; gap: 0.35rem; white-space: nowrap;
  padding: 0.22rem 0.55rem; font-size: 0.78rem; color: var(--ink); background: var(--bg-row);
  border: 1px solid var(--line); border-left: 3px solid var(--line); border-radius: var(--radius); }
.store-chip:hover:not(:disabled) { background: var(--bg-panel); border-color: var(--btn-line); }
.store-name { color: var(--ink-dim); }
.store-val { font-variant-numeric: tabular-nums; }
.store-note { font-size: 0.7rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.store-chip.fill, .store-chip.fill:hover:not(:disabled) { border-left-color: var(--good); }
.store-chip.fill .store-note { color: var(--good); }
.store-chip.full, .store-chip.full:hover:not(:disabled) { border-left-color: var(--accent); }
.store-chip.full .store-note { color: var(--accent); }
.store-chip.short, .store-chip.short:hover:not(:disabled) { border-left-color: var(--bad); }
.store-chip.short .store-note { color: var(--bad); }
.store-chip.folk, .store-chip.folk:hover:not(:disabled) { border-left-color: var(--ink-dim); }
.store-all { flex: none; font-size: 0.78rem; padding: 0.22rem 0.65rem; white-space: nowrap;
  color: var(--accent); border-color: var(--accent); background: transparent; }

/* --- the rail: every width with room for one ----------------------------- */
@media (min-width: 561px) {
  :root.layout-banner #app > .tabs {
    grid-area: rail; display: flex; flex-direction: column; flex-wrap: nowrap; gap: 0.2rem;
    margin: 0; padding: 0.35rem; overflow-x: hidden; overflow-y: auto;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-panel);
    -webkit-mask-image: none; mask-image: none;
  }
  :root.layout-banner .tabs .tab, :root.layout-banner .tabs .tab-search {
    display: flex; flex-direction: column; align-items: center; gap: 0.22rem; flex: none;
    width: 100%; margin: 0; padding: 0.45rem 0.15rem; min-height: 0;
    font-size: 0.66rem; letter-spacing: 0.02em; color: var(--ink-dim);
    border: 1px solid transparent; border-radius: var(--radius); background: transparent;
  }
  :root.layout-banner .tabs .tab:hover:not(:disabled),
  :root.layout-banner .tabs .tab-search:hover:not(:disabled) {
    color: var(--ink); background: var(--bg-row); border-color: transparent; }
  :root.layout-banner .tabs .tab.on, :root.layout-banner .tabs .tab.on:hover:not(:disabled) {
    color: var(--accent); background: var(--raised); border-color: var(--btn-line); }
  :root.layout-banner .tabs .tab-search { margin-top: 0.35rem; border-top: 1px dashed var(--line);
    border-radius: 0; padding-top: 0.6rem; }
  :root.layout-banner .tabs .tab-search:hover:not(:disabled) { border-top-color: var(--line); }
}

/* A desktop: the page scrolls. The rail pins under the header beside
 * everything else, the next-unlock strip under the header (no tab bar
 * over it now), and the stores strip under that. */
@media (min-width: 881px) and (min-height: 601px) {
  :root.layout-banner #app { display: grid; grid-template-columns: 4.75rem minmax(0, 1fr);
    column-gap: 1.1rem; grid-template-areas: "top top" "rail next" "rail main"; align-items: start; }
  :root.layout-banner #app > .top { grid-area: top; }
  :root.layout-banner #app > .tabs { position: sticky; top: calc(var(--top-h, 0px) + 0.6rem);
    max-height: calc(100vh - var(--top-h, 0px) - var(--ticker-h) - 1.5rem); }
  :root.layout-banner #app > .next-strip { grid-area: next; margin: 0 0 0.8rem; top: var(--top-h, 0px); }
  :root.layout-banner #app > .layout { grid-area: main; }
  :root.layout-banner .store-strip { top: calc(var(--top-h, 0px) + var(--next-h, 0px)); }
}

/* A tablet, or a phone on its side: the app shell, with the rail down the
 * left of the scrolling middle. */
@media (max-width: 880px) and (min-width: 561px), (max-height: 600px) and (min-width: 561px) {
  :root.layout-banner #app { display: grid; grid-template-columns: 4.25rem minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr) auto; column-gap: 0.7rem;
    grid-template-areas: "top top" "rail next" "rail main" "tick tick"; }
  :root.layout-banner #app > .top { grid-area: top; }
  :root.layout-banner #app > .tabs { position: static; align-self: stretch; margin-bottom: 0.4rem; }
  :root.layout-banner #app > .next-strip { grid-area: next; }
  :root.layout-banner #app > .layout { grid-area: main; }
  :root.layout-banner #app > .ticker { grid-area: tick; }
}
@media (max-height: 500px) and (min-width: 561px) {
  :root.layout-banner .tabs .tab, :root.layout-banner .tabs .tab-search { padding: 0.3rem 0.1rem; }
  :root.layout-banner .tabs .tab-label { font-size: 0.6rem; }
}

/* --- a phone: the bar at the bottom ------------------------------------- */
@media (max-width: 560px) {
  :root.layout-banner #app { display: grid; grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr) auto auto;
    grid-template-areas: "top" "next" "main" "tick" "bar"; }
  :root.layout-banner #app > .top { grid-area: top; }
  :root.layout-banner #app > .next-strip { grid-area: next; }
  :root.layout-banner #app > .layout { grid-area: main; }
  /* The bar takes the home indicator's inset, so the log above it does not. */
  :root.layout-banner #app > .ticker { grid-area: tick; padding-bottom: var(--ticker-pad);
    max-height: var(--ticker-h); }
  :root.layout-banner #app > .tabs {
    grid-area: bar; position: static; display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 0; overflow: visible; flex-wrap: nowrap;
    margin: 0 calc(-1 * (0.9rem + var(--safe-r))) 0 calc(-1 * (0.9rem + var(--safe-l)));
    padding: 0.1rem calc(0.3rem + var(--safe-r)) calc(0.2rem + var(--safe-b)) calc(0.3rem + var(--safe-l));
    border-top: 1px solid var(--line); border-bottom: 0; background: var(--bg-panel);
    -webkit-mask-image: none; mask-image: none;
  }
  :root.layout-banner .tabs .tab, :root.layout-banner .tabs .tab-more {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.15rem;
    min-width: 0; margin: 0; padding: 0.3rem 0; font-size: 0.64rem; letter-spacing: 0.02em;
    color: var(--ink-dim); background: transparent; border: 0; border-radius: var(--radius);
  }
  :root.layout-banner .tabs .tab:hover:not(:disabled),
  :root.layout-banner .tabs .tab-more:hover:not(:disabled) { background: transparent; color: var(--ink); }
  :root.layout-banner .tabs .tab.on, :root.layout-banner .tabs .tab.on:hover:not(:disabled),
  :root.layout-banner .tabs .tab-more.on, :root.layout-banner.more-open .tabs .tab-more {
    color: var(--accent); background: transparent; border: 0; }
  :root.layout-banner .tabs .tab-extra, :root.layout-banner .tabs .tab-search { display: none; }

  :root.layout-banner.more-open .more-sheet {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.4rem;
    position: fixed; z-index: 45;
    left: calc(0.5rem + var(--safe-l)); right: calc(0.5rem + var(--safe-r));
    bottom: calc(var(--tabs-h, 3.5rem) + 0.4rem);
    padding: 0.6rem; background: var(--bg-panel); border: 1px solid var(--accent);
    border-radius: calc(var(--radius) * 2); box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.5);
  }
  .more-item { display: flex; flex-direction: column; align-items: center; gap: 0.25rem;
    padding: 0.6rem 0.3rem; font-size: 0.78rem; color: var(--ink);
    background: var(--bg-row); border: 1px solid var(--line); }
  .more-item.on { color: var(--accent); border-color: var(--accent); }
  .more-item .tab-ico svg { width: 1.4rem; height: 1.4rem; }

  /* Notices clear the log and the bar; the drawer stops above both. */
  :root.layout-banner .toasts { bottom: calc(var(--ticker-h) + var(--tabs-h, 3.5rem) + 0.6rem); }
  :root.layout-banner.ledger-open .ledger {
    left: calc(0.5rem + var(--safe-l)); right: calc(0.5rem + var(--safe-r)); width: auto;
    max-height: calc(100vh - var(--top-h, 3rem) - var(--ticker-h) - var(--tabs-h, 3.5rem) - 2rem);
    max-height: calc(100dvh - var(--top-h, 3rem) - var(--ticker-h) - var(--tabs-h, 3.5rem) - 2rem);
  }

  /* The strip scrolls away with the picture here: a phone has no height
   * to pin a second row of chrome with. The Stores button stays. */
  .store-strip { position: static; margin-bottom: 0.55rem; }
  .banner-over { left: 0.35rem; top: 0.35rem; padding: 0.15rem 0.45rem 0.2rem; }
  .banner-type { font-size: 0.95rem; }
  .banner-line { font-size: 0.64rem; }
}

/* === THE TABS REORGANISED (2026-10-08) ==================================
 * The Almanac, People in two columns, Others as a list and a page, search
 * as a bar on Records, and the picture opened live. */

/* Something new on the Almanac: a dot on its button. */
.tab.news { position: relative; }
.tab.news::after { content: ''; position: absolute; top: 0.3rem; right: 0.35rem;
  width: 0.45rem; height: 0.45rem; border-radius: 50%; background: var(--bad);
  box-shadow: 0 0 0 2px var(--bg); }
:root.layout-banner .tabs .tab.news::after { top: 0.25rem; right: calc(50% - 1.05rem); }

/* --- the almanac's look back -------------------------------------------- */
details.lookback { border-top: 1px solid var(--line); padding: 0.45rem 0; }
details.lookback:first-of-type { border-top: 0; }
details.lookback > summary { display: flex; align-items: baseline; justify-content: space-between; gap: 0.8rem;
  cursor: pointer; list-style: none; }
details.lookback > summary::-webkit-details-marker { display: none; }
details.lookback > summary::before { content: '▸'; color: var(--ink-faint); margin-right: 0.4rem; }
details.lookback[open] > summary::before { content: '▾'; }
details.lookback > summary .name { flex: 1; min-width: 0; }
details.lookback > summary .count { color: var(--ink-dim); font-size: 0.8rem; white-space: nowrap; }
details.lookback .age-card { margin-top: 0.4rem; }

/* --- people: two columns ------------------------------------------------ */
.people-switch { margin-bottom: 0.8rem; }
.people-cols { display: block; }
.people-cols[data-view="jobs"] .people-side,
.people-cols[data-view="side"] .people-jobs { display: none; }
@media (min-width: 1000px) {
  .people-switch { display: none; }
  .people-cols { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    gap: 1.1rem; align-items: start; }
  .people-cols[data-view] .people-side, .people-cols[data-view] .people-jobs { display: block; }
  .people-side { position: sticky; top: calc(var(--top-h, 0px) + var(--next-h, 0px) + 3.4rem);
    max-height: calc(100vh - var(--top-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 5rem);
    max-height: calc(100dvh - var(--top-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 5rem);
    overflow-y: auto; overscroll-behavior: contain; padding-right: 0.15rem; }
  :root:not(.layout-banner) .people-side { top: calc(var(--top-h, 0px) + var(--tabs-h, 0px) + 0.6rem); }
}
/* Leadership: three parts of one window. */
.leadership .merged > .panel { background: none; border: 0; border-radius: 0; padding: 0; margin: 0; }
.leadership .merged > .panel + .panel { border-top: 1px solid var(--line); margin-top: 0.8rem; padding-top: 0.8rem; }
.leadership .merged > .panel h2 { font-size: 0.72rem; color: var(--accent); }
.leadership .kbig { font-size: 1.5rem; }

/* --- others: a list and a page ------------------------------------------ */
.others-lay { display: grid; grid-template-columns: 13rem minmax(0, 1fr); gap: 1rem; align-items: start; }
.others-list { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.5rem;
  background: var(--bg-panel); border: 1px solid var(--line); border-radius: var(--radius);
  position: sticky; top: calc(var(--top-h, 0px) + var(--next-h, 0px) + 3.4rem);
  max-height: calc(100vh - var(--top-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 5rem);
  max-height: calc(100dvh - var(--top-h, 0px) - var(--next-h, 0px) - var(--ticker-h) - 5rem);
  overflow-y: auto; }
:root:not(.layout-banner) .others-list { top: calc(var(--top-h, 0px) + var(--tabs-h, 0px) + 0.6rem); }
.others-group { font-size: 0.68rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-dim); margin: 0.55rem 0.45rem 0.15rem; }
.others-item { display: grid; grid-template-columns: 0.75rem minmax(0, 1fr); column-gap: 0.35rem;
  align-items: baseline; text-align: left; padding: 0.4rem 0.5rem; min-height: 2.4rem; line-height: 1.3; row-gap: 0.1rem;
  background: none; border: 1px solid transparent; border-radius: var(--radius); color: var(--ink); }
.others-item:hover:not(:disabled) { background: var(--bg-row); }
.others-item.on { border-color: var(--accent); background: var(--bg-row); }
.others-item.war .others-name { color: var(--bad); }
.others-name { grid-column: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.others-sub { grid-column: 2; font-size: 0.72rem; color: var(--ink-dim); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.others-dot { grid-column: 1; grid-row: 1; width: 0.5rem; height: 0.5rem; border-radius: 50%;
  background: var(--line); align-self: center; }
.others-dot.warm { background: var(--good); }
.others-dot.uneasy { background: var(--accent); }
.others-dot.cold, .others-dot.hostile { background: var(--bad); }
.others-dot.asks { box-shadow: 0 0 0 2px var(--accent-hi, var(--accent)); }
.others-detail { min-width: 0; }
.others-back { margin-bottom: 0.7rem; }
@media (max-width: 760px) and (min-width: 561px) {
  .others-lay { grid-template-columns: 10.5rem minmax(0, 1fr); gap: 0.7rem; }
}
@media (max-width: 560px) {
  .others-lay { grid-template-columns: minmax(0, 1fr); }
  .others-list { position: static; max-height: none; }
  .others-lay.picked .others-list { display: none; }
  .others-lay:not(.picked) .others-detail { display: none; }
}

/* --- records: search ----------------------------------------------------- */
.rec-search-row { display: flex; gap: 0.5rem; align-items: center; }
.rec-search-input { flex: 1; min-width: 0; box-sizing: border-box; padding: 0.5rem 0.6rem; font: inherit;
  color: var(--ink); background: var(--bg-row); border: 1px solid var(--line); border-radius: var(--radius); }
.rec-search-input:focus { outline: none; border-color: var(--accent); }
.rec-scopes { margin-top: 0.5rem; }
.rec-results { display: flex; flex-direction: column; gap: 0.15rem; margin-top: 0.5rem;
  max-height: 24rem; overflow-y: auto; }
.rec-results:empty { display: none; }
.rec-results .log { max-height: none; }

/* --- the picture, open and live ----------------------------------------- */
.modal.viewer { max-width: min(96vw, 1600px); }
.viewer-stage { overflow: hidden; max-height: 70vh; max-height: 70dvh; touch-action: none;
  border-radius: var(--radius); background: var(--sunken, var(--bg)); }
.viewer-stage.zoomed { overflow: auto; cursor: grab; }
.viewer-stage.zoomed:active { cursor: grabbing; }
.viewer-zoom .pixelwin { margin: 0; }
.viewer .banner-over, .viewer .pixel-foot { display: none; }
.viewer .pixel-tip { position: fixed; left: 50%; right: auto; top: auto; bottom: 1.2rem;
  transform: translateX(-50%); max-width: 90vw; font-size: 0.8rem; z-index: 60; }
.viewer-acts { align-items: center; margin-top: 0.6rem; }
.viewer-level { min-width: 2.6rem; text-align: center; color: var(--ink-dim); font-variant-numeric: tabular-nums; }

/* AWARDS, LAID OUT ONLY WHERE THEY ARE SEEN (2026-10-08). 373 rows and a
 * page 120,000px tall: re-attaching them on a rebuild cost 22-26 ms of
 * style and layout alone. Off-screen rows are skipped now (about 2 ms),
 * and the tab rebuilds in 12-16 ms instead of 31-39 (measured at
 * 1280x800). Award rows hold no popups, so the containment clips nothing;
 * the size kept for an unseen row is the measured median, about 85px. */
.row.award { content-visibility: auto; contain-intrinsic-size: auto 5.25rem; }

/* SOMETHING WAITS ON THE OTHER SIDE (2026-10-13): the People switch when
 * two heirs stand in the column not shown. The ring the Others list uses
 * for an answer that is wanted. */
.people-switch .mult.asks:not(.on) { box-shadow: 0 0 0 2px var(--accent-hi, var(--accent)); color: var(--ink); }
