/* ==========================================================================
   Astral Sovereignty — CONCEPT 02 layout

   A denser arrangement of the whole game: one slim line at the top, THREE
   tiers of navigation all on screen at once (the five destinations along the
   bottom, the group's categories under the header, the screen's own sections
   under those), and everything left over given to data.

   Loaded alongside game.css on every page; EVERY rule here is scoped under
   `.concept-c02` — a class ui/concept/c02/shell.js puts on #app when it
   assembles, and app.js clears at the top of every render — so with the class
   absent this file changes nothing at all. tests/concept_c02_shell.test.js
   pins that scoping.

   WHY THE CLASS IS ON #app AND NOT ON <html>. Same answer shell-app.css gives:
   these rules assume the game shell's arrangement, so on login, shard select or
   the public status page they would be wrong, and while the shell module is
   still being fetched they would be wrong about the standard arrangement
   underneath them. Owned by the thing that builds the arrangement, applied at
   the same moment, and there is no state where the CSS and the DOM disagree.

   DISCIPLINE — do not relax these:

   - NO COLOUR LITERALS. Not one. Every colour below is a game.css token, which
     core/colours.js rewrites inline on <html> for every scheme (Astral, Mint,
     Ember, Verdant, Slate, Custom) and every colour-vision mode. The `--c2-*`
     declarations in section 1 are ALIASES over those tokens and nothing else —
     they exist so the rest of the file can say "accent" instead of choosing a
     colour. So the concept pack's navy-and-mint look is simply the Mint scheme,
     and this layout is correct in the other five without a second stylesheet.
     tests/concept_c02_shell.test.js fails on `#hex`, `rgb(`, `hsl(` and every
     named colour.
   - game.css IS NOT TOUCHED. The header, tab-bar and sub-strip geometry it pins
     is overridden here, inside the scope, and nowhere else.
   - STATE IS NEVER COLOUR ALONE. Every tone below is used beside a word or an
     icon the JS prints (`Waiting`, `short 200 t`, `Unread`, `In combat`), which
     is what keeps the whole vocabulary safe in Monochrome.
   - EVERY TAP TARGET IS 44px. Density is bought with visual weight — a row
     action is a text link inside a 44px row, a secondary is an outline, there
     is one filled button per subject — and never by shrinking a hit area.
   - No backdrop-filter: nothing scrolls behind these bars, so a blur buys
     nothing and costs a compositor pass per frame on cheap hardware.
   ========================================================================== */

/* ---------- 1. Aliases ---------------------------------------------------- */

/* The whole palette this file is allowed to speak in. Each one is a game.css
   token and nothing more; the names are semantic so a rule below says what it
   MEANS rather than which colour it picked. */
.concept-c02 {
  --c2-accent: var(--blue);
  --c2-accent-lit: var(--blue-lit);
  --c2-accent-dim: var(--blue-dim);
  --c2-accent-deep: var(--blue-deep);
/* NO --c2-accent-ink. It aliased --on-accent, which core/colours.js computes as
   pure white on the game's own accent — 4.25:1 at this layout's 13px, under what
   WCAG asks for, and reported by the render harness on every filled control that
   used it. Every filled or pressed state in this layout takes --c2-accent-deep
   with --c2-ink instead, and tests/concept_c02_kit.test.js fails if the alias
   comes back. */
  --c2-ok: var(--green);
  --c2-ok-dim: var(--green-dim);
  --c2-warn: var(--amber);
  --c2-warn-dim: var(--amber-dim);
  --c2-bad: var(--danger);
  --c2-bad-dim: var(--danger-dim);
  --c2-info: var(--cyan);
  --c2-info-dim: var(--cyan-dim);
  --c2-rule: var(--line);
  --c2-rule-soft: var(--line-soft);
  --c2-rule-lit: var(--line-lit);
  --c2-panel: var(--panel);
  --c2-panel-2: var(--panel-2);
  --c2-canvas: var(--bg-0);
  --c2-band: var(--bg-1);
  --c2-ink: var(--text);
  --c2-ink-2: var(--text-2);
  --c2-ink-3: var(--text-3);
  --c2-ink-4: var(--text-4);

  /* Measurements. The three bands and the gutters, declared once so a rule
     never repeats a number and the desktop block can re-point one of them. */
  --c2-head-h: 40px;
  --c2-strip-h: 44px;
  --c2-bar-tap: 3.25rem;   /* 52px of tap + the bar's own padding = 56px */
  --c2-gutter: 10px;
  --c2-gap: 6px;
  --c2-pad: 8px;
  --c2-rad: 4px;
  --c2-rail-w: 4.5rem;
  --c2-data-h: 28px;
  --c2-text: 13px;
  --c2-text-sm: 12px;
}

/* ---------- 2. The frame -------------------------------------------------- */

/* #app becomes the window: exactly one viewport tall, a column of fixed bands
   with one scrolling band in the middle.

   `height`, not `min-height`: the whole idea is that the chrome does not move,
   and an #app that can grow is a page that scrolls its chrome away.

   Deliberately NO `overflow: hidden`. If the chrome ever did exceed the
   viewport — a combat strip naming a dozen fleets, at 200% system text — then
   clipping it would take the bottom bar off the screen with no way back. Left
   visible, that case scrolls the page instead: ugly, and reachable. */
.concept-c02 { height: 100dvh; }

/* THE BOTTOM BAR MUST SURVIVE A PULL-DOWN. The bar is `position: static` at the
   foot of a 100dvh flex column, so anything that moves the PAGE moves the bar
   off the bottom edge — and a pull that starts on the header or on the bar
   itself has no scrollable ancestor short of the viewport, which Chrome answers
   with pull-to-refresh plus the Android 12 stretch. `contain` on the root stops
   both while ordinary page scrolling still works. `:has`, because the frame's
   class is on #app and the root is above it; both selectors still key on
   .concept-c02, so with the class absent this file still changes nothing. */
html:has(> body > #app.concept-c02),
body:has(> #app.concept-c02) { overscroll-behavior-y: contain; }

/* The bands keep their own height; the screen absorbs everything left over.
   Written out one by one rather than as `> *` so a screen's own absolutely
   positioned children can never pick it up. The two ALERT strips are matched as
   descendants, not children: both are filled in place inside a
   `display: contents` mount point, so the strip itself is a grandchild of the
   root while still laying out as a direct flex item. */
.concept-c02 > .shell-header,
.concept-c02 .shard-shutdown-strip,
.concept-c02 .combat-strip,
.concept-c02 > .sub-tabbar,
.concept-c02 > .c2-detail,
.concept-c02 > .tabbar { flex: 0 0 auto; }

/* ---------- 3. The top line ----------------------------------------------- */

/* ONE ROW. game.css gives the in-game header a fixed two-row height below
   800px (the clock owns one of them there); here the wordmark, the clock, the
   menu and the mailbox share a single line and the row break is switched off.
   Content-height with a 40px floor: the band is DECLARED at 40px and MEASURES
   44px, because the menu and mailbox buttons are 44x44 tap targets and a 44px
   target cannot live inside a 40px row. The tap floor wins that argument
   everywhere in this layout; see the header comment. */
.concept-c02 > .shell-header--game {
  height: auto;
  min-height: calc(var(--c2-head-h) + env(safe-area-inset-top));
  padding: 0 var(--c2-pad);
  padding-top: env(safe-area-inset-top);
  flex-wrap: nowrap;
  align-items: center;
  column-gap: .3rem;
  row-gap: 0;
  justify-content: flex-start;
  border-bottom: 1px solid var(--c2-rule);
}
/* WHO SITS WHERE, and it is `order` rather than a second header, because the
   shell may not build one: app.js's DOM order is mark, clock, menu, identity,
   spacer, refresh, mailbox, and what this line wants is the mark on the left
   and everything you press on the right, under the thumb.
   The budget this has to hold, measured at 390px with the mailbox present (live
   mode — the demo has no mailbox, so it is the wider case that binds): 374px of
   content box against 67 (mark + "Astral") + 117 (clock) + 3 x 44 (refresh,
   mailbox, menu) + 5 x 4.8 (gaps) = 340px. At 360px it is 344 against 293 once
   the wordmark drops to the mark. */
.concept-c02 > .shell-header--game .brand { order: 0; }
.concept-c02 > .shell-header--game .spacer { order: 1; }
.concept-c02 > .shell-header--game .tick-chip { order: 2; }
.concept-c02 > .shell-header--game .icon-btn { order: 3; }
.concept-c02 > .shell-header--game .mail-btn { order: 4; }
.concept-c02 > .shell-header--game .menu-btn { order: 5; }
/* The break that makes two rows in the shipped header is what this line is
   trying not to spend. */
.concept-c02 > .shell-header--game .header-break { display: none; }
/* The identity block (crest, empire name, rank) belongs to a two-row header; on
   one 40px line it is the thing that would push the clock off the screen. It is
   in the drawer and on the Profile section, both one tap away, and the menu
   button that reaches them is directly beside where it used to be. */
.concept-c02 > .shell-header--game .empire-id { display: none; }
/* THE WAY OUT OF THE DEMO STAYS ON THE LINE, and hiding it was a real trap.
   This rule used to be `display: none`, on the argument that "leaving the demo
   lives in the drawer with everything else about the account". It does not:
   ui/drawer.js builds no such row, ui/settings.js gates logout behind
   `!isDemo()`, and the shard-select screen offers nothing either — so a player
   who tried the demo in this layout had no way out of it short of editing the
   URL. A frame may move a control; it may not be the only reason a control is
   unreachable.
   It is compact rather than absent: 12px, tight padding, and the 44px floor
   every other target on this line pays. The word is app.js's, in its own case —
   this file changes the size, not the label. */
.concept-c02 > .shell-header--game .btn {
  flex: none;
  min-height: var(--tap);
  padding: 0 .45rem;
  border-radius: var(--c2-rad);
  /* QUIET, AND STILL THERE. It is an escape hatch, not a destination: at
     --line-lit it out-shouted the clock, which is the one thing on this line
     that is read constantly. The dim rule and the page's third ink keep it
     legible (the same pair every label in the layout uses) without making it
     the loudest thing on the header. */
  border: 1px solid var(--c2-rule);
  background: none;
  color: var(--c2-ink-3);
  font-size: var(--c2-text-sm);
  font-weight: 600;
  white-space: nowrap;
}
.concept-c02 > .shell-header--game .btn:hover { border-color: var(--c2-accent); color: var(--c2-ink); }
/* AND THE LINE PAYS FOR IT OUT OF THE WORDMARK. Measured at 390px: the mark and
   "Astral" is 70px, the clock 117, and three 44px targets plus their gaps is
   156 — which leaves 31px for a 73px button. The wordmark is the one item on
   this line that is not load-bearing (brandMark() keeps its full aria-label
   whatever is painted), so where the header also carries the demo button it
   drops to the mark alone and the sum is 322px of a 374px box.
   `:has(.btn)` is the whole condition: that button exists only in demo mode
   (app.js builds it behind isDemo()), so this asks the DOM the question rather
   than asking a stylesheet to know about a mode. */
@media (max-width: 429px) {
  .concept-c02 > .shell-header--game:has(.btn) .brand b { display: none; }
}
/* The wordmark gives way first and shortens from the end; below 360px it drops
   to the mark alone (section 11). */
.concept-c02 > .shell-header--game .brand {
  flex: 0 1 auto;
  min-width: 0;
  gap: .35rem;
  font-size: var(--c2-text);
  letter-spacing: .01em;
}
.concept-c02 > .shell-header--game .brand img { width: 20px; height: 20px; }
/* THE WORDMARK GIVES WAY IN TWO STEPS, and both are measured rather than
   chosen. On one line with three 44px tap targets and a clock that never
   shortens, the full name only fits from about 430px up; below that the second
   word goes (game.css already does exactly this to the bare header below
   480px), and at 360px and under the mark stands alone (section 11).
   Nothing is lost to a screen reader either way: brandMark() carries
   aria-label="Astral Sovereignty" whatever is painted. */
@media (max-width: 429px) {
  .concept-c02 > .shell-header--game .brand b span { display: none; }
}
.concept-c02 > .shell-header--game .brand b {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The spacer holds the clock and the two buttons hard right. */
.concept-c02 > .shell-header--game .spacer { flex: 1 1 auto; min-width: 0; }
/* The clock never gives way — it is the single most-looked-at element in the
   game — but it gives way LAST: past its cap it ellipsises like any other label
   instead of leaving the screen. */
.concept-c02 > .shell-header--game .tick-chip {
  flex: none;
  height: auto;
  padding: .15rem .45rem;
  gap: .3rem;
  max-width: 100%;
}
/* THE CLOCK RESERVES ITS OWN BOX, and this is the one rule here that exists to
   stop the screen MOVING rather than to place something.

   The header is content-height, so its height follows its widest item — and its
   widest item is a string that rewrites itself every second and changes LENGTH
   as it goes: "1:02:09" (7 characters) becomes "17:54" (5), becomes "0:09" (4).
   Without a reserved box the row re-flows in the last seconds before orders
   lock, which is the worst possible moment: the player's thumb is on Cancel.

   .tick-chip is monospace (game.css), so `ch` is exact, and 7ch is the longest
   countdown any shard builds. Capped in `vw` and not in `%`: a percentage here
   resolves against a chip that is `flex: none` and therefore sized by this very
   text, so it is circular and constrains nothing — while `ch` scales with the
   font, so at 200% system text an uncapped reserve pushes the clock off the
   edge of a 360px phone with no way to scroll to it. */
.concept-c02 > .shell-header--game .tick-chip .tick-text { min-width: min(7ch, 30vw); }
/* The tick's NUMBER is the elastic half of the pill and shortens first. */
.concept-c02 > .shell-header--game .tick-chip .tick-num { flex: 0 1 auto; min-width: 0; }
/* Both header buttons are real 44px targets on every pointer, not only on a
   coarse one: this layout is used on a desktop with a mouse too, and the floor
   is the floor. */
.concept-c02 > .shell-header--game .icon-btn {
  width: var(--tap); height: var(--tap); flex: none;
}

/* ---------- 4. The alert strips ------------------------------------------- */

/* The one thing in this arrangement that is not up for discussion: a shard
   closing and a fleet in combat stay above the screen, on every screen. In the
   standard shell they are `position: sticky` and follow the page down; here the
   page does not move, so there is nothing to follow — `static` says that out
   loud, and it is the only declaration this file writes about either strip.
   The header goes with them: unpinning the strips drops their z-index into the
   unpositioned layer, and a still-sticky header would slide over them in the
   one case section 2 leaves reachable (chrome taller than the viewport). */
.concept-c02 > .shell-header,
.concept-c02 .shard-shutdown-strip,
.concept-c02 .combat-strip { position: static; }
.concept-c02 > .shell-header { backdrop-filter: none; }

/* AND THEY ARE COMPACTED, which is a size change and nothing else. Measured at
   390px the contact strip was 82.8px — three lines at 14px/1.5 with the "What
   this means" fold on a line of its own — which is a tenth of the phone spent
   on furniture above a layout whose whole point is density. At the frame's own
   13px/1.3 with 6px/10px padding it is two lines, and the fold sits on the
   second line where it fits instead of claiming a third.
   WHAT IS NOT TOUCHED: the content, the order, the colours, the borders, the
   44px tap floor the strip's own links and its fold summary already pay
   (game.css buys those with padding and gives the space back with a negative
   margin, so they cost this band nothing). Only size, spacing and whether the
   fold may share a line. */
.concept-c02 .shard-shutdown-strip,
.concept-c02 .combat-strip {
  padding: 6px var(--c2-gutter);
  font-size: var(--c2-text);
  line-height: 1.3;
  /* The row gap goes: the strip's lines are one sentence broken across a narrow
     phone, not separate statements, and .15rem between each of three of them is
     a line's worth of nothing. The COLUMN gap is untouched. */
  row-gap: 0;
}
/* AND THE STRIP'S OWN LINKS KEEP THEIR 44px TARGET. game.css buys it with
   `padding-block: 11px` and gives the space back with `margin-block: -11px` —
   arithmetic that assumes the page's 15px/1.5 line box (22.5 + 22 = 44.5). At
   this frame's 13px/1.3 the line box is 16.9 and the same pair makes a 38.9px
   target: two real controls in the one strip a player must be able to act on,
   lost to a type change. Derived from the line box instead of assumed, so it is
   44px at any size the text is set to — and the negative margin still hands the
   whole overflow back, so the band's height is exactly what it was. */
.concept-c02 .combat-strip a {
  padding-block: calc((var(--tap) - 1.3em) / 2);
  margin-block: calc((1.3em - var(--tap)) / 2);
}

/* `flex-basis: 100%` is what put the fold and the "nothing is being fought yet"
   sentence on lines of their own. `auto` lets them ride the line they follow
   when there is room and take their own line when there is not — which is the
   same answer, reached by measurement rather than by decree. */
.concept-c02 .combat-strip .combat-strip-why,
.concept-c02 .combat-strip .combat-strip-note { flex-basis: auto; }

/* ---------- 5. Tier 2 — the category strip -------------------------------- */

/* The current group's sections: one horizontal row, 44px, the active item in
   the accent with a 2px underline, and an edge fade where it overflows (the
   mask is game.css's and is left alone). app.js's revealActiveTab scrolls the
   current one into view on every render, so nothing is ever off the side with
   nothing saying so. */
.concept-c02 > .sub-tabbar {
  flex-wrap: nowrap;
  min-height: var(--c2-strip-h);
  padding: 0 calc(var(--c2-gutter) / 2);
  background: var(--c2-band);
  border-bottom: 1px solid var(--c2-rule);
  /* 14px MIXED CASE. The preset's uppercase at 12.24px plus .04em of tracking
     needed 428px for Empire · Profile · Orders · Reports · Settings; the same
     five words in their own case at 14px need 331px of a 380px content box, so
     the strip stops scrolling and "Settings" stops being clipped at the edge. */
  font-size: 14px;
}
.concept-c02 > .sub-tabbar a {
  flex: 1 0 auto;
  justify-content: center;
  align-items: center;
  min-height: var(--c2-strip-h);
  padding: 0 .45rem;
  color: var(--c2-ink-3);
  font-size: 14px;
  font-weight: 600;
  border-bottom: 2px solid transparent;
}
.concept-c02 > .sub-tabbar a[aria-current="page"] {
  color: var(--c2-accent-lit);
  border-bottom-color: var(--c2-accent);
}

/* ---------- 6. Tier 3 — the detail strip ---------------------------------- */

/* The shell's own slot, and the one node it builds. It keeps its height when a
   screen has no sections to put in it, so moving between a screen with tier-3
   views and one without never shifts the page under the player's thumb.
   The active item is a filled accent rectangle with `--on-accent` ink — a
   different mark from tier 2's underline, so the two strips are never read as
   one. */
.concept-c02 > .c2-detail {
  display: flex;
  align-items: stretch;
  gap: .25rem;
  min-height: var(--c2-strip-h);
  padding: 0 calc(var(--c2-gutter) / 2);
  overflow-x: auto;
  scrollbar-width: none;
  background: var(--c2-canvas);
  border-bottom: 1px solid var(--c2-rule);
  font-size: var(--c2-text);
}
.concept-c02 > .c2-detail::-webkit-scrollbar { display: none; }
/* AN EMPTY SLOT COLLAPSES. It used to reserve its 44px whatever happened, on the
   argument that a page which never shifts is worth a band of nothing — and on a
   screen with a single view (Contacts, Rank, Alliance, Profile today) that is a
   dead strip under the category strip for the whole visit, which is real estate
   this layout exists to give back. The slot is still in the DOM and still the
   shell's one node; it simply has no height until a screen fills it, and
   `:empty` is exactly that question asked of the DOM rather than of a flag
   somebody has to remember to set. */
.concept-c02 > .c2-detail:empty { display: none; }
.concept-c02 .c2-detail__item {
  display: inline-flex;
  flex: 1 0 auto;
  align-items: center;
  justify-content: center;
  min-height: var(--c2-strip-h);
  /* A three-letter section word ("All") measured 41px wide — under the 44px tap
     floor every other control in this layout keeps. The strip's items grow to
     fill the row anyway; this only bites on a strip with more words than fit. */
  min-width: var(--tap);
  padding: 0 .7rem;
  border-radius: var(--c2-rad);
  color: var(--c2-ink-3);
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}
.concept-c02 .c2-detail__item:hover { color: var(--c2-ink); text-decoration: none; }
/* THE FILL IS THE DEEP ACCENT, NOT THE ACCENT, and the reason is a measurement.
   The pack draws this as a filled accent rectangle with dark ink, which is what
   it looks like in the palette it was drawn in (Mint: a pale mint fill). In the
   game's own Astral scheme the accent is #2e75f2 and the ink core/colours.js
   picks for it is white — 4.25:1, under the 4.5 WCAG asks for at 13px, and the
   render harness reports it on every single route. The deep accent with the
   page's own ink clears it comfortably in every scheme, and the inset ring
   keeps the shape reading as a filled rectangle rather than as plain text.
   `box-shadow` and not `border`: a border would make the item 46px and the
   band 47. */
.concept-c02 .c2-detail__item[aria-current="page"] {
  background: var(--c2-accent-deep);
  color: var(--c2-ink);
  box-shadow: inset 0 0 0 1px var(--c2-accent);
}
/* The fallback strip, for a c02 screen rendered where the shell's slot does not
   exist. Same paint, placed in the page instead of in the chrome. */
.concept-c02 .c2-detail--inline {
  margin: 0 0 var(--c2-gap);
  border-bottom: 1px solid var(--c2-rule);
}

/* ---------- 7. The screen ------------------------------------------------- */

/* The only thing on the page that scrolls — and it has a FLOOR. A flex item
   refuses to shrink below its content unless min-height says otherwise, and `0`
   sets no floor either: at 400% zoom or in a split-screen window the screen was
   squeezed to a slit under the chrome. 40vh lifts the shrink-to-content
   restriction AND says where the shrinking stops; past that point the frame
   overflows and the PAGE scrolls, which section 2 deliberately keeps possible. */
.concept-c02 > main.game-main {
  flex: 1 1 auto;
  min-height: 40vh;
  width: 100%;
  max-width: none;
  margin: 0;
  padding: var(--c2-gap) var(--c2-gutter) calc(var(--c2-gutter) * 2);
  overflow-y: auto;
  overflow-x: hidden;
  /* `contain`, not `none`: the platform rubber-band at the end of a list is the
     phone telling the player they are at the bottom, and fighting it is what
     makes a web app feel wrong. This only stops the scroll CHAINING out. */
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  scroll-padding-top: .5rem;
  /* THE BASE SIZE AND THE BASE LEADING, on the scroller, so everything inside it
     inherits both. game.css sets the page to 15px/1.5; at that leading a two-line
     row is 45px of text before any padding, which is most of why this layout was
     not reading as denser than the others. 13px/1.3 is the pack's own body size
     and the density the whole concept is for.
     It reaches the CLASSIC screens rendered inside this frame too, which is
     deliberate — they should be as dense here as the redrawn ones — and §7a
     below pays back the one thing that costs: a classic control sized in `em`
     that would fall under the 44px floor. */
  font-size: var(--c2-text);
  line-height: 1.3;
}
.concept-c02 .c2-screen { font-size: var(--c2-text); line-height: 1.3; }

/* ---------- 7a. Undoing the Command preset, inside this scope only --------- */

/* THE CONSOLE VOICE IS NOT THIS LAYOUT'S VOICE. preset-command.css uppercases
   and letter-spaces nine families of text — both tab strips, h1/h2/h3, .btn,
   .pill, .kv labels, table headers and the map key. Uppercase costs ~18% of the
   width of every word it touches and it is exactly the width this layout does
   not have: measured at 390px the five Empire categories needed 428px of a
   390px strip and scrolled, and the empire's own name read as a banner.
   So it is undone here, selector for selector against that file, and NOWHERE
   ELSE: the rules below are all inside .concept-c02, every one of them is a
   pair of the same two declarations, and preset-command.css is untouched — a
   Command player on any other layout sees exactly what they saw. This file
   loads after it, so each pair wins its tie on order rather than on
   specificity. */
.concept-c02 > .tabbar a,
.concept-c02 > .sub-tabbar a,
.concept-c02 h1,
.concept-c02 h2,
.concept-c02 h3,
.concept-c02 .card > h2,
.concept-c02 details.card--fold > summary h2,
.concept-c02 .card h3,
.concept-c02 .kv dt,
.concept-c02 .pill,
.concept-c02 .btn,
.concept-c02 .tbl th,
.concept-c02 .map-key > summary {
  text-transform: none;
  letter-spacing: normal;
}

/* ---------- 7b. A classic control must still be a 44px target -------------- */

/* The base size above is 13px against game.css's 15px, and a control whose
   height comes from its own text shrinks with it. Measured at 390px on the
   Diplomacy standings, the seven empire buttons went from 44px to 42px — real
   tap targets lost to a layout that was only supposed to move the furniture.
   A floor on the two classic controls that are sized that way costs nothing to
   anything already taller. */
.concept-c02 > main.game-main button.name-link,
.concept-c02 > main.game-main a.name-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
}
/* The map sizes its stage as "the viewport, less what is above me". Here <main>
   already IS what is left over, so the stage is all of it. ui/map.js is
   untouched and its ResizeObserver redraws the canvas when the box changes. */
/* A WIDE ROUTE'S SCROLLER CARRIES NO GUTTERS, so a chart can run to both edges
   of the phone — that is what `wide` is for (app.js TABS marks the Map tab).
   The CARDS on such a route still want their 10px, so the gutters move one box
   in, onto the screen's own wrapper; a chart cancels them again with .c2-chart
   --bleed. Nothing else changes: a screen that does not use .c2-screen (the
   classic map, today) gets the same edge-to-edge box it always had. */
.concept-c02 > main.game-main.wide {
  display: flex;
  flex-direction: column;
  padding: 0;
}
.concept-c02 > main.game-main.wide > .c2-screen {
  padding: var(--c2-gap) var(--c2-gutter) calc(var(--c2-gutter) * 2);
}
.concept-c02 .map-shell { flex: 1 1 auto; height: auto; min-height: 0; }
/* The designer's vitals bar sticks under the standard shell's measured chrome.
   Here the chrome overlaps nothing, so the offset is the top of the scroller. */
.concept-c02 .design-vitals { top: 0; }

.concept-c02 .c2-screen { display: flex; flex-direction: column; gap: var(--c2-gap); }
/* The sheet is in the TOP LAYER and mounted inside #app (kit.js sheet()) rather
   than on <body>, because every rule this file writes is scoped under the class
   #app carries — a dialog parented to <body> would paint as an unstyled slab. */

/* ---------- 8. Tier 1 — the bottom bar ------------------------------------ */

/* The five groups, at the bottom, where the thumb already is. The same node
   app.js builds for the top strip, re-parented — the shell does not build a
   second one.

   WHAT CLEARS THE GESTURE BAR: index.html's viewport meta has no
   `viewport-fit`, so the browser lays the page out inside the safe area and
   env(safe-area-inset-bottom) resolves to 0 on every device; max() picks the
   padding and the env() is inert. It is kept because it is what makes this bar
   correct on the day the client adopts `viewport-fit=cover`. */
.concept-c02 > .tabbar {
  position: static;
  top: auto;
  gap: 0;
  overflow: hidden;
  padding: 1px 2px max(2px, env(safe-area-inset-bottom));
  background: var(--c2-band);
  border-top: 1px solid var(--c2-rule);
  border-bottom: none;
  backdrop-filter: none;
  flex-wrap: wrap;
}
/* Declared on the frame AND on the toast stack in one rule, because #toasts is
   a SIBLING of #app (index.html) and inherits nothing from it — a token the
   toasts cannot see is how their clearance over the bar goes stale. */
.concept-c02,
.concept-c02 ~ #toasts { --c2-bar-tap: 3.25rem; }
.concept-c02 > .tabbar a {
  flex: 1 1 0;
  flex-basis: 8ch;
  min-width: 0;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  min-height: var(--c2-bar-tap);
  padding: 4px 2px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: normal;
  line-height: 1.1;
  border-radius: var(--c2-rad);
  border-bottom: none;
  color: var(--c2-ink-3);
}
.concept-c02 > .tabbar a img { width: 20px; height: 20px; }
/* The label shortens from the end instead of being sliced at both: equal slots
   mean a fixed width per label, and centred text under `overflow: hidden` on
   the anchor loses BOTH ends and stops being a word. */
.concept-c02 > .tabbar a .tabbar-label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.concept-c02 > .tabbar a[aria-current="page"] {
  background: var(--c2-accent-dim);
  color: var(--tab-current, var(--c2-accent-lit));
}
.concept-c02 > .tabbar a[aria-current="page"] img { opacity: 1; }
/* Toasts clear the bar through the bar's OWN height token, so growing the bar
   cannot bury them in it. */
.concept-c02 ~ #toasts { bottom: calc(var(--c2-bar-tap) + .9rem + env(safe-area-inset-bottom)); }

/* ---------- 9. The kit ---------------------------------------------------- */
/* Everything below paints a primitive in ui/concept/c02/kit.js. A c02 SCREEN
   writes no CSS and invents no class: if a screen needs something that is not
   here, the kit grows a builder and this file grows its rules.
   tests/concept_c02_kit.test.js walks every c02 module for `c2-` tokens and
   fails on one with no rule. */

/* 9a. Sections ------------------------------------------------------------- */

.concept-c02 .c2-section {
  background: var(--c2-panel);
  border: 1px solid var(--c2-rule);
  border-radius: var(--c2-rad);
  overflow: hidden;
}
.concept-c02 .c2-section__head {
  display: flex;
  align-items: center;
  gap: .3rem;
  min-height: 28px;
  padding: 0 var(--c2-pad);
  border-bottom: 1px solid var(--c2-rule-soft);
}
.concept-c02 .c2-section__title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--c2-ink);
}
.concept-c02 .c2-section__count { color: var(--c2-ink-4); font-size: var(--c2-text-sm); }
.concept-c02 .c2-section__spacer { flex: 1 1 auto; min-width: 0; }
.concept-c02 .c2-section__aside {
  display: inline-flex;
  align-items: center;
  gap: .15rem;
  min-height: var(--tap);
  padding: 0 .25rem;
  background: none;
  border: 0;
  color: var(--c2-accent-lit);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.concept-c02 .c2-section__aside:hover { text-decoration: none; color: var(--c2-accent); }
.concept-c02 .c2-section__body { display: flex; flex-direction: column; }

/* A SUB-HEADING INSIDE THE BODY (kit-10). The colony Overview's storage block
   holds a table and then the settings that govern it, and the line between them
   used to be a 44px row with a gear in the icon column — the same shape and
   weight as the settings rows under it, competing with them instead of
   introducing them. This is a 24px label in the muted ink the TABLE HEAD uses,
   with the same hairline over it, so the two groups read as two groups and only
   the rows are tappable. */
.concept-c02 .c2-subhead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 .4rem;
  min-height: 24px;
  padding: 6px var(--c2-pad) 2px;
  border-top: 1px solid var(--c2-rule);
}
.concept-c02 .c2-section__body > .c2-subhead:first-child { border-top: 0; }
.concept-c02 .c2-subhead__title {
  color: var(--c2-ink-4);
  font-size: var(--c2-text-sm);
  font-weight: 600;
  line-height: 1.3;
}
.concept-c02 .c2-subhead__sub {
  min-width: 0;
  color: var(--c2-ink-4);
  font-size: var(--c2-text-sm);
  font-weight: 400;
  line-height: 1.3;
}
/* The row straight after a sub-heading does not draw a second rule on top of
   the sub-heading's own. */
.concept-c02 .c2-subhead + .c2-row { border-top: 0; }

/* 9b. Rows ----------------------------------------------------------------- */

.concept-c02 .c2-row {
  display: flex;
  align-items: center;
  gap: var(--c2-pad);
  width: 100%;
  min-height: var(--tap);
  padding: 2px var(--c2-pad);
  background: none;
  border: 0;
  border-top: 1px solid var(--c2-rule-soft);
  color: var(--c2-ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.concept-c02 .c2-section__body > .c2-row:first-child,
.concept-c02 .c2-section__body > .c2-job:first-child .c2-row { border-top: 0; }
.concept-c02 .c2-row:hover { background: var(--c2-panel-2); text-decoration: none; }
/* YOUR OWN ROW IS MARKED without spending the tone slot: the raised ground and
   a hairline in the accent down the leading edge. `aria-current="page"` is
   what row({ current: true }) already writes; it just painted nothing. */
.concept-c02 .c2-row[aria-current="page"],
.concept-c02 .c2-row__open[aria-current="page"] {
  background: var(--c2-panel-2);
  box-shadow: inset 2px 0 0 var(--c2-accent);
}
.concept-c02 .c2-row--static { cursor: default; }
.concept-c02 .c2-row--static:hover { background: none; }
/* THE TITLE COLUMN KEEPS TWO FIFTHS OF THE ROW, WHATEVER THE META ASKS. With
   `min-width: 0` it gave way to a meta column whose own floor is its widest
   fact, and on the live shard a colony row carrying "Construction: Habitat
   Block · Shipyard: Ashfall Lance Destroyer I" as ONE fact printed its name a
   letter per line down a 12px column. The meta's cap is 40% (58% with an
   action beside its facts), so 40% here is the share it can never take. */
.concept-c02 .c2-row__main { flex: 1 1 auto; min-width: 40%; display: flex; flex-direction: column; }
.concept-c02 .c2-row__title {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--c2-ink);
  overflow-wrap: anywhere;
}
/* A row whose title NAMES something you can open. Long names still wrap —
   identities stay whole, which is the pack's rule. */
.concept-c02 .c2-row--entity .c2-row__title { color: var(--c2-accent-lit); }
/* IT WRAPS; IT IS NOT CLIPPED. The obvious fix for a report message that runs
   to a paragraph is one ellipsised line — and it is the wrong one: an ellipsis
   is still text the player cannot read, the render harness counts it as clipped
   (layoutFacts clippedText reports `ellipsis: true` and still reports it), and
   this layout's acceptance is zero. So the SHORTENING happens in the model, on
   a word boundary, where it can be tested — and what reaches the page is short
   enough to wrap into two lines at 360px. */
.concept-c02 .c2-row__sub {
  min-width: 0;
  color: var(--c2-ink-3);
  font-size: var(--c2-text-sm);
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.concept-c02 .c2-row__meta {
  /* NEVER SQUEEZED BELOW ONE WHOLE FACT — and never wider than 40% of the row.
     `min-width: min-content` is the floor and it is the exact one: the widest
     single `.c2-row__fact`, which is nowrap, so the column can give way until
     "Cargo 1,204/6,000 t" would have to break and then stops. Below that floor
     a one-word status used to end up stacked a letter at a time; above the cap
     the MAIN column is the one that pays.

     THE CAP IS 40%, NOT 58%, AND THE ROW'S HEIGHT IS WHY. Both columns wrap, so
     the width is a trade between them — and they do not cost the same. A fleet
     carrying cargo has three facts, which at 58% took 205px and left the place
     line 99px: the meta fitted on two lines and the sub needed three, so a row
     that is meant to be 44–56px measured 68px, or 83px with a tag pill on it.
     At 40% the same row is 53px: the meta wraps to two right-aligned lines
     inside its own column, the place line gets 163px and needs two, and the
     tallest column is one line shorter than it was. Measured at 390px on the
     fleets list, not reasoned about.

     A FLEX ROW, ALWAYS — not only when it carries two kinds of thing. It used
     to be an inline box with a `:has()` rule for the both-at-once case, and when
     that case arrived (a job's inline progress beside its action) the block-level
     meter inside an inline span broke out of the flow and printed the reading
     straight through the words beside it. One layout mode, every time. */
  display: flex;
  align-items: center;
  align-self: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0 .5rem;
  flex: 0 1 auto;
  min-width: min-content;
  max-width: 40%;
  text-align: right;
  color: var(--c2-ink-2);
  font-size: var(--c2-text-sm);
  font-variant-numeric: tabular-nums;
  /* game.css sets `overflow-wrap: break-word` on <body> for prose. In a narrow
     right-hand column that turns a one-word status into "Unre / ad", one letter
     per line at the extreme. This column breaks between facts or not at all. */
  overflow-wrap: normal;
  word-break: normal;
}
/* A READING PLUS A WAY IN is a different shape from a set of figures, and it
   keeps the wider cap. The both-at-once form is exactly two items — a job's
   inline progress and its "Build" — and neither of them wraps, so narrowing
   this slot does not save the row a line; it only puts the action underneath
   its own reading. The cap above is for the case that DOES wrap. */
.concept-c02 .c2-row__meta:has(.c2-row__facts) { max-width: 58%; }
/* One served fact, kept whole. The column wraps between facts, never inside
   "180/600 t". */
.concept-c02 .c2-row__fact { white-space: nowrap; }
/* A fact LONGER THAN A FIGURE is a phrase — "Shipyard: Ashfall Lance Destroyer
   I" — and a phrase may break at its spaces. Kept nowrap, its full width was
   the meta column's floor (`min-width: min-content`), and the floor beat the
   40% cap. kit metaParts marks any string fact past 24 characters. */
.concept-c02 .c2-row__fact--long { white-space: normal; text-align: right; }
/* The row's action, in the accent: "Open build", "Shipyard", "View orders". */
.concept-c02 .c2-row__action { color: var(--c2-accent-lit); font-weight: 600; white-space: nowrap; }
/* The reading half of a both-at-once meta slot. inline-flex so a block-level
   meter inside it stays inside it. */
.concept-c02 .c2-row__facts { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; min-width: 0; gap: 0 .5rem; } /* the gap IS the separator: a glyph would start or end a wrapped line */ /* the separator's space is the gap — without it a { facts, action } meta printed "New· Still there" */
.concept-c02 .c2-row--ok .c2-row__sub { color: var(--c2-ok); }
.concept-c02 .c2-row--warn .c2-row__sub { color: var(--c2-warn); }
.concept-c02 .c2-row--bad .c2-row__sub { color: var(--c2-bad); }
.concept-c02 .c2-row--info .c2-row__sub { color: var(--c2-info); }
.concept-c02 .c2-row--neutral .c2-row__sub { color: var(--c2-ink-3); }

/* A ROW WITH TWO ANSWERS IN IT. Where a row is handed a trailing action the row
   itself is the container — a plain div — and the thing you OPEN and the thing
   you DO sit side by side inside it, each a real 44px target and neither inside
   the other. The container gives up its own block padding so both children can
   claim the tap floor without the row growing past it, and it gives up its own
   hover so the two halves light separately: a player has to be able to see
   which of the two they are about to hit. */
.concept-c02 .c2-row--split {
  padding-block: 0;
  gap: 0;
  cursor: default;
}
.concept-c02 .c2-row--split:hover { background: none; }
.concept-c02 .c2-row__open {
  display: flex;
  align-items: center;
  gap: var(--c2-pad);
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap);
  padding-block: 2px;
  padding-right: var(--c2-pad);
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.concept-c02 .c2-row__open:hover { background: var(--c2-panel-2); text-decoration: none; }
.concept-c02 .c2-row--static .c2-row__open { cursor: default; }
.concept-c02 .c2-row--static .c2-row__open:hover { background: none; }
/* The trailing control. A rule between the two halves rather than a button
   outline: the row is already a bordered band and a second box inside it reads
   as a card in a card. It shrinks before the main column does NOT — `flex: 0 1
   auto` with the tap floor as its own minimum — and a long label wraps inside
   it rather than pushing the title off the row. */
.concept-c02 .c2-row__do {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .25rem;
  flex: 0 1 auto;
  min-height: var(--tap);
  min-width: var(--tap);
  max-width: 40%;
  padding: 0 var(--c2-pad);
  background: none;
  border: 0;
  border-left: 1px solid var(--c2-rule-soft);
  color: var(--c2-accent-lit);
  font: inherit;
  font-size: var(--c2-text-sm);
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
}
.concept-c02 .c2-row__do:hover { background: var(--c2-panel-2); text-decoration: none; }
.concept-c02 .c2-row__do-label { min-width: 0; overflow-wrap: anywhere;
  /* A trailing action's label never wraps — 'Upgr/ade' at 390px is a defect, not
     a layout. A label that does not fit is a label to shorten at the call site. */
  white-space: nowrap;
}

.concept-c02 .c2-glyph { flex: none; opacity: .9; }
.concept-c02 .c2-chev { flex: none; color: var(--c2-ink-4); font-size: 1.1em; line-height: 1; }
.concept-c02 .c2-ext { color: var(--c2-accent-lit); font-size: .85em; margin-left: .15rem; }
/* `pre`, so the spaces around a separator survive inside a nowrap fact span —
   el() appends its children with no whitespace between them, so the only spaces
   are the ones written into the node itself. */
.concept-c02 .c2-sep { color: var(--c2-ink-4); white-space: pre; }


/* 9c. Data lines ----------------------------------------------------------- */

.concept-c02 .c2-data {
  display: flex;
  align-items: baseline;
  gap: var(--c2-pad);
  min-height: var(--c2-data-h);
  padding: 2px var(--c2-pad);
  border-top: 1px solid var(--c2-rule-soft);
  line-height: 1.3;
  font-variant-numeric: tabular-nums;
}
.concept-c02 .c2-section__body > .c2-data:first-child { border-top: 0; }
/* WHICH HALF GIVES WAY, and it is not the label. `flex: none` on the value meant
   a long one — a served sentence, a name, "Waiting · Ore short 200 t" — held its
   full width and squeezed the label to one letter per line, then ran off the
   card. The value is the elastic half now and wraps inside its own column; the
   label holds its content width up to half the row, which is what keeps the two
   readable against each other down a card. */
.concept-c02 .c2-data__label {
  flex: 0 0 auto;
  max-width: 50%;
  min-width: 0;
  color: var(--c2-ink-3);
  overflow-wrap: anywhere;
}
.concept-c02 .c2-data__value {
  flex: 0 1 auto;
  min-width: 0;
  margin-left: auto;
  text-align: right;
  color: var(--c2-ink);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  overflow-wrap: anywhere;
}
/* THE SUB IS ELASTIC TOO, and `flex: none` on it was the root cause of a bug
   fixed twice at the call site. A data row is three items on one line — label,
   value, sub — and with the sub refusing to shrink, a SERVED SENTENCE in it
   ("a yellow star, the system's own", the Designer's Warp and Fuel lines) held
   its full width and squeezed the two beside it to nothing: the map's Star line
   rendered one letter per line. The label keeps its 50% cap, the value and the
   sub both give way, and a long sub wraps inside its own column. */
.concept-c02 .c2-data__sub {
  flex: 0 1 auto;
  min-width: 0;
  color: var(--c2-ink-4);
  font-size: var(--c2-text-sm);
  overflow-wrap: anywhere;
}
.concept-c02 .c2-data--ok .c2-data__value { color: var(--c2-ok); }
.concept-c02 .c2-data--warn .c2-data__value { color: var(--c2-warn); }
.concept-c02 .c2-data--bad .c2-data__value { color: var(--c2-bad); }
.concept-c02 .c2-data--info .c2-data__value { color: var(--c2-info); }
.concept-c02 .c2-data--neutral .c2-data__value { color: var(--c2-ink); }

/* 9d. Tables --------------------------------------------------------------- */

.concept-c02 .c2-table-wrap { padding: 0 0 2px; }
.concept-c02 .c2-table__caption {
  padding: 2px var(--c2-pad);
  color: var(--c2-ink-3);
  font-size: var(--c2-text-sm);
  line-height: 1.3;
}
.concept-c02 .c2-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--c2-text);
  font-variant-numeric: tabular-nums;
}
.concept-c02 .c2-table thead th { height: 26px; }
.concept-c02 .c2-table tbody th,
.concept-c02 .c2-table tbody td { height: 28px; }
.concept-c02 .c2-table th,
.concept-c02 .c2-table td {
  padding: 4px 6px;
  text-align: left;
  /* The label column can be two 44px links tall (a colony and its system); the
     figures belong to the whole row, so they sit against its middle rather than
     against the top of a cell they are not part of. */
  vertical-align: middle;
  font-weight: 400;
  color: var(--c2-ink);
  line-height: 1.25;
}
.concept-c02 .c2-table thead th {
  color: var(--c2-ink-4);
  font-size: var(--c2-text-sm);
  font-weight: 600;
  border-bottom: 1px solid var(--c2-rule);
  white-space: nowrap;
}
.concept-c02 .c2-table tbody th { color: var(--c2-ink); font-weight: 600; }
.concept-c02 .c2-table tbody tr + tr th,
.concept-c02 .c2-table tbody tr + tr td { border-top: 1px solid var(--c2-rule-soft); }
.concept-c02 .c2-table th:first-child,
.concept-c02 .c2-table td:first-child { padding-left: var(--c2-pad); }
/* A LABEL CELL THAT HOLDS A LINK PAYS THE 44px FLOOR, AND THE ROW PAYS NOTHING
   MORE FOR IT. 4px above and below a 44px target makes a 52px row; on the label
   column — the only one that ever holds a control — the block padding comes off
   so the row is 48px and the figures still sit clear of the rules. */
.concept-c02 .c2-table tbody th:first-child { padding-block: 2px; }
.concept-c02 .c2-table th:last-child,
.concept-c02 .c2-table td:last-child { padding-right: var(--c2-pad); }
.concept-c02 .c2-num { text-align: right; white-space: nowrap; }
/* A FIGURE COLUMN IS RIGHT-ALIGNED, AND ITS HEADER WITH IT (kit-10).
   `.concept-c02 .c2-num` (0,2,0) lost `text-align: right` to
   `.concept-c02 .c2-table th, .concept-c02 .c2-table td { text-align: left }`
   (0,2,1), so EVERY figure column in Concept 02 was left-aligned — measured with
   getComputedStyle in Chrome, `text-align: left` on every `.c2-num` cell and
   every numeric `<th>` of the colony resource table. Restated here at (0,3,0),
   which beats the type-carrying rule; the stacked layout (section 11) restates
   `text-align: left` at (0,3,1), so a FOLDED row is untouched — its figures are
   `label value` flex items and their alignment is the flex column's. */
.concept-c02 .c2-table .c2-num { text-align: right; }
/* THE EXPLICIT ESCAPE HATCH. A column declared `wrap: true` is never a figure
   column and breaks long words as well — the shape a served identifier, a URL or
   a German compound needs so it cannot push the table past its own card. */
.concept-c02 .c2-wrap {
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: left;
}
/* The label column takes a share and wraps; the figure columns are sized by
   their own headers, which is what keeps "Organics" from being squeezed out by
   a long colony name. */
.concept-c02 .c2-table th:first-child { width: 30%; }
.concept-c02 .c2-table__row:hover { background: var(--c2-panel-2); }
.concept-c02 .c2-section__foot,
.concept-c02 .c2-table__foot {
  margin: 0;
  padding: 2px var(--c2-pad) 4px;
  color: var(--c2-ink-4);
  font-size: var(--c2-text-sm);
  line-height: 1.3;
}
.concept-c02 .c2-cell { display: inline-flex; flex-direction: column; align-items: inherit; }
.concept-c02 .c2-num .c2-cell { align-items: flex-end; }
/* A SUB-FACT UNDER A FIGURE IS A FACT, NOT AN ALARM (kit-10). This was
   hard-coded `var(--c2-warn)`, so `T2 → T3 cap 41` and `200 reserved` — a row
   with nothing wrong with it — rendered in the amber this layout uses for
   trouble (measured rgb(232, 163, 61) on an untoned cell). The default is the
   muted ink; a sub-fact inside a TONED cell inherits that cell's tone instead,
   which is what keeps `near cap` amber all the way down. */
.concept-c02 .c2-cell__sub { color: var(--c2-ink-3); font-size: var(--c2-text-sm); }
/* The per-cell label only appears in the stacked layout (section 11). */
.concept-c02 .c2-cell__label { display: none; }
.concept-c02 .c2-cell--ok { color: var(--c2-ok); }
.concept-c02 .c2-cell--warn { color: var(--c2-warn); }
.concept-c02 .c2-cell--bad { color: var(--c2-bad); }
.concept-c02 .c2-cell--info { color: var(--c2-info); }
.concept-c02 .c2-cell--neutral { color: var(--c2-ink); }
/* A TONED TABLE CELL IS ACTUALLY TONED (kit-10). kit.table() puts the tone on
   the `<td>`/`<th>` itself, and the five rules above (0,2,0) lost `color` to
   `.concept-c02 .c2-table td` / `.concept-c02 .c2-table tbody th` (0,2,1 and
   0,2,2): a `near cap` cell computed rgb(234, 241, 255) — plain ink — on every
   table in this layout, not just the colony's. Restated inside the table at
   (0,3,0) and (0,4,0) for the sub-fact, which is the smallest thing that wins
   without inventing a class name or moving the tone onto the inner span. */
.concept-c02 .c2-table .c2-cell--ok { color: var(--c2-ok); }
.concept-c02 .c2-table .c2-cell--warn { color: var(--c2-warn); }
.concept-c02 .c2-table .c2-cell--bad { color: var(--c2-bad); }
.concept-c02 .c2-table .c2-cell--info { color: var(--c2-info); }
.concept-c02 .c2-table .c2-cell--neutral { color: var(--c2-ink); }
.concept-c02 .c2-table .c2-cell--ok .c2-cell__sub,
.concept-c02 .c2-table .c2-cell--warn .c2-cell__sub,
.concept-c02 .c2-table .c2-cell--bad .c2-cell__sub,
.concept-c02 .c2-table .c2-cell--info .c2-cell__sub,
.concept-c02 .c2-table .c2-cell--neutral .c2-cell__sub { color: inherit; }
.concept-c02 .c2-restable { display: block; }

/* 9e. Meters, tiles, progress ---------------------------------------------- */

/* A METER IS NEVER A BARE BAR. The track is 4px; the numbers and the percentage
   are printed beside it, always, because a bar alone says nothing to anybody
   who cannot read its colour or its length. */
/* IT CARRIES NO MINIMUM OF ITS OWN. The track used to floor at 40px and every
   part was `flex: none`, so a meter's min-content was 157px — three of them
   across a 390px row is 120px per column, and the row overflowed sideways,
   which this layout's acceptance counts as a defect. The TRACK is the elastic
   part now (it can shrink to nothing and still be a bar), the numbers hold their
   own width, and the component as a whole shrinks to fit whatever box it is put
   in: a tile, a table cell, a row's meta slot. */
.concept-c02 .c2-meter {
  display: flex;
  align-items: center;
  gap: .35rem;
  min-width: 0;
  padding: 2px var(--c2-pad);
  font-size: var(--c2-text-sm);
  font-variant-numeric: tabular-nums;
}
.concept-c02 .c2-meter__label { flex: 0 1 auto; min-width: 0; color: var(--c2-ink-3); }
.concept-c02 .c2-meter__track {
  flex: 1 1 0;
  min-width: 0;
  height: 4px;
  border-radius: 2px;
  background: var(--c2-rule-lit);
  overflow: hidden;
}
.concept-c02 .c2-meter__fill { display: block; height: 100%; background: var(--c2-accent); }
.concept-c02 .c2-meter__text {
  flex: none;
  color: var(--c2-ink-2);
  font-size: var(--c2-text-sm);
  font-variant-numeric: tabular-nums;
}
.concept-c02 .c2-meter__pct {
  flex: none;
  color: var(--c2-ink-4);
  font-size: var(--c2-text-sm);
  font-variant-numeric: tabular-nums;
}
.concept-c02 .c2-meter--ok .c2-meter__fill { background: var(--c2-ok); }
.concept-c02 .c2-meter--warn .c2-meter__fill { background: var(--c2-warn); }
.concept-c02 .c2-meter--bad .c2-meter__fill { background: var(--c2-bad); }
.concept-c02 .c2-meter--info .c2-meter__fill { background: var(--c2-info); }
.concept-c02 .c2-meter--neutral .c2-meter__fill { background: var(--c2-ink-4); }

/* THREE TILES FIT ACROSS 390px, and that is the number the strip exists for
   (Shield · Armor · Hull, Cargo · Fuel · Mass). The content box inside a section
   is 354px, so three columns plus two 1px rules is 117px each — the track
   `minmax(100px, 1fr)` leaves the grid room to put four across a wider card and
   two across a 320px one, and never forces a fourth into 88px. */
.concept-c02 .c2-tiles {
  font-variant-numeric: tabular-nums;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 1px;
  /* The hairlines between tiles are each tile's own outline, not the container's
     background showing through the gap — that painted every cell the item count
     did not fill as a solid block of rule colour (four tiles in three columns left
     a 236×40px slab on Profile). The outline overlaps the 1px gap exactly. */
  background: transparent;
}
.concept-c02 .c2-tile { outline: 1px solid var(--c2-rule-soft); outline-offset: 0; }
.concept-c02 .c2-tile {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  min-width: 0;
  padding: 4px 6px;
  background: var(--c2-panel);
}
/* A LABEL IS NEVER ELLIPSISED. "Colonists", "Organics", "Fuel tank" are the
   whole point of the tile, and an ellipsis is text the player cannot read — the
   render harness counts it as clipped for exactly that reason. Two lines inside
   the tile instead; the grid row takes its height from the tallest tile, so the
   strip stays level. */
.concept-c02 .c2-tile__label {
  color: var(--c2-ink-3);
  font-size: var(--c2-text-sm);
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.concept-c02 .c2-tile__value {
  color: var(--c2-ink);
  font-size: var(--c2-text);
  font-weight: 600;
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.concept-c02 .c2-tile .c2-meter { padding: 0; min-width: 0; }

.concept-c02 .c2-progress { display: flex; flex-direction: column; }
.concept-c02 .c2-progress__text {
  padding: 0 var(--c2-pad) 2px;
  color: var(--c2-ink-2);
  font-size: var(--c2-text-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* THE INLINE READING: `1 / 4 ticks [====----] 25%` on one line, sized to sit in
   a row's meta slot beside its action. The meter's own padding goes — it is no
   longer a band of its own — and the track takes a fixed share so the numbers
   either side never move as the bar fills. */
.concept-c02 .c2-progress--inline {
  flex-direction: row;
  align-items: center;
  gap: .3rem;
}
.concept-c02 .c2-progress--inline .c2-progress__text { padding: 0; }
.concept-c02 .c2-progress--inline .c2-meter { padding: 0; flex: 0 1 auto; }
.concept-c02 .c2-progress--inline .c2-meter__track { flex: 0 0 34px; min-width: 34px; }

/* 9f. The entity header ---------------------------------------------------- */

/* 44px OF TITLE ROW AND 26px OF LINKS, AND NOT A PIXEL MORE. It was two full
   44px bands plus its own padding — 94px on a colony page, 110px once the links
   wrapped — for a header whose job is to say which entity you are looking at.
   The title row keeps its height because it holds two real controls (the back
   link and the picker); the links row does not hold one that needs a band of its
   own, so it buys its 44px targets with padding and gives the space back with a
   negative margin, exactly as game.css does for the alert strip's links. The
   card's own block padding goes with it: each row carries what it needs. */
.concept-c02 .c2-entity {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0 var(--c2-pad);
  background: var(--c2-panel);
  border: 1px solid var(--c2-rule);
  border-radius: var(--c2-rad);
}
/* THE ROW WRAPS INSTEAD OF SQUEEZING. With the picker capped at 45% and the
   title at min-width 0 on a single-line flex row, a 34-character name beside
   the picker broke mid-word ("Ironre / ach", "Ashfal / l / Lance") — the title
   column had been shrunk below the width of its own words. Now the row wraps:
   flex lays items out by their natural width, so a name that does not fit
   beside the picker takes the line and the picker goes under it, while a
   short title ("Planet 1") still shares one 44px line with the picker and the
   right-hand fact. The picker's basis is 8rem — small enough to sit beside a
   short title, and it grows into whatever its line has left. */
.concept-c02 .c2-entity__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem;
  min-height: var(--tap);
}
.concept-c02 .c2-entity__back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--tap);
  height: var(--tap);
  margin-left: -.35rem;
  color: var(--c2-accent-lit);
  font-size: 1.4rem;
  line-height: 1;
}
.concept-c02 .c2-entity__back:hover { text-decoration: none; }
.concept-c02 .c2-entity__title {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  font-size: 18px;
  line-height: 1.2;
  font-weight: 600;
  color: var(--c2-ink);
  overflow-wrap: anywhere;
}
.concept-c02 .c2-entity__picker {
  flex: 1 1 8rem;
  min-width: var(--tap);
  min-height: var(--tap);
  max-width: 100%;
  padding: 0 .4rem;
  background: var(--c2-panel-2);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad);
  color: var(--c2-ink);
  font-size: var(--c2-text);
}
.concept-c02 .c2-entity__right {
  flex: none;
  color: var(--c2-ink-2);
  font-size: var(--c2-text-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.concept-c02 .c2-entity__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 .25rem;
  /* The margin is the other half of the trick below: it is exactly the overflow
     the links' negative margin gives back at the TOP, so their boxes start where
     this row starts and nothing in the title row above is ever overlapped. What
     bleeds is the bottom 14px, into the card's border and the 6px gap under it —
     a band with nothing in it to mis-tap. */
  margin-top: 14px;
  padding-bottom: 0;
  font-size: var(--c2-text-sm);
  line-height: 1.3;
}
/* A 44px TARGET IN A 26px BAND. The link's own `min-height: var(--tap)` would
   make this row a third 44px band; padding buys the same box and the negative
   margin hands the layout back. Derived from the line box, so it is 44px at any
   text size: 12px x 1.3 is 15.6, and 15.6 + 2 x 14.2 = 44. */
.concept-c02 .c2-entity__links .c2-link {
  min-height: 0;
  padding-block: calc((var(--tap) - 1.3em) / 2);
  margin-block: calc((1.3em - var(--tap)) / 2);
}
.concept-c02 .c2-entity__fact { color: var(--c2-ink-3); }
.concept-c02 .c2-entity__sub { margin: 0; color: var(--c2-ink-4); font-size: var(--c2-text-sm); }

/* 9g. Buttons and actions -------------------------------------------------- */

.concept-c02 .c2-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .2rem;
  min-height: var(--tap);
  min-width: var(--tap);
  padding: 0 .7rem;
  border-radius: var(--c2-rad);
  border: 1px solid transparent;
  background: none;
  color: var(--c2-ink);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
}
.concept-c02 .c2-btn:hover { text-decoration: none; }
/* THE FILLED BUTTON USES THE DEEP ACCENT AND THE PAGE'S OWN INK, and it is the
   same pair the tier-3 active item uses, for the same measured reason: white on
   the Astral accent is 4.25:1, under the 4.5 WCAG asks for at this size, and
   game.css's own .btn--primary carries that pair everywhere. Inside this scope
   the fill is --blue-deep with --text on it, ringed in the accent so it still
   reads as the one filled control on the card. */
.concept-c02 .c2-btn--primary {
  background: var(--c2-accent-deep);
  border-color: var(--c2-accent);
  color: var(--c2-ink);
}
.concept-c02 .c2-btn--secondary {
  border-color: var(--c2-accent);
  color: var(--c2-accent-lit);
}
.concept-c02 .c2-btn--secondary:hover { border-color: var(--c2-accent-lit); }
.concept-c02 .c2-btn--text {
  padding: 0 .25rem;
  color: var(--c2-accent-lit);
  font-weight: 600;
}
/* OFF, it is a greyed word — not a bordered box. `.c2-off` paints a rule-coloured
   border because every OTHER control has one to grey; a text button's border is
   transparent and must stay so, or the refused Resume grows a frame the live
   one never had. */
.concept-c02 .c2-btn--text.c2-off { border-color: transparent; background: none; }
/* ---- The three states a control can be in (kit.js applyGate) -------------- */

/* OFF — the SERVER has refused this order, or the tick lock is on.
   NOT `opacity`. It was `.55`, and opacity is a contrast change wearing a
   costume: every disabled control in the layout measured 3.4–4.1:1 against its
   own card, i.e. unreadable, which makes "why is this off" unanswerable for
   exactly the player who most needs the answer. Quiet COLOURS at full opacity
   say the same thing and stay measurable — the same move the mint kit's cmCta
   made, and the one core/gate.js's own comment argues for. */
.concept-c02 .c2-off {
  cursor: not-allowed;
  color: var(--c2-ink-3);
  border-color: var(--c2-rule);
}
.concept-c02 .c2-btn--secondary.c2-off,
.concept-c02 .c2-action.c2-off,
.concept-c02 .c2-segmented__item.c2-off {
  background: var(--c2-panel-2);
}
/* THE OFF TILE MUST LOOK OFF. `.c2-off` and `.c2-action` are both (0,2,0) and
   `.c2-action` is declared later, so its own `color` and `border` won the tie and
   a switched-off tile painted exactly like a live one — measured 2026-09-16 on
   the fleet screen: the label pixel was rgb(234,241,255) either way, only the
   background differed by four units. Restated at (0,3,0) so the paint agrees
   with the `disabled` attribute the harness and the screen reader already see. */
.concept-c02 .c2-btn--secondary.c2-off,
.concept-c02 .c2-action.c2-off,
.concept-c02 .c2-segmented__item.c2-off {
  color: var(--c2-ink-3);
  border-color: var(--c2-rule);
}
/* A filled control that is off is still filled — it keeps its place in the card
   — but in the page's own quiet surface rather than in the accent. */
.concept-c02 .c2-btn--primary.c2-off,
.concept-c02 .c2-action--primary.c2-off,
.concept-c02 .c2-segmented__item[aria-pressed="true"].c2-off {
  background: var(--c2-panel-3);
  color: var(--c2-ink-3);
  border-color: var(--c2-rule);
}

/* ADVISED — the CLIENT's own prediction, and the owner's ruling is that it goes
   quiet and STAYS PRESSABLE. No `disabled`, no `aria-disabled`: both take the
   control out of the tab order and out of a screen reader's actionable set,
   which is a refusal wearing a visual costume. Muted ink and a soft border say
   "this may be refused"; the sentence under it says why. */
.concept-c02 .c2-advised {
  color: var(--c2-ink-2);
  border-color: var(--c2-rule-soft);
}
.concept-c02 .c2-btn--primary.c2-advised,
.concept-c02 .c2-action--primary.c2-advised {
  background: var(--c2-panel-2);
  color: var(--c2-ink-2);
  border-color: var(--c2-rule-soft);
}
/* The prediction itself, tied to its control by aria-describedby. A sentence,
   not a tooltip: a phone has no hover, which is the whole reason core/gate.js
   stopped relying on `title` alone. */
.concept-c02 .c2-gate-note {
  margin: 0;
  padding: 2px var(--c2-pad) 4px;
  color: var(--c2-warn);
  font-size: var(--c2-text-sm);
  line-height: 1.3;
}
/* An action grid is a grid, so its advised sentences are collected under it
   rather than squeezed into a column. */
.concept-c02 .c2-actions-wrap { display: flex; flex-direction: column; }
.concept-c02 .c2-actions__notes { display: flex; flex-direction: column; }

.concept-c02 .c2-actions {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  padding: 1px;
  background: var(--c2-rule-soft);
}
.concept-c02 .c2-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .3rem;
  min-height: var(--tap);
  padding: 0 .3rem;
  background: var(--c2-panel);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad);
  color: var(--c2-ink);
  font: inherit;
  font-size: var(--c2-text);
  font-weight: 600;
  cursor: pointer;
}
/* THE SAME FILLED PAIR AS .c2-btn--primary, and for the same measured reason:
   white on the Astral accent is 4.25:1 at this size, under the 4.5 WCAG asks
   for, and the render harness reported it on the fleet's action grid. The deep
   accent with the page's own ink clears it in every scheme, and the accent ring
   is what still says "this is the one to press". */
.concept-c02 .c2-action--primary {
  background: var(--c2-accent-deep);
  border-color: var(--c2-accent);
  color: var(--c2-ink);
}
/* IT WRAPS; IT IS NOT ELLIPSISED. "Unload cargo" and "Survey body" are the whole
   of what an action tile says, and `Unload car…` is a control the player has to
   guess at. Two lines at 12px inside the tile, which grows to 52px only when a
   label needs the second one. */
.concept-c02 .c2-action__label {
  min-width: 0;
  font-size: var(--c2-text-sm);
  line-height: 1.2;
  text-align: center;
  overflow-wrap: anywhere;
}
.concept-c02 .c2-action { padding: 4px .3rem; }

.concept-c02 .c2-disclosure { border-top: 1px solid var(--c2-rule-soft); }
.concept-c02 .c2-disclosure__summary {
  display: flex;
  align-items: center;
  gap: .3rem;
  min-height: var(--tap);
  padding: 0 var(--c2-pad);
  color: var(--c2-accent-lit);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
.concept-c02 .c2-disclosure__summary::-webkit-details-marker { display: none; }
.concept-c02 .c2-disclosure__text { flex: 1 1 auto; min-width: 0; }
.concept-c02 .c2-disclosure__body {
  padding: 0 var(--c2-pad) var(--c2-pad);
  color: var(--c2-ink-2);
  font-size: var(--c2-text-sm);
}

/* A NOTE IS PAINTED ON AN OPAQUE SURFACE, and the tone is an EDGE rather than a
   wash. The tints were --c2-*-dim, which are translucent by construction: the
   render harness reported `contrastUnknown` on every note in the layout — "nobody
   can check this", not "this is fine" — and the note's real contrast varied with
   whatever card happened to be behind it. One opaque ground, a 2px rule in the
   tone down its leading edge, and the tone's own ink on the words: measurable,
   and the same wherever the note is placed. */
.concept-c02 .c2-note {
  display: flex;
  align-items: center;
  gap: .35rem;
  margin: 0;
  min-height: var(--c2-data-h);
  padding: 3px var(--c2-pad);
  border-top: 1px solid var(--c2-rule-soft);
  border-left: 2px solid var(--c2-rule-lit);
  background: var(--c2-panel-2);
  font-size: var(--c2-text-sm);
  line-height: 1.3;
}
.concept-c02 .c2-note__text { flex: 1 1 auto; min-width: 0; }
.concept-c02 .c2-note__action { flex: none; }
.concept-c02 .c2-note--ok { color: var(--c2-ok); border-left-color: var(--c2-ok); }
.concept-c02 .c2-note--warn { color: var(--c2-warn); border-left-color: var(--c2-warn); }
.concept-c02 .c2-note--bad { color: var(--c2-bad); border-left-color: var(--c2-bad); }
.concept-c02 .c2-note--info { color: var(--c2-info); border-left-color: var(--c2-info); }
.concept-c02 .c2-note--neutral { color: var(--c2-ink-2); border-left-color: var(--c2-rule-lit); }

.concept-c02 .c2-verdict {
  display: flex;
  align-items: center;
  gap: .35rem;
  margin: 0;
  min-height: var(--c2-data-h);
  padding: 3px var(--c2-pad);
  font-size: var(--c2-text);
  font-weight: 600;
}
.concept-c02 .c2-verdict__mark { flex: none; }
.concept-c02 .c2-verdict__text { flex: 1 1 auto; min-width: 0; }
.concept-c02 .c2-verdict--ok { color: var(--c2-ok); }
.concept-c02 .c2-verdict--bad { color: var(--c2-warn); }

.concept-c02 .c2-stepper {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.concept-c02 .c2-stepper__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  background: var(--c2-panel-2);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad);
  color: var(--c2-ink);
  font: inherit;
  font-size: 1.1rem;
  cursor: pointer;
}
.concept-c02 .c2-stepper__input {
  width: 7ch; /* six digits — the widest figure a stepper here carries; 5.5ch rendered 10500 as "1050(" */
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0 .25rem;
  background: var(--c2-canvas);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad);
  color: var(--c2-ink);
  font: inherit;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.concept-c02 .c2-stepper__suffix { color: var(--c2-ink-3); font-size: var(--c2-text-sm); }

/* 9g-2. Fields --------------------------------------------------------------- */

/* A LABEL WRAPS THE CONTROL, so the label IS the target: tapping the word
   focuses the field without a `for`/`id` pair for a screen to keep unique. The
   column keeps the label directly above what it names, which is the one
   arrangement that survives a 360px phone — beside it, a label and a field share
   a row neither can read. */
/* THE GROUP IS ONE OPAQUE PANEL, and that is a contrast fix rather than a look.
   The label had no ground of its own, so whatever it sat over decided its
   contrast — and on the search-box routes what it sat over was the preset's
   background IMAGE, which makes a computed contrast underivable: the render
   harness reported `contrastUnknown` on every field label in the layout, which
   is "nobody can check this", not "this is fine". One --panel ground under the
   label and the field together, and the checker can measure both. */
.concept-c02 .c2-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 4px var(--c2-pad);
  background: var(--c2-panel);
}
.concept-c02 .c2-field__label { color: var(--c2-ink-3); font-size: var(--c2-text-sm); }
.concept-c02 .c2-field__input {
  width: 100%;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 .5rem;
  background: var(--c2-canvas);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad);
  color: var(--c2-ink);
  font: inherit;
  font-size: var(--c2-text);
}
.concept-c02 .c2-field__input::placeholder { color: var(--c2-ink-4); }
.concept-c02 .c2-field__input:focus-visible {
  outline: 2px solid var(--c2-accent-lit);
  outline-offset: 1px;
}
/* The native picker keeps the platform's own arrow; it is the affordance a
   player already knows and it costs no artwork. */
.concept-c02 .c2-field__select { padding-right: .3rem; }

/* A SEGMENT'S WORD IS NEVER BROKEN. Four segments at 360px gave "Available"
   78px and it printed "Availabl / e". A segment is as wide as its word at the
   least, and the row wraps a segment that does not fit onto a second line
   rather than breaking the word. */
.concept-c02 .c2-segmented {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: 2px var(--c2-pad);
}
.concept-c02 .c2-segmented__item {
  flex: 1 1 0;
  min-width: max-content;
  min-height: var(--tap);
  padding: 0 .3rem;
  white-space: nowrap;
  background: var(--c2-panel-2);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad);
  color: var(--c2-ink-2);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
/* The pressed segment is a filled state and takes the same pair — Load · Unload
   · Fleet transfer is exactly where a player must be able to read which one is
   on. */
.concept-c02 .c2-segmented__item[aria-pressed="true"] {
  background: var(--c2-accent-deep);
  border-color: var(--c2-accent);
  color: var(--c2-ink);
}

/* 9h. The sheet ------------------------------------------------------------ */

.concept-c02 .c2-sheet {
  width: 100%;
  max-width: 34rem;
  max-height: 86dvh;
  margin: auto auto 0;
  padding: 0;
  background: var(--c2-panel);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad) var(--c2-rad) 0 0;
  color: var(--c2-ink);
  overflow: hidden;
}
.concept-c02 .c2-sheet::backdrop { background: var(--c2-canvas); opacity: .72; }
.concept-c02 .c2-sheet__head {
  display: flex;
  align-items: center;
  gap: .3rem;
  padding: 2px var(--c2-pad);
  border-bottom: 1px solid var(--c2-rule);
}
.concept-c02 .c2-sheet__title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 15px;
  font-weight: 700;
}
/* The same frame as the stepper's and the chart's buttons: one kind of button
   (owner, 2026-09-16). It was a bare glyph in a 44px box that measured 55×50 on
   a phone with the head's padding around it. */
.concept-c02 .c2-sheet__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: var(--tap);
  height: var(--tap);
  background: var(--c2-panel-2);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad);
  color: var(--c2-ink);
  font: inherit;
  font-size: 1.1rem;
  cursor: pointer;
}
.concept-c02 .c2-sheet__body {
  max-height: 64dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--c2-pad);
  font-size: var(--c2-text);
}
.concept-c02 .c2-sheet__actions {
  display: flex;
  gap: var(--c2-gap);
  padding: var(--c2-pad);
  padding-bottom: max(var(--c2-pad), env(safe-area-inset-bottom));
  border-top: 1px solid var(--c2-rule);
}
.concept-c02 .c2-sheet__actions .c2-btn { flex: 1 1 auto; }

/* 9i. Small parts ---------------------------------------------------------- */

.concept-c02 .c2-pill {
  display: inline-flex;
  align-items: center;
  padding: 0 .45rem;
  border-radius: 999px;
  font-size: var(--c2-text-sm);
  font-weight: 600;
  white-space: nowrap;
}
.concept-c02 .c2-pill--ok { background: var(--c2-ok-dim); color: var(--c2-ok); }
.concept-c02 .c2-pill--warn { background: var(--c2-warn-dim); color: var(--c2-warn); }
.concept-c02 .c2-pill--bad { background: var(--c2-bad-dim); color: var(--c2-bad); }
.concept-c02 .c2-pill--info { background: var(--c2-info-dim); color: var(--c2-info); }
.concept-c02 .c2-pill--neutral { background: var(--c2-panel-2); color: var(--c2-ink-3); }
.concept-c02 .c2-chips { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px var(--c2-pad); }
.concept-c02 .c2-chips .c2-pill { min-height: var(--c2-data-h); }

/* An entity link inside a row is a 44px target in its own right — that is the
   whole trade this layout makes: small paint, full hit area. */
.concept-c02 .c2-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--c2-accent-lit);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.concept-c02 .c2-link:hover { text-decoration: none; color: var(--c2-accent); }
.concept-c02 .c2-link--ext { gap: .1rem; }
.concept-c02 .c2-link--dead { color: var(--c2-ink-3); font-weight: 400; }
/* Two lines inside ONE target: the subject in the accent, its context under it
   in the page's quiet ink. 13px + 12px at 1.3 is 32px of text inside a 44px
   link, so the cell is a tap target and the row is still 44px. */
.concept-c02 .c2-link--stacked {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  line-height: 1.25;
}
.concept-c02 .c2-link__label { display: inline-flex; align-items: center; gap: .1rem; }
.concept-c02 .c2-link__sub { color: var(--c2-ink-3); font-size: var(--c2-text-sm); font-weight: 400; }

/* 9j. The chart box --------------------------------------------------------- */

/* THE BOX IS SIZED BY CSS AND THE CANVAS FOLLOWS IT — never the other way round.
   A canvas whose backing store is written from its own laid-out height grows on
   every resize (the map's first pass reached 780px on a 390px phone by reading
   back what it had just set). Here the BOX has an aspect ratio and a clamp, the
   canvas fills it absolutely, and kit.chart() writes the backing store from
   clientWidth x devicePixelRatio. */
.concept-c02 .c2-chart {
  position: relative;
  width: 100%;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--c2-rad);
  background: var(--c2-canvas);
  border: 1px solid var(--c2-rule);
}
.concept-c02 .c2-chart__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}
/* THE CHART'S OWN CONTROLS, in its corner rather than in a row beneath it.
   A row under a chart is 44px of the screen the chart was given, permanently,
   for three things a player touches twice a session. These are the same 44px
   targets, stacked over the bottom-right corner on a translucent ground so the
   chart reads through them, and they cost it only their own corner. */
.concept-c02 .c2-chart__controls {
  position: absolute;
  right: var(--c2-pad);
  bottom: var(--c2-pad);
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 1px;
  background: color-mix(in srgb, var(--c2-panel) 82%, transparent);
  border: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad);
}
.concept-c02 .c2-chart__control {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--c2-rad);
  color: var(--c2-ink);
  font: inherit;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.concept-c02 .c2-chart__control-mark { font-size: 1.25rem; font-weight: 600; line-height: 1; }
.concept-c02 .c2-chart__control:hover { background: var(--c2-panel-2); }
.concept-c02 .c2-chart__control:focus-visible {
  outline: 2px solid var(--c2-accent-lit);
  outline-offset: -2px;
}

/* Edge to edge on a wide route: it gives back exactly the gutters .c2-screen
   put on. */
.concept-c02 .c2-chart--bleed {
  margin-inline: calc(var(--c2-gutter) * -1);
  border-inline: 0;
  border-radius: 0;
}

/* THE CHART THAT IS THE SCREEN — the map's, and the answer to the ~200px of
   empty page the owner measured under the docked panel on a 390x844 phone.

   It had an aspect ratio clamped between two dvh numbers (1/1, 50-60dvh). A
   ratio and a clamp can only be right at ONE viewport: on a 390px phone the
   clamp won and the chart stopped at 422px, leaving the rest of the content area
   to nothing at all. Here the height is a FLEX SHARE of a column that is the
   scroller's own height, so it is the available height by construction, at every
   viewport and on the desktop too.

   `min-height: 0` is what makes the share real: a flex item refuses to shrink
   below its content without it, and `flex-basis: auto` on a box whose only child
   is absolutely positioned measures as 0 — which is exactly what is wanted here,
   because the space this box gets should be decided by what is AROUND it.
   THE MAP GROWS INTO THE BAND; the dock does not. */
.concept-c02 .c2-chart--fill {
  flex: 1 1 auto;
  min-height: 0;
  /* Nothing may reintroduce a cap: the box takes what the column gives it. */
  max-height: none;
  aspect-ratio: auto;
}

/* ---------- 9k. The map's panes -------------------------------------------- */

/* THE MAP KEEPS ITS FULL REAL ESTATE (owner, 2026-09-16: "the bottom area of the
   map is still eating a lot of real estate").

   The Galaxy and System panes are a column the height of the scroller: a 44px
   header, the chart, and the docked panel on the bottom edge. Nothing is left
   over to be empty, and the chart — the only thing in the column that grows —
   takes what the clamp used to waste.

   The bottom padding goes with it: a panel that sits on the bottom bar has no
   use for it, and it was part of the band. The cards inside the dock keep their
   own. Intel and the list routes under `#/game/map` have no chart and no dock,
   so they are ordinary scrolling pages, do not take this class, and keep their
   padding. */
.concept-c02 > main.game-main.wide > .c2-map-pane {
  /* `height` as well as `min-height`, and the pair is deliberate: a DEFINITE
     height is what lets the dock's cap below be a share of this column rather
     than a share of the viewport, and `min-height` is what keeps the column
     right in a parent that is not the shell's flex scroller. */
  height: 100%;
  padding-bottom: 0;
}
.concept-c02 .c2-map-pane {
  min-height: 100%;
  /* The chart is the only thing that grows; nothing else in this column may. */
  gap: var(--c2-gap);
}
/* AND THE DOCK MAY NEVER TAKE MORE THAN HALF OF IT. `half` was 45dvh — 45% of
   the PHONE, which on a 390x844 screen is 71% of the ~535px this column
   actually is, and the chart would have been squeezed to a strip. The owner's
   rule for this package is that the map keeps its real estate: the space the
   dead band gave back goes to the CHART, so the panel is capped at half the
   column and its body scrolls inside that, which is what `.c2-dock__body`'s
   own `overflow-y: auto` is for.

   HALF MEANS HALF (map-7). It was 55%, which is not half of anything; both
   panes now ARRIVE at `half` — a tap on a system raises the quick card, and the
   System pane opens on the system's summary — so the chart is behind an open
   panel far more of the time and the few percent are the map's. */
.concept-c02 .c2-map-pane > .c2-dock { max-height: 50%; }
/* AND THE MAP'S STRIP KEEPS ITS PICKER ON A DESKTOP. Section 12 hides
   `.c2-entity__picker` at >= 1024 because "with room for a list beside the
   detail, the list IS the picker" — but these two panes have no list beside
   their detail: the chart IS the pane. At 1280 the strip therefore named the
   PLACE (which is what it does when a picker exists) and then hid the picker
   that was carrying the fleet's name, so the selected fleet was nowhere on the
   screen and could not be changed. This rule is more specific, so it wins
   wherever it is read. */
.concept-c02 .c2-map-pane .c2-entity__picker { display: inline-block; }
/* The header, the chart and the dock are the column, with no gap under the chart
   — the dock's own top border is the line between them, and a gap there is a
   stripe of page showing through the middle of the stage. */
.concept-c02 .c2-map-pane > .c2-chart {
  margin-bottom: calc(var(--c2-gap) * -1);
}

.concept-c02 .c2-split { display: block; }
.concept-c02 .c2-split__list,
.concept-c02 .c2-split__detail { display: flex; flex-direction: column; gap: var(--c2-gap); }

/* BODY COPY, and the one primitive in this kit that is allowed to be a
   paragraph. note() is a one-line status row — an icon, a tone and a sentence
   that fits — and a report's message or a server's explanation is neither: run
   through note() it stretched the row, lost its icon alignment and read as a
   warning about itself. Prose has no icon, no border and no tone by default; it
   wraps, it is capped at about 70 characters so a line stays readable on a wide
   card, and 1.4 leading because it is read rather than scanned. */
/* ---- One climate axis, as a gauge ------------------------------------------ */

/* THE ZONE IS THE POINT, not the fill. A meter fills from the left and answers
   "how much"; this answers "where does this world sit against where our people
   can live" — so the track carries the comfort band as a WINDOW and the planet
   as a marker standing on it.

   IT STILL READ AS A FILL (owner, phone round 5: "Also really hard to tell
   environment efficiency"). The band was drawn flush inside a clipped track with
   no ends on it, and the server's comfort window is the empire's outer tolerance
   — ideal ± a width of 45 on a 100-point scale — so its low end clamps to 0 on
   most axes and the result is a lit strip from the left edge: a quantity.

   Three things fix it and all three are geometry, not colour:
     * the window has TWO ENDS — a full border, and it stands 1px proud of the
       track top and bottom, so its right end is an edge rather than the end of
       a fill;
     * the ground OUTSIDE it is the page's own canvas, not a lit rule, so the
       window is the only lit thing on the line;
     * the marker overhangs the track by 4px top and bottom and carries a
       canvas-coloured hairline, so it reads as a pin dropped ON the scale even
       when it lands inside the band.
   The track therefore does NOT clip (`overflow: visible`); every part is
   positioned in percent and none of them can escape 0–100. */
.concept-c02 .c2-axis {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 4px var(--c2-pad) 7px;
  border-top: 1px solid var(--c2-rule-soft);
  font-variant-numeric: tabular-nums;
}
.concept-c02 .c2-section__body > .c2-axis:first-child { border-top: 0; }
.concept-c02 .c2-axis__head {
  display: flex;
  align-items: baseline;
  gap: .35rem;
  font-size: var(--c2-text-sm);
  line-height: 1.25;
}
.concept-c02 .c2-axis__label { flex: 0 0 auto; color: var(--c2-ink); font-weight: 600; }
/* THE BAND IS ONE WORD AND IT IS NEVER BROKEN. `overflow-wrap: anywhere` with
   `min-width: 0` let a two-line verdict beside it squeeze "Standard" into
   "Standar / d" on every hostile axis at 390 (colonies-7's sweep). The band
   keeps its word whole; the verdict is the flexible one and wraps its own
   words instead. */
.concept-c02 .c2-axis__band {
  flex: 1 1 auto;
  min-width: auto;
  color: var(--c2-ink-3);
  overflow-wrap: normal;
}
/* THE VERDICT, hard right and in its own tone: the one thing on the line that
   answers "is this world good for my people". It never shrinks below its own
   words — the band beside it is the elastic part — and it wraps rather than
   clipping, because nothing in this layout is ever ellipsised. */
.concept-c02 .c2-axis__verdict {
  flex: 0 1 auto;
  margin-left: auto;
  text-align: right;
  color: var(--c2-ink-2);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.concept-c02 .c2-axis__verdict--ok { color: var(--c2-ok); }
.concept-c02 .c2-axis__verdict--warn { color: var(--c2-warn); }
.concept-c02 .c2-axis__verdict--bad { color: var(--c2-bad); }
.concept-c02 .c2-axis__verdict--info { color: var(--c2-info); }
.concept-c02 .c2-axis__verdict--neutral { color: var(--c2-ink-3); }
/* Track and numbers on one line, and the numbers drop UNDER the track rather
   than squeezing it when a window's figures are long. */
.concept-c02 .c2-axis__gauge {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
  padding: 4px 0 1px;
  font-size: var(--c2-text-sm);
  line-height: 1.25;
}
.concept-c02 .c2-axis__reading {
  flex: 0 1 auto;
  min-width: 0;
  color: var(--c2-ink-2);
  overflow-wrap: anywhere;
}
.concept-c02 .c2-axis__track {
  position: relative;
  flex: 1 1 120px;
  min-width: 96px;
  height: 12px;
  border: 1px solid var(--c2-rule);
  border-radius: 2px;
  background: var(--c2-canvas);
}
/* LIVEABLE WITH PROTECTION — the empire's OUTER tolerance, and the faint one.
   The server builds it as ideal ± a tolerance width of 45 on a 100-point scale
   and judges hostility against the inner window instead, so most of this band
   wants a structure built. Drawn strong it told the owner a world they needed
   six domes for was fine: "it looks like I'm good for all the stuff but a few
   bands". It is a dashed outline with a quiet fill, inside the track, and it is
   never promoted to the strong band — not even when it is the only pair the
   server sent. */
.concept-c02 .c2-axis__liveable {
  position: absolute;
  top: 0;
  bottom: 0;
  border: 1px dashed var(--c2-rule-lit);
  border-radius: 2px;
  background: var(--c2-accent-deep);
}
/* WHERE THESE PEOPLE THRIVE — `comfortable_min/max_0_100`, the severity <= 15
   zone around the ideal, and the band the whole gauge is read against. It is
   the STRONG one: a lit fill, bordered on four sides, standing two pixels proud
   of the track so it has two unmistakable ends and reads as a window rather
   than as a quantity. `min-width` keeps a pinpoint window visible — a band of
   a fraction of a percent would otherwise round away to nothing. */
.concept-c02 .c2-axis__thrive {
  position: absolute;
  top: -2px;
  bottom: -2px;
  min-width: 4px;
  border: 1px solid var(--c2-accent);
  border-radius: 2px;
  background: var(--c2-accent-lit);
}
/* The empire's ideal point — a hairline, not a second marker. */
.concept-c02 .c2-axis__ideal {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--c2-ink);
  opacity: .45;
}
/* WHERE THIS WORLD IS. 3px wide and overhanging the track so it reads as a pin
   on the scale rather than a quantity in it, and it never disappears off either
   end: the negative margin keeps it on the track at 100% and the border-box
   inset keeps it on at 0%. The hairline ring is what keeps it visible where it
   lands on the lit window. */
.concept-c02 .c2-axis__mark {
  position: absolute;
  top: -4px;
  bottom: -4px;
  width: 3px;
  margin-left: -1px;
  border-radius: 1px;
  background: var(--c2-ink);
  box-shadow: 0 0 0 1px var(--c2-canvas);
}
.concept-c02 .c2-axis--ok .c2-axis__mark { background: var(--c2-ok); }
.concept-c02 .c2-axis--warn .c2-axis__mark { background: var(--c2-warn); }
.concept-c02 .c2-axis--bad .c2-axis__mark { background: var(--c2-bad); }
.concept-c02 .c2-axis--info .c2-axis__mark { background: var(--c2-info); }
.concept-c02 .c2-axis--neutral .c2-axis__mark { background: var(--c2-ink-3); }
.concept-c02 .c2-axis--warn .c2-axis__reading { color: var(--c2-warn); }
.concept-c02 .c2-axis--bad .c2-axis__reading { color: var(--c2-bad); }

/* ---- The docked panel ------------------------------------------------------ */

/* NOT A MODAL. It sits in the frame's own flow at the foot of the content area,
   it has no scrim, and the chart behind it keeps every event that does not land
   on it — which is the whole reason it exists rather than another <dialog>.
   `sticky`, not `fixed`: the bottom bar is a real band of this frame and a fixed
   panel would sit on top of it. */
.concept-c02 .c2-dock {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  min-height: var(--tap);
  margin-top: auto;
  background: var(--c2-panel);
  border-top: 1px solid var(--c2-rule-lit);
  border-radius: var(--c2-rad) var(--c2-rad) 0 0;
}
/* The whole title row is the toggle, and it is a button: a grabber a player has
   to hit exactly is a grabber they miss. */
.concept-c02 .c2-dock__handle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: none;
  width: 100%;
  min-height: var(--tap);
  padding: 4px var(--c2-pad);
  background: none;
  border: 0;
  color: var(--c2-ink);
  font: inherit;
  cursor: pointer;
}
/* HIDDEN IS A STATE. With nothing selected there is nothing to inspect, and a
   44px row saying so is 44px the chart is not getting back. */
.concept-c02 .c2-dock--hidden { display: none; }
/* COLLAPSED, THE ROW IS ONE LINE AND IT SAYS WHAT IT HOLDS: the grabber, the
   name of what is selected, and an up-chevron — the affordance, so the way to
   see more is visible rather than learned.
   IT KEEPS THE 44px FLOOR. It was built at 28px, which is what the panel had
   asked for, and the render harness counted it as an under-size target on the
   first run: this is the one control on the pane that a player has to hit to
   see anything at all, and in this layout the tap floor wins that argument
   everywhere. The saving the panel was asked for comes from the row that is now
   gone entirely when nothing is selected, and from the control row that left the
   chart's underside. */
.concept-c02 .c2-dock--peek > .c2-dock__handle {
  flex-direction: row;
  justify-content: flex-start;
  gap: var(--c2-pad);
  min-height: var(--tap);
  padding: 2px var(--c2-pad);
}
.concept-c02 .c2-dock--peek .c2-dock__title { display: none; }
.concept-c02 .c2-dock--peek .c2-dock__grabber { width: 20px; flex: none; }
.concept-c02 .c2-dock__peek { display: none; }
.concept-c02 .c2-dock--peek .c2-dock__peek {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  align-items: center;
  justify-content: space-between;
  gap: var(--c2-pad);
  font-size: var(--c2-text);
  font-weight: 600;
  line-height: 1.2;
  text-align: left;
}
.concept-c02 .c2-dock__peek-text {
  overflow-wrap: anywhere;
}
.concept-c02 .c2-dock__chevron { flex: none; color: var(--c2-accent-lit); }
.concept-c02 .c2-dock__grabber {
  width: 32px;
  height: 3px;
  border-radius: 2px;
  background: var(--c2-rule-lit);
}
.concept-c02 .c2-dock__title {
  margin: 0;
  width: 100%;
  font-size: var(--c2-text);
  font-weight: 600;
  line-height: 1.25;
  text-align: left;
  overflow-wrap: anywhere;
}
/* PEEK hides the body rather than shrinking it: a body clipped to a few pixels
   is a scroller nobody can use and a row of half-letters. */
.concept-c02 .c2-dock--peek .c2-dock__body,
.concept-c02 .c2-dock--peek .c2-dock__actions { display: none; }
.concept-c02 .c2-dock__body {
  flex: 1 1 auto;
  max-height: 45dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  border-top: 1px solid var(--c2-rule-soft);
}
.concept-c02 .c2-dock__actions {
  display: flex;
  gap: var(--c2-gap);
  flex: none;
  padding: var(--c2-pad);
  padding-bottom: max(var(--c2-pad), env(safe-area-inset-bottom));
  border-top: 1px solid var(--c2-rule);
}
.concept-c02 .c2-dock__actions .c2-btn { flex: 1 1 auto; }
/* The one thing that moves, and only the height of the body moves: transform
   and opacity are the composited pair everywhere else in this file, and a panel
   that slid would take the chart's own pointer target with it mid-gesture. */
.concept-c02 .c2-dock__body { transition: max-height .16s ease-out; }
/* Its reduced-motion guard is in section 12 with the route transition's: ONE
   block in this file, so a reader looking for "what moves, and what stops
   moving" finds all of it in one place. */

.concept-c02 .c2-prose {
  margin: 0;
  max-width: 70ch;
  padding: 4px var(--c2-pad);
  color: var(--c2-ink-2);
  font-size: var(--c2-text);
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.concept-c02 .c2-prose + .c2-prose { padding-top: 0; }
.concept-c02 .c2-prose--ok { color: var(--c2-ok); }
.concept-c02 .c2-prose--warn { color: var(--c2-warn); }
.concept-c02 .c2-prose--bad { color: var(--c2-bad); }
.concept-c02 .c2-prose--info { color: var(--c2-info); }
.concept-c02 .c2-prose--neutral { color: var(--c2-ink-3); }

.concept-c02 .c2-empty {
  margin: 0;
  padding: var(--c2-pad);
  color: var(--c2-ink-4);
  font-size: var(--c2-text-sm);
}

/* The ⓘ button is game.css's (ui/infoButton.js); inside a 28px section head it
   must not stretch the row, so it keeps its 44px hit area and loses its
   margin. */
.concept-c02 .c2-section__head .info-btn { margin-left: 0; }

/* ---------- 9z. THE ART VARIANT ------------------------------------------- */

/* CONCEPT 02 · MINT ART — the same frame with the concept pack's artwork and
   more colour in it. Every rule in this section is written
   `.concept-c02.concept-c02--art …`: the modifier is a second class the shell
   adds beside the root one only when the art entry is the one that mounted, so
   with it absent this whole section is inert and Concept 02 proper is
   unchanged — the same terms the root class itself is on, and
   tests/concept_c02_shell.test.js pins both.

   STILL NO COLOUR LITERALS. The variant was drawn in the Mint SCHEME, and the
   scheme is core/colours.js's job, not this file's: every colour below is the
   same token the plain layout uses, so Astral, Ember, Verdant, Slate, Custom
   and all five colour-vision modes paint the variant too. What changes here is
   WHERE colour is spent — on the words that carry a state, on the edge of a row
   that has one, on the ground under a tag — not which colours exist.

   AND THE DENSITY RULE STANDS. No hero art, no banners: the art band is 56px,
   a section's thumbnail is 28px and a row's is 20px. A picture in this layout
   is decoration over data and never the data itself — every one of them is
   aria-hidden by default, because the words beside it already say it. */

/* 9z-a. The frame ----------------------------------------------------------- */

/* One framed square. The pack is OPAQUE — every file is a full rectangle on its
   own dark navy — so a picture is never floated on a card as if it were a
   cutout: it fills its frame, and the frame is what belongs to this layout. */
.concept-c02.concept-c02--art .c2-art {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: var(--c2-rad);
  border: 1px solid var(--c2-rule-lit);
  background: var(--c2-panel-2);
}
.concept-c02.concept-c02--art .c2-art--sm { width: 20px; height: 20px; }
.concept-c02.concept-c02--art .c2-art--md { width: 28px; height: 28px; }
.concept-c02.concept-c02--art .c2-art--lg { width: 40px; height: 40px; }
.concept-c02.concept-c02--art .c2-art__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* 9z-b. The 56px art band --------------------------------------------------- */

/* The entity's own picture at the left and its title over it, in ONE band of 56
   px — the header's own two rows sit on top of it rather than under it, so the
   band costs the page nothing it did not already spend. The gradient is what
   keeps the words readable over whatever the illustration is doing: --panel at
   the left, where the text is, fading to nothing across the art. */
.concept-c02.concept-c02--art .c2-entity--art {
  position: relative;
  min-height: 56px;
  isolation: isolate;
}
/* THE ART HAS A LANE OF ITS OWN, AND NOTHING IS EVER PRINTED OVER IT. The band
   used to centre the picture on the header's height and scrim the words over
   it — an arrangement measured on a header that was one 44px line. Since the
   header wraps (kit-7), a long name puts the picker on a second line and the
   picture ended up half behind it; a right slot printed over it; the links row
   ran under it. So the picture docks at the TOP-RIGHT and every row of words
   (title row, links, sub) keeps `--c2-art-lane` clear on its right: the
   picker, the right slot and the links stop short of the picture instead of
   crossing it. A 56px lane on a 390px phone is the width of one short word,
   and the title wraps a word earlier for it. */
.concept-c02.concept-c02--art .c2-entity--art { --c2-art-lane: calc(56px + var(--c2-pad)); }
.concept-c02.concept-c02--art .c2-entity--art > .c2-entity__top,
.concept-c02.concept-c02--art .c2-entity--art > .c2-entity__links,
.concept-c02.concept-c02--art .c2-entity--art > .c2-entity__sub { padding-right: var(--c2-art-lane); }
.concept-c02.concept-c02--art .c2-entity__band {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding-right: var(--c2-pad);
  overflow: hidden;
  border-radius: var(--c2-rad);
}
.concept-c02.concept-c02--art .c2-entity__band .c2-art {
  width: 56px;
  height: 56px;
  border: 0;
  border-radius: 0;
  background: none;
}
/* The scrim only softens the picture's left edge into the card now: the words
   never reach the picture, so there is nothing to protect but the seam. */
.concept-c02.concept-c02--art .c2-entity__band::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, var(--c2-panel) calc(100% - var(--c2-art-lane)), transparent calc(100% - 40px));
}

/* 9z-c. Colour that means something ----------------------------------------- */

/* A TAG SITS ON ITS OWN GROUND. The plain layout draws a pill as a word in a
   colour; here the accent ones take a tinted bed as well, which is what makes a
   player's own fleet tags read as a row of labels rather than as more prose. */
.concept-c02.concept-c02--art .c2-pill { background: var(--c2-accent-dim); }
.concept-c02.concept-c02--art .c2-pill--ok { background: var(--c2-ok-dim); }
.concept-c02.concept-c02--art .c2-pill--warn { background: var(--c2-warn-dim); }
.concept-c02.concept-c02--art .c2-pill--bad { background: var(--c2-bad-dim); }
.concept-c02.concept-c02--art .c2-pill--info { background: var(--c2-info-dim); }

/* A ROW THAT CARRIES A STATE SAYS SO DOWN ITS EDGE. Two pixels at the leading
   edge, in the tone the row already has — the word is still there and still
   carries the state on its own (a colour-vision mode takes the edge's meaning
   away and loses nothing), and down a list of twenty fleets it is what lets the
   one in combat be found without reading. */
.concept-c02.concept-c02--art .c2-row--ok,
.concept-c02.concept-c02--art .c2-row--warn,
.concept-c02.concept-c02--art .c2-row--bad,
.concept-c02.concept-c02--art .c2-row--info { border-left: 2px solid var(--c2-rule-lit); }
.concept-c02.concept-c02--art .c2-row--ok { border-left-color: var(--c2-ok); }
.concept-c02.concept-c02--art .c2-row--warn { border-left-color: var(--c2-warn); }
.concept-c02.concept-c02--art .c2-row--bad { border-left-color: var(--c2-bad); }
.concept-c02.concept-c02--art .c2-row--info { border-left-color: var(--c2-info); }
/* The row's meta is where the state WORD is, and on this variant it takes the
   tone with it: `Exploring` in the info tone, `In combat` in the bad one,
   `Arrived` in the ok one, `Standing by` left quiet. */
.concept-c02.concept-c02--art .c2-row--ok .c2-row__meta { color: var(--c2-ok); }
.concept-c02.concept-c02--art .c2-row--warn .c2-row__meta { color: var(--c2-warn); }
.concept-c02.concept-c02--art .c2-row--bad .c2-row__meta { color: var(--c2-bad); }
.concept-c02.concept-c02--art .c2-row--info .c2-row__meta { color: var(--c2-info); }
.concept-c02.concept-c02--art .c2-row--neutral .c2-row__meta { color: var(--c2-ink-3); }

/* AN ATTENTION SECTION SAYS WHAT KIND OF THING IT IS HOLDING before a word of
   it is read: the head band takes the tone, and a 2px rule down the card's own
   edge carries it past the head. Kept to the HEAD rather than the whole card —
   a tinted card is a card nobody can read a table on. */
.concept-c02.concept-c02--art .c2-section--ok,
.concept-c02.concept-c02--art .c2-section--warn,
.concept-c02.concept-c02--art .c2-section--bad,
.concept-c02.concept-c02--art .c2-section--info { border-left: 2px solid var(--c2-rule-lit); }
.concept-c02.concept-c02--art .c2-section--ok { border-left-color: var(--c2-ok); }
.concept-c02.concept-c02--art .c2-section--warn { border-left-color: var(--c2-warn); }
.concept-c02.concept-c02--art .c2-section--bad { border-left-color: var(--c2-bad); }
.concept-c02.concept-c02--art .c2-section--info { border-left-color: var(--c2-info); }
.concept-c02.concept-c02--art .c2-section--ok > .c2-section__head { background: var(--c2-ok-dim); }
.concept-c02.concept-c02--art .c2-section--warn > .c2-section__head { background: var(--c2-warn-dim); }
.concept-c02.concept-c02--art .c2-section--bad > .c2-section__head { background: var(--c2-bad-dim); }
.concept-c02.concept-c02--art .c2-section--info > .c2-section__head { background: var(--c2-info-dim); }

/* A METER GLOWS, faintly. The bar is 4px and on a dark ground a 4px bar of a
   dim token is easy to miss; a soft spread of its own colour is what makes it
   read at a glance. `box-shadow` and not a filter: a filter on a fill is a
   compositor pass per frame on a phone, and there can be a dozen of these on a
   colony page. */
.concept-c02.concept-c02--art .c2-meter__fill {
  box-shadow: 0 0 6px var(--c2-accent-dim);
}
.concept-c02.concept-c02--art .c2-meter--ok .c2-meter__fill { box-shadow: 0 0 6px var(--c2-ok-dim); }
.concept-c02.concept-c02--art .c2-meter--warn .c2-meter__fill { box-shadow: 0 0 6px var(--c2-warn-dim); }
.concept-c02.concept-c02--art .c2-meter--bad .c2-meter__fill { box-shadow: 0 0 6px var(--c2-bad-dim); }
.concept-c02.concept-c02--art .c2-meter--info .c2-meter__fill { box-shadow: 0 0 6px var(--c2-info-dim); }

/* The tier-3 strip's current item and the bottom bar's carry the same soft
   spread, so the three tiers read as one family rather than three flat bands. */
.concept-c02.concept-c02--art .c2-detail__item[aria-current="page"],
.concept-c02.concept-c02--art > .tabbar a[aria-current="page"] {
  box-shadow: 0 0 8px var(--c2-accent-dim);
}

/* ---------- 10. Desktop (>= 1024px) --------------------------------------- */

/* SAME DOM, ONE MEDIA QUERY. The bottom bar becomes a left rail; the header,
   both strips and the screen stay stacked in the column beside it, and the
   content is capped and centred so a table is not 1,200px wide.

   The rail is `position: fixed` and the frame takes a matching padding: the
   alternative is a grid over a column whose bands come and go (the alert
   strips mount inside `display: contents` holders), which would need an
   explicit row for a band that may not exist. Fixed costs one rule and cannot
   misalign. */
@media (min-width: 1024px) {
  .concept-c02 { padding-left: var(--c2-rail-w); }
  .concept-c02 > .tabbar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--c2-rail-w);
    flex-direction: column;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: 2px;
    padding: var(--c2-gap) 4px;
    border-top: none;
    border-right: 1px solid var(--c2-rule);
    z-index: 30;
  }
  /* THE RAIL IS 5rem, NOT 4.5rem, and the 8px is a measured label rather than a
     preference: "Colonies" under the Command preset's uppercase and .04em
     tracking needs 68px at 12px type, and a 72px rail leaves the label box 59px
     — the render harness reported it clipped at 1280x900. 80px with an 11px
     label leaves 68px against 62px of word. Every other label is shorter. */
  .concept-c02 { --c2-rail-w: 5rem; }
  .concept-c02 > .tabbar a {
    flex: 0 0 auto;
    min-height: var(--c2-bar-tap);
    font-size: 11px;
  }
  .concept-c02 ~ #toasts { bottom: 1rem; }

  .concept-c02 > main.game-main { padding-inline: 0; }
  .concept-c02 .c2-screen {
    width: 100%;
    max-width: 1100px;
    margin: 0 auto;
    padding: 0 var(--c2-gutter);
  }
  /* The picker is the phone's way of changing entity; with room for a list
     beside the detail, the list IS the picker. */
  .concept-c02 .c2-entity__picker { display: none; }
  .concept-c02 .c2-split {
    display: grid;
    grid-template-columns: 320px minmax(0, 1fr);
    gap: var(--c2-gutter);
    align-items: start;
  }
}

/* ---------- 11. Narrow (<= 360px) ----------------------------------------- */

/* A WIDE TABLE STACKS RATHER THAN CLIPPING. Six columns of resource figures do
   not fit a 360px phone at a readable size, and shrinking the type is the one
   answer the pack rules out. So each row becomes ONE ROW of the kit's own
   shape — title left, `label value` facts right, wrapping between facts — and
   nothing is cut off. It used to become a block of one `label — value` LINE
   per cell, and that is the layout the owner called "super spread out". */
@media (max-width: 360px) {
  .concept-c02 .c2-table--stack .c2-table,
  .concept-c02 .c2-table--stack .c2-table tbody { display: block; width: 100%; }
  .concept-c02 .c2-table--stack .c2-table thead { display: none; }
  /* ONE ROW PER ROW. The heading cell is the title and takes what is left of
     its line; every other cell is a `label value` fact beside it, and the
     facts wrap between themselves onto a second line when they must. Two
     lines for a five-column row — never a line per cell, which is what turned
     twenty ranked empires into six lines and 260px each on the live shard
     ("super spread out for no reasons"). Every number is still there, still
     tabular; only the empty width is gone. */
  .concept-c02 .c2-table--stack .c2-table tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--c2-pad);
    width: 100%;
    padding: 3px var(--c2-pad);
    border-top: 1px solid var(--c2-rule-soft);
  }
  .concept-c02 .c2-table--stack .c2-table tbody tr:first-child { border-top: 0; }
  .concept-c02 .c2-table--stack .c2-table th,
  .concept-c02 .c2-table--stack .c2-table td {
    display: inline-flex;
    align-items: baseline;
    flex: 0 0 auto;
    gap: .3rem;
    max-width: 100%;
    padding: 0;
    border-top: 0;
    text-align: left;
    /* `height` on a table cell is a MINIMUM; on a flex item it is absolute — so
       the 28px that keeps a table row honest at width would box a 44px link. */
    height: auto;
    line-height: 1.3;
  }
  /* The title: the row's own heading, sharing the line with its facts when
     they fit and keeping a third of it when they do not. The 30% share the
     label column takes at width is meaningless here and squeezed names. */
  .concept-c02 .c2-table--stack .c2-table th:first-child {
    flex: 1 1 auto;
    min-width: 34%;
    width: auto;
    font-weight: 600;
    border-bottom: 0;
  }
  /* The first fact pushes the run of facts to the right edge — on the title's
     line when they fit, on their own line when they wrap. */
  .concept-c02 .c2-table--stack .c2-table th:first-child + td { margin-left: auto; }
  /* AT THE FOLD A SUB-FACT SITS BESIDE ITS FIGURE, NOT UNDER IT. A folded row
     is already `label value` facts on one line, so a sub-fact hung in its own
     column under the value while the facts after it stayed on the line above:
     `200 reserved` printed under `This tick`, three fields away from the stock
     it belongs to (measured on the owner's own colony at 390). So a cell's
     inner column is a ROW here — figure, then its sub-fact — and the sub-fact
     never takes the bold of a heading cell.
     The two `align-items` rules this replaces (flex-start on the heading cell,
     flex-end on a figure cell) were for the column form, which no folded cell
     has any more. */
  .concept-c02 .c2-table--stack .c2-table th .c2-cell,
  .concept-c02 .c2-table--stack .c2-table td .c2-cell {
    flex-direction: row;
    align-items: baseline;
    gap: .3rem;
  }
  .concept-c02 .c2-table--stack .c2-cell__sub { font-weight: 400; }
  .concept-c02 .c2-table--stack .c2-cell__label {
    display: inline;
    color: var(--c2-ink-4);
    font-size: var(--c2-text-sm);
    font-weight: 400;
  }
  .concept-c02 .c2-table--stack th:first-child .c2-cell__label { display: none; }

  /* The wordmark drops to the mark alone: below 360px the line has to hold the
     clock, the menu and the mailbox, and the game's name in words is the only
     thing on it that is not load-bearing. */
  .concept-c02 > .shell-header--game .brand b { display: none; }

  .concept-c02 .c2-actions { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* THE SAME LAYOUT, AT TWO WIDER TRIGGERS. `stackAt` is a VIEWPORT width and the
   supported set is fixed (kit/model.js STACK_BREAKPOINTS) because each one is a
   media query, and a media query cannot be written from a number a caller
   passes at runtime. Before this, the layout was hard-coded to 360 and
   `stackAt: 390` was a setting that read like one and did nothing — a table
   asking to stack on the phone the game is actually played on did not.

   THE RULES ARE REPEATED, AND THAT IS A DELIBERATE TRADE. The alternative was to
   drive every declaration through custom properties flipped by three tiny media
   blocks: one copy of the layout, and a rewrite of the base table rules that
   eighteen merged screens already depend on. Repetition a test pins beats a
   refactor nothing can watch go wrong — tests/concept_c02_kit.test.js compares
   the three blocks declaration for declaration and fails if they drift. The 360
   block above is untouched, so every table already asking for 360 renders
   exactly as it did. Each class appears in exactly ONE block: a wider trigger
   already covers every narrower width. */
@media (max-width: 390px) {
  .concept-c02 .c2-table--stack-390 .c2-table,
  .concept-c02 .c2-table--stack-390 .c2-table tbody { display: block; width: 100%; }
  .concept-c02 .c2-table--stack-390 .c2-table thead { display: none; }
  /* ONE ROW PER ROW. The heading cell is the title and takes what is left of
     its line; every other cell is a `label value` fact beside it, and the
     facts wrap between themselves onto a second line when they must. Two
     lines for a five-column row — never a line per cell, which is what turned
     twenty ranked empires into six lines and 260px each on the live shard
     ("super spread out for no reasons"). Every number is still there, still
     tabular; only the empty width is gone. */
  .concept-c02 .c2-table--stack-390 .c2-table tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--c2-pad);
    width: 100%;
    padding: 3px var(--c2-pad);
    border-top: 1px solid var(--c2-rule-soft);
  }
  .concept-c02 .c2-table--stack-390 .c2-table tbody tr:first-child { border-top: 0; }
  .concept-c02 .c2-table--stack-390 .c2-table th,
  .concept-c02 .c2-table--stack-390 .c2-table td {
    display: inline-flex;
    align-items: baseline;
    flex: 0 0 auto;
    gap: .3rem;
    max-width: 100%;
    padding: 0;
    border-top: 0;
    text-align: left;
    /* `height` on a table cell is a MINIMUM; on a flex item it is absolute — so
       the 28px that keeps a table row honest at width would box a 44px link. */
    height: auto;
    line-height: 1.3;
  }
  /* The title: the row's own heading, sharing the line with its facts when
     they fit and keeping a third of it when they do not. The 30% share the
     label column takes at width is meaningless here and squeezed names. */
  .concept-c02 .c2-table--stack-390 .c2-table th:first-child {
    flex: 1 1 auto;
    min-width: 34%;
    width: auto;
    font-weight: 600;
    border-bottom: 0;
  }
  /* The first fact pushes the run of facts to the right edge — on the title's
     line when they fit, on their own line when they wrap. */
  .concept-c02 .c2-table--stack-390 .c2-table th:first-child + td { margin-left: auto; }
  /* AT THE FOLD A SUB-FACT SITS BESIDE ITS FIGURE, NOT UNDER IT. A folded row
     is already `label value` facts on one line, so a sub-fact hung in its own
     column under the value while the facts after it stayed on the line above:
     `200 reserved` printed under `This tick`, three fields away from the stock
     it belongs to (measured on the owner's own colony at 390). So a cell's
     inner column is a ROW here — figure, then its sub-fact — and the sub-fact
     never takes the bold of a heading cell.
     (kit-10, the same rule as the 360 block above.)
     The two `align-items` rules this replaces (flex-start on the heading cell,
     flex-end on a figure cell) were for the column form, which no folded cell
     has any more. */
  .concept-c02 .c2-table--stack-390 .c2-table th .c2-cell,
  .concept-c02 .c2-table--stack-390 .c2-table td .c2-cell {
    flex-direction: row;
    align-items: baseline;
    gap: .3rem;
  }
  .concept-c02 .c2-table--stack-390 .c2-cell__sub { font-weight: 400; }
  .concept-c02 .c2-table--stack-390 .c2-cell__label {
    display: inline;
    color: var(--c2-ink-4);
    font-size: var(--c2-text-sm);
    font-weight: 400;
  }
  .concept-c02 .c2-table--stack-390 th:first-child .c2-cell__label { display: none; }
}

@media (max-width: 480px) {
  .concept-c02 .c2-table--stack-480 .c2-table,
  .concept-c02 .c2-table--stack-480 .c2-table tbody { display: block; width: 100%; }
  .concept-c02 .c2-table--stack-480 .c2-table thead { display: none; }
  /* ONE ROW PER ROW. The heading cell is the title and takes what is left of
     its line; every other cell is a `label value` fact beside it, and the
     facts wrap between themselves onto a second line when they must. Two
     lines for a five-column row — never a line per cell, which is what turned
     twenty ranked empires into six lines and 260px each on the live shard
     ("super spread out for no reasons"). Every number is still there, still
     tabular; only the empty width is gone. */
  .concept-c02 .c2-table--stack-480 .c2-table tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--c2-pad);
    width: 100%;
    padding: 3px var(--c2-pad);
    border-top: 1px solid var(--c2-rule-soft);
  }
  .concept-c02 .c2-table--stack-480 .c2-table tbody tr:first-child { border-top: 0; }
  .concept-c02 .c2-table--stack-480 .c2-table th,
  .concept-c02 .c2-table--stack-480 .c2-table td {
    display: inline-flex;
    align-items: baseline;
    flex: 0 0 auto;
    gap: .3rem;
    max-width: 100%;
    padding: 0;
    border-top: 0;
    text-align: left;
    /* `height` on a table cell is a MINIMUM; on a flex item it is absolute — so
       the 28px that keeps a table row honest at width would box a 44px link. */
    height: auto;
    line-height: 1.3;
  }
  /* The title: the row's own heading, sharing the line with its facts when
     they fit and keeping a third of it when they do not. The 30% share the
     label column takes at width is meaningless here and squeezed names. */
  .concept-c02 .c2-table--stack-480 .c2-table th:first-child {
    flex: 1 1 auto;
    min-width: 34%;
    width: auto;
    font-weight: 600;
    border-bottom: 0;
  }
  /* The first fact pushes the run of facts to the right edge — on the title's
     line when they fit, on their own line when they wrap. */
  .concept-c02 .c2-table--stack-480 .c2-table th:first-child + td { margin-left: auto; }
  /* AT THE FOLD A SUB-FACT SITS BESIDE ITS FIGURE, NOT UNDER IT. A folded row
     is already `label value` facts on one line, so a sub-fact hung in its own
     column under the value while the facts after it stayed on the line above:
     `200 reserved` printed under `This tick`, three fields away from the stock
     it belongs to (measured on the owner's own colony at 390). So a cell's
     inner column is a ROW here — figure, then its sub-fact — and the sub-fact
     never takes the bold of a heading cell.
     (kit-10, the same rule as the 360 block above.)
     The two `align-items` rules this replaces (flex-start on the heading cell,
     flex-end on a figure cell) were for the column form, which no folded cell
     has any more. */
  .concept-c02 .c2-table--stack-480 .c2-table th .c2-cell,
  .concept-c02 .c2-table--stack-480 .c2-table td .c2-cell {
    flex-direction: row;
    align-items: baseline;
    gap: .3rem;
  }
  .concept-c02 .c2-table--stack-480 .c2-cell__sub { font-weight: 400; }
  .concept-c02 .c2-table--stack-480 .c2-cell__label {
    display: inline;
    color: var(--c2-ink-4);
    font-size: var(--c2-text-sm);
    font-weight: 400;
  }
  .concept-c02 .c2-table--stack-480 th:first-child .c2-cell__label { display: none; }
}


/* ---------- 11a. Narrow rows (<= 380px) ----------------------------------- */

/* A JOB ROW KEEPS ITS 44px BY DROPPING THE BAR, NOT THE NUMBERS. At 360px the
   inline reading plus its action plus the job's name is 40px more than the row
   has, so the name wrapped and the row grew to 57px. The 34px track is the
   redundant part — `1 / 4 ticks` and `25%` are the reading, and both stay. */
@media (max-width: 380px) {
  .concept-c02 .c2-progress--inline .c2-meter__track { display: none; }
}

/* ---------- 12. Motion ---------------------------------------------------- */

/* The route change, and nothing else: the chrome is what stays put in an app,
   so only the screen moves. Transform and opacity only — both composited — and
   it ends on `transform: none` rather than a zero translate, so the finished
   screen leaves no transform behind to become a containing block for a sheet
   opened later. */
@keyframes concept-c02-enter {
  from { opacity: 0; transform: translate3d(0, 6px, 0); }
  to   { opacity: 1; transform: none; }
}
.concept-c02 > main.game-main { animation: concept-c02-enter .14s ease-out both; }

/* game.css's global kill switch already clamps every animation when the device
   asks for reduced motion. This is the belt to that pair of braces, and the
   rule preset-command.css sets for any stylesheet that adds an animation of its
   own: turn it off, here, in the file that added it. */
@media (prefers-reduced-motion: reduce) {
  .concept-c02 > main.game-main { animation: none; }
  /* The docked panel's height change (section 9k) is the only other thing in
     this file that moves. */
  .concept-c02 .c2-dock__body { transition: none; }
}
