@import url('canonical.css');

html {
  scrollbar-gutter: stable;
  --ceres-vw: 1vw;
  --ceres-vh: 1vh;
}

/* S-15630: scrollbars are global by construction. Any element that becomes a
   scroll container is covered automatically; intentional hidden-scroll surfaces
   keep their later, explicit none overrides. */
:where(*), html {
  scrollbar-width: thin;
  scrollbar-color: var(--border-bright) transparent;
}
:where(*)::-webkit-scrollbar, html::-webkit-scrollbar { width: 0.5rem; height: 0.5rem; }
:where(*)::-webkit-scrollbar-track, html::-webkit-scrollbar-track { background: transparent; }
:where(*)::-webkit-scrollbar-thumb, html::-webkit-scrollbar-thumb {
  background: var(--border-bright);
  border-radius: 999px;
}
:where(*)::-webkit-scrollbar-thumb:hover, html::-webkit-scrollbar-thumb:hover { background: var(--accent); }

/* Ceres project-local tokens and layout. The UI is deliberately plain (plan §1) --
   no card art, no build step. Iris supplies color/spacing tokens and the theme picker
   chrome; everything below is Ceres-specific structure only.

   All colors below are Iris tokens with NO local hex fallback -- canonical.css is
   @import'd above and always defines every var in :root before this stylesheet's
   rules run, so a fallback would be dead code that only invites a hardcoded color to
   creep back in. Iris has no spacing/size scale beyond --radius/--page-max/--gutter
   (confirmed against canonical.css, 2026-08-20), so rem-based spacing below is
   Ceres-local layout, not a token gap -- --radius is used everywhere Iris does define
   a shape token. */

/* Viewport-relative height replaced the old fixed body height (board-polish batch 19, item 16, Florian: "make the
   main table on Ceres not fixed in height but fully scrollable. I dont want to
   have empty space there."). That fixed height was the whole reason
   `.table-scroll` needed its own bounded, internally-scrolling region below --
   with `body` no longer clamped to the viewport, the PAGE grows to the table's
   real content height and the page itself scrolls, so a short table (e.g. a
   fresh/filtered/small account) leaves no dead space, and a tall one (6,300+
   plot accounts) just makes for a long page, exactly like any other content-
   sized page. The viewport-relative minimum stays so a near-empty board still fills the
   viewport instead of leaving the header floating over a half-blank window. */
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  margin: 0;
  padding: 0 0 3rem 0;
  min-height: calc(var(--ceres-vh) * 100);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  /* Pre-landing review fix (S-14280 v6 amendment): `body` had no `position` before --
     #edge-tabs (index.html) moved to a direct child of `body` (out of the
     `overflow-x: auto` `.controls-row`, which was clipping/scrolling it sideways),
     and needs `body` as its `position: absolute` containing block so
     app.js::centerThemePicker() can position it in document coordinates. IR-6730:
     `#badge-bar` (also a direct child of `body` now) uses the same containing
     block for the exact same reason -- see that element's own comment. */
  position: relative;
}

header.board-header {
  position: sticky;
  top: 0;
  z-index: 50; /* IR-6930: raised from 10 -- the wallet stack (#edge-tabs/.dec-drawer)
     used to paint at 40, ABOVE this header, once the filter row scrolled under it.
     Header now outranks the whole wallet stack explicitly; see .edge-tabs' own
     comment and the ladder notes at ~2298/~2360 for the full chain. */
  background: var(--bg2);
  /* Vertical padding trimmed 0.75rem -> 0.4rem (2026-08-21, Florian: "give the logo
     60px height, reduce the padding top and bottom a bit for the header, accept the
     larger header height") -- the 60px logo (below) is now the tallest thing in this
     row by a wide margin, so full vertical breathing room is still there without the
     original padding; horizontal padding (1rem) is untouched. */
  padding: 0.65rem 1rem;
  /* Header round 4 (2026-08-24, Florian on round 3's result: "row 1 and 2 are
     vert far away from each other and too close to the logo. both are too
     close to the upper or lower border of the header"). Round 3's `align-
     self: start` (row 1, flush to the logo's top) / `align-self: end` (row
     2, flush to the logo's bottom) split is GONE -- that split is exactly
     what forced the ~22px inter-row gap Florian was reacting to: with the
     two rows totalling ~53px inside the logo's 75px box, flushing them to
     opposite edges necessarily strands the leftover ~22px between them, and
     also welds each row to the logo's own top/bottom edge instead of the
     header's padding.

     `header.board-header` is now a two-column, ONE-ROW CSS GRID: column 1 is
     the logo, column 2 is `.board-header-rows` (see that rule) -- a single
     grid item wrapping BOTH `.board-header-row` and `.totals-strip` in a
     flex column with its own small, literal `gap`. `align-items: center`
     here centres both column-1 (logo) and column-2 (the two-row block, ~62px
     tall) as a unit against the row's own height (pinned to the logo's 75px,
     the taller of the two column contents) -- so the two-row block reads as
     vertically centred beside the logo, never welded to either edge.
     `padding` (0.4rem -> 0.65rem vertical) buys the header's own upper/lower
     border some breathing room now that the rows no longer reach all the way
     to the logo's edges -- header height grows modestly (~89px -> ~97px at
     1440px), NOT ballooning past "logo height + a small padding", the
     original ceiling. `column-gap` (1rem -> 1.5rem) widens the logo-to-rows
     horizontal gap, the third complaint.

     Column 2's own left edge is shared by both rows inside `.board-header-
     rows`, and since `.board-header-row`'s first child is the player switch
     (the logo lives in its own column 1, see markup), `.totals-strip` lines
     up with the player switch's left edge for free, with no explicit margin
     needed -- unchanged from round 3, re-verified by measurement in
     work/ceres-header4/.

     Its own `position: sticky; top: 0`, `overflow-x: clip`, `overflow-y:
     visible` are UNCHANGED -- this element was already the sticky one (never
     a descendant), so the sticky containing block for anything inside it is
     unaffected by this layout change. */
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 1.5rem;
  align-items: center;
  min-width: 0;
  max-width: 100%;
  /* Keep the checkout dock from creating document overflow while allowing it to drop below the header. */
  overflow-x: clip;
  overflow-y: visible;
}

/* Header round 4 (2026-08-24): the single grid item occupying column 2 --
   see `header.board-header`'s own comment for why this replaces round 3's
   per-row `grid-row`/`align-self` split. `gap` here is the ONLY thing that
   now separates row 1 from row 2 -- a small, deliberate value (was an
   incidental ~22px of leftover grid space in round 3), and it is a real,
   literal flex gap rather than distributed track space, so it stays exactly
   this value regardless of how tall the logo is. */
.board-header-rows {
  grid-column: 2;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 0;
  max-width: 100%;
}

/* Item 41 redone's top row -- player switch, capture cluster, checkout dock
   (the logo moved OUT to its own grid column, see `.board-logo` below; it is
   no longer this row's child). Carries every layout rule this row already
   carried as a flex row (nowrap + the "no scrollbar on the header" reasoning
   below both still apply verbatim): */
.board-header-row {
  /* Header round 4 (2026-08-24): no longer a direct grid child -- lives
     inside `.board-header-rows` (flex column) instead. `grid-column`/`grid-
     row`/`align-self` are gone; positioning within column 2 is now entirely
     the wrapper's job. */
  display: flex;
  /* `nowrap` (was `wrap` on the old single-row header) — Florian, 2026-08-21:
     "the stats in the header break the header height when the browser is too
     narrow." Measured cause: it was never `.totals-strip` wrapping internally
     (it stayed one 16px line at every width); it was THIS row wrapping its
     children onto a second, then a third line — 88.78px @1920 -> 120.78px
     @1600-900 -> 163.56px @800. Since `updateHeaderHeightVar()` measures
     `header.board-header` into `--ceres-header-height`, every wrap also
     pushed the sticky table header down. Holding this row on one line keeps
     the height constant. This row's children are the player/reload/
     checkout dock -- none of which need to shrink or scroll, so
     nowrap is a plain guarantee here, not a squeeze-absorption mechanism. */
  flex-wrap: nowrap;
  /* No scrollbar on the header itself (Florian, 2026-08-21: "there is now a scroll
     bar in the header" — an earlier `overflow-x: auto` here was rejected on sight). */
  gap: 1rem 2rem;
  align-items: center;
  min-width: 0;
  max-width: 100%;
}

/* Header redesign, 2026-08-21 (Florian: "Clean up the header and filter row and make
   it look more coherent with the rest of the page"). Fixed left-to-right order: logo,
   version, currently-selected player, and the totals strip. `.totals-strip` gets
   `margin-inline-start: auto` so it (and everything after it) is pushed to the right
   of the player switcher, keeping the identity cluster (logo/version/player) visually
   grouped on the left the way a page title would be. */
/* `position: relative` (header-controls polish, 2026-08-21) makes this the
   containing block for `.board-version`, pinned to its bottom-right corner --
   see index.html's comment on the version span for why this box is exactly
   the logo image's own 75px-tall footprint (inline-flex + line-height:0
   below), so "bottom-right of the box" IS "bottom-right of the artwork". */
/* Item 41 redone (2026-08-24): now its own grid column, spanning BOTH header
   rows full-height (see `header.board-header`'s own comment for the mechanism)
   -- `grid-row: 1 / span 2` places it beside both rows instead of inside the
   first one. `align-self: center` keeps it at its own intrinsic 75px image
   height rather than stretching to fill the (slightly taller, due to
   row-gap) two-row box -- a stretched box would drift `.board-version`'s
   bottom-right pin (below) off the actual artwork, since that pin anchors to
   THIS box's edges, not the image's. */
/* Header round 4 (2026-08-24): `grid-row: 1 / span 2` is gone -- the header
   grid is one row now (see `header.board-header`'s own comment), and the
   header's own `align-items: center` centres this column-1 item against the
   row's height, same visual effect as the old explicit `align-self: center`
   had (kept here too, redundant but harmless, in case a future edit adds
   `align-items: stretch` back to the container). */
.board-logo { grid-column: 1; align-self: center; position: relative; display: inline-flex; align-items: center; gap: 0.4rem; line-height: 0; }
/* Raster logo, 60px -> 75px (2026-08-21 board-polish batch 19, item 11, Florian:
   "wire them in with the height of 75px"). `width: auto` preserves the source
   image's own aspect ratio at that height (scales width, never stretches); all
   four theme variants share the same 225x150 (3:2) aspect so the header's own
   width/layout doesn't shift when the image swaps (see the `content: url()`
   overrides below) -- only the swap-in-and-out of an image at the identical
   render box. */
.board-logo-img { display: block; height: 75px; width: auto; }
/* Four theme-matched logos, auto-switching (2026-08-21 board-polish batch 19,
   item 11, Florian: "four full size and four web optimized, one for each of
   Iris themes and named after them ... switch them automatically with the
   fitting theme"). Pure-CSS switch via `content: url()`, keyed off the same
   `[data-theme="..."]` attribute canonical.css itself switches on (set by
   theme.js:37/applyTheme(), and mirrored by index.html's inline pre-paint
   script from localStorage before this stylesheet's first render) -- chosen
   over a JS-driven swap because it needs no app.js hook at all: the browser
   resolves the matching rule the moment `data-theme` is set, and re-resolves
   it the instant theme.js changes the attribute on click, with zero listener
   code to write or maintain. `<img class="board-logo-img">`'s markup `src`
   (index.html) is the alchemical file -- the same one this ruleset selects
   for the default theme -- so there's nothing to swap FROM before CSS loads;
   no flash of a different logo is possible. All four `url()`s live in this
   stylesheet, so the browser fetches (and this project's HTTP cache serves)
   all four on first paint regardless of which one is initially visible --
   no per-click network fetch waits behind a theme switch, so it feels instant.
   Not a `.theme-*` selector and not a canonical.css edit -- purely reads the
   `data-theme` attribute the Iris picker already publishes, per the Iris
   consumer-protocol's read-only allowance for host CSS. Assets are the
   225x150 "-header" set (`ceres/static/assets/logos/`, 7-8.7KB each) -- the
   1536x1024 full-size set exists for future print/large-format use only and
   is never referenced here. */
[data-theme="alchemical"] .board-logo-img,
:root:not([data-theme]) .board-logo-img {
  content: url('assets/logos/ceres-logo-alchemical-header.png');
}
[data-theme="dark"] .board-logo-img {
  content: url('assets/logos/ceres-logo-dark-header.png');
}
[data-theme="light"] .board-logo-img {
  content: url('assets/logos/ceres-logo-light-header.png');
}
[data-theme="wood"] .board-logo-img {
  content: url('assets/logos/ceres-logo-wood-header.png');
}

/* Iris theme-picker dock -- see index.html's own comment on this element for the
   full mechanism. `transform` makes THIS div the fixed-position containing block for
   the vendored .theme-picker widget mounted inside it, so that widget's own
   (un-editable) `top:10px; right:0` resolve against this div's box instead of the
   viewport's. Not a .theme-* selector -- purely this project's own positioning hook,
   per the Iris consumer-protocol's "wrapper element's own layout rules" allowance.

   RETARGETED 2026-08-24 (FR-1671, header round 3): `position` changed from `absolute`
   to `fixed`. As `absolute`, this div's containing block was `.controls-row` (which
   carried `position: relative` for exactly this reason) -- so the dock, and the
   picker mounted inside it, scrolled away with the page along with everything else
   below the sticky header. Florian: "fix the theme picker in the vertical middle of
   the header but far right edge of the page like now and dont make it scroll but sit
   fixed at the same position." `position: fixed` makes the dock's containing block
   the viewport itself, so it no longer moves on scroll; `.controls-row`'s own
   `position: relative` is no longer load-bearing for this rule but is left in place
   (harmless, and removing it is out of scope for this fix).

   Vertically centred on `header.board-header` (previously `.controls-row`, the
   table's filter row, which put the picker below the header -- see app.js's
   `centerThemePicker()` for the full retargeting story). The rendered picker is
   right-anchored to the viewport through this zero-width dock, while app.js measures
   the REAL `.theme-picker` box (`getBoundingClientRect()`, no guessing) after it
   mounts and on every resize and sets only this div's `top` (now a direct
   viewport-relative fixed-position coordinate) so the picker's vertical center lands
   on the header's actual center. `top: 0; left: auto; right: 0` here is the pre-JS
   fallback: the picker is flush to the right edge at the viewport's top until the
   measured vertical position is applied. */
/* z-index 15 (2026-08-21 board-polish batch 19, item 10, Florian: "Make iris
   theme picker a higher level as the table header"). This became visible only
   once item 9 (above) gave the sticky `th` an opaque primary background --
   before that a semi-transparent header let the picker/tray show through
   regardless of stacking order, masking the bug. The sticky header element
   was `table.plot-table th` at `z-index: 5` at the time this was written;
   item 16's correction moved the sticky positioning off `th` and onto its
   new `.table-head-sticky` wrapper (`z-index: 9` since S-14340, see that rule's own
   comment) -- 15 still clears it with headroom, and still stays below
   the filter-row custom dropdown (`.csel-list`, `z-index: 30`) and the shared
   tooltip (`#ceres-tooltip`, `z-index: 1300`) -- neither of those needs to yield
   to the picker, and picking a value between the two existing neighbours (not
   a magic 9999) keeps the whole stack legible. Resulting order, low to high:
   table header 6 < picker (collapsed and tray-open share this one dock, both
   raised together) 15 < filter dropdowns 30 < tooltips 200. Per the Iris
   consumer-protocol, this is the project-owned WRAPPER's z-index, not the
   vendored `.theme-picker`/`.theme-tray` selectors themselves (untouched,
   canonical.css-owned) -- the dock's own `transform: translateZ(0)` already
   makes it a stacking context, so raising ITS z-index lifts the picker inside
   it as a unit. Note `.theme-picker` itself already carries `z-index: 10100`
   in canonical.css -- that number is a red herring for this bug: it only
   ranks `.theme-picker` against OTHER elements inside this dock's own
   stacking context (there are none), and cannot reach past the dock's
   boundary to outrank `th`'s separate context -- which is exactly why the
   fix has to live on the dock (the stacking-context root), not the picker.
   Verified with the tray open and the table scrolled (the sticky `th`
   actively pinned); did not find a viewport where the tray's own box overlaps
   `th`'s box (the tray is vertically centred over `.controls-row`, which sits
   entirely above the sticky header, not over it), so no visible glitch was
   reproducible either before or after this change at the tested sizes -- the
   z-index ordering bug is real and independently confirmed (verified by
   getComputedStyle before/after: dock's effective paint order relative to
   `th` was governed by z-index 0 vs 5 before this fix, now 15 vs 5), so the
   fix is applied and correct regardless of whether this session's viewports
   happened to expose it visually. */
.theme-picker-dock {
  position: fixed;
  top: 0;
  left: auto;
  right: 0;
  width: 0;
  height: 0;
  z-index: 1250; /* S-7560 (6): above every modal overlay (1200), below the tooltip (1300) -- the picker is always reachable */
  transform: translateZ(0);
}

/* S-14180 (owner): edge-attached tabs that open the Badges overlay and the Wallet
   drawer, styled to match the Iris theme picker's own recipe (see .theme-picker /
   .theme-picker.is-open in canonical.css and the tab-specific rules right below) --
   "a design that attaches directly sideways into the right edge of the corner...
   Same as iris, respectively." `top` is set by app.js's centerThemePicker(), from the
   same measured picker box that positions #header above; the value here is only the
   pre-JS/no-JS fallback. Desktop only (see the html.ceres-lw-le-1279 override at the
   bottom of this file) -- mobile keeps the filter sheet's own Wallet button and has no
   badge overlay at all. */
/* S-14280 (owner: "the wallet must fit perfectly into the center of the filter row"):
   originally a DOM child of `.controls-row`, using pure CSS (`top: 50%; transform:
   translateY(-50%)`) to centre on that row's own box with no JS measurement. Pre-
   landing review fix (v6 amendment): `.controls-row` is `overflow-x: auto` on
   desktop, which clipped `#edge-tabs` and scrolled it sideways with the row's own
   filter controls instead of keeping it visible at the right edge -- #edge-tabs moved
   OUT to a direct child of `<body>` (index.html), right after the row, with `body`
   itself as its `position: absolute` containing block (`body { position: relative }`,
   above) -- `top` is JS-computed (app.js::centerThemePicker(), in DOCUMENT
   coordinates so it still tracks `.controls-row`'s own position at every scroll
   offset -- see that function's own comment for the full reasoning).
   S-14380 (owner: "they are neither aligned in style nor vertically... one column,
   equal gaps") replaced this with a fixed, stacked-under-Iris strip that also held
   the Badges flag; the owner's SUBSEQUENT ruling S-14440 (verbatim: "resource ...
   slider must sit centered in the filter row") explicitly supersedes that -- the
   Wallet picker centres on `.controls-row` again exactly as this comment originally
   described, and `#edge-tabs` holds ONLY the Wallet picker now (the Badges flag
   moved back inside `#badge-bar` as `.badge-bar-head`, since S-14440 wants it
   centred on the table header instead, a different target entirely). */
.edge-tabs {
  /* S-14980 (owner: "the wallet and flag slider are still not sticky in their
     respective rows"): `position: fixed` (was `absolute`, document coordinates)
     -- `.controls-row` is now itself `position: sticky` under the page header
     (see that rule's own comment), so a document-coordinate `top` (rect.top +
     window.scrollY) would keep drifting past the row once it sticks, the same
     way `#badge-flag` would have drifted off `#table-head-sticky` before its own
     IR-6940 fix. `#edge-tabs` now reads `.controls-row`'s live, viewport-relative
     `getBoundingClientRect()` directly (app.js updateEdgeTabsPosition(), synced on
     every scroll with no rAF hop, same pattern) and writes that straight to a
     `position: fixed` `top` -- correct whether the row is mid-scroll or already
     stuck, since `getBoundingClientRect()` already reflects the sticky clamp.
     `position: fixed` also needs none of `body`'s `position: relative` containing-
     block machinery `position: absolute` depended on here (fixed always escapes
     to the viewport regardless of ancestor `position`), and, unlike `absolute`,
     is never clipped by `.controls-row`'s own `overflow-x: auto` on an ancestor --
     not that this element sits inside it any more, but it removes the entire class
     of clipping concern S-14280's original "moved OUT to `<body>`" fix existed for. */
  position: fixed;
  right: 0;
  z-index: 40; /* IR-6930 (owner: "when the wallet slider scrolls up, it must be
     behind the header and never above it"): lowered from 40 to 20 at the time,
     below the header (now 50) and above the sticky table head (9). S-14980 (owner:
     "the wallet and flag slider are still not sticky in their respective rows")
     raised it BACK to 40: `.controls-row` (the filter row) is now itself sticky at
     z-index 30 (that rule's own comment) -- MEASURED, at 20 the sticky row painted
     OVER the fixed wallet pill and intercepted its clicks (elementFromPoint on the
     toggle returned the row, not the toggle) once both were stuck in the same band.
     40 keeps the wallet clickable above its own row while staying below the Iris
     picker (10100) and the header (50) -- see the header's own comment and the
     ladder note near .dec-drawer below. */
  display: flex;
  flex-direction: column;
  align-items: flex-end; /* S-14250: the picker grows leftwards on open; keep the column right-anchored */
  gap: 0.4rem;
  /* S-14580 (owner, verbatim: "the wallet slider must not be visible during the
     animation at page load"): hidden by default, pre-JS -- no `transition` here
     on purpose, there is nothing to animate FROM until app.js deliberately
     reveals it (`.edge-tabs-revealed`, below) once its position has been
     computed at least once AND the board's own reveal has finished (see that
     class's own comment, and app.js::maybeRevealEdgeTabs()). `visibility`
     (not just `opacity`) keeps it out of the a11y tree and un-clickable while
     hidden, same reasoning `.scroll-top`'s own `opacity: 0` uses `pointer-
     events: none` for. */
  visibility: hidden;
  opacity: 0;
}
/* S-14580: the reveal -- originally Iris's own `fadeInRight` timing (canonical.css),
   copied under a new name (`.theme-*`/its own keyframe are Iris's vendored surface,
   consumers may not reuse or redefine them per the consumer protocol -- see the
   `.edge-picker`/`.edge-toggle` block's own header comment for the same rule
   applied to class names). `app.js::maybeRevealEdgeTabs()` adds this class once,
   never removes it (the reveal is a one-time page-load event, not a toggle).
   IR-6900: the copy also carried Iris's own `translateX(20px)->0` slide, but
   Iris's vendored `.theme-picker` runs that same slide over 1s while this copy
   ran it over .6s, and the two are never started at the same instant either
   (`.theme-picker` mounts as soon as `ready()` fires; `#edge-tabs`/`#badge-flag`
   only reveal once the board itself has rendered) -- for a real, reproducible
   stretch of the page load (measured: up to ~20px, occasionally enough to push
   `#badge-flag`'s centre past the viewport edge so `elementFromPoint` misses it
   entirely) the Wallet/Flag were still physically mid-slide while Iris had
   already settled, landing them visibly right of Iris exactly as reported.
   Dropped the `transform` here -- the opacity fade alone still reads as a
   reveal, and these two elements are JS-positioned (`centerThemePicker()`/
   `updateBadgeFlagPosition()`) to already sit exactly where they belong before
   this class is ever added, so there is nothing left for a slide to usefully
   do, and nothing left that can drift out from under Iris while animating. */
@keyframes edgeTabsFadeInRight {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.edge-tabs-revealed {
  visibility: visible;
  opacity: 1;
  animation: edgeTabsFadeInRight .6s 1 cubic-bezier(.77, 0, .175, 1);
}
/* S-14210: mirrors canonical.css .theme-picker (resting state) -- one shared look for
   #badge-bar and (via the S-14250 picker rules below) the Wallet edge picker, so all
   three right-edge slide-outs (Iris picker included) read as one design. Only the
   resting-state chrome is shared here; each element's own size/position/open-state
   stays on its own rule below.
   S-14440 z-fix, round 2 (owner-approved: avoid the z-cycle instead of accepting the
   trade-off): `background`/`backdrop-filter` moved OFF `.badge-bar` itself onto a
   `.badge-bar::before` pseudo-element (see that rule, near `.badge-bar`'s own
   geometry rule) -- `backdrop-filter` is what forced `.badge-bar` to establish its
   own stacking context regardless of z-index (see that rule's own z-ladder comment
   for the full story); moving it to a `content: ''; position: absolute; inset: 0`
   pseudo-element gives the glass a home that doesn't force anything on the bar
   itself, so the bar's real children (`.badge-bar-head`/`.badge-bar-row`) compete
   DIRECTLY in the scroller's own shared context instead. `border`/`border-radius`
   stay here -- they don't force a stacking context either way, no reason to move. */
.badge-bar {
  border: 0;
  border-radius: 5px 0 0 5px;
}
/* S-14250 (owner): "Make the top of the wallet mirror exactly the icon only
   slide-out of Iris." These replicate canonical.css's `.theme-picker`/`.theme-tray`/
   `.theme-opt`/`.theme-toggle` VALUES under new class names -- the Iris consumer
   protocol forbids redefining those class names in a consumer stylesheet, so the
   look is copied rather than shared. `.edge-toggle` below carries `.theme-toggle`'s
   own chip look (background/radius/box-shadow), not just its sizing. */
.edge-picker {
  display: flex;
  flex-direction: row;
  align-items: center;
  /* S-14680 measured fix: `min-inline-size`/`inline-size` on a `content-box`
     element (the default, and this element's own previous box-sizing) apply
     to the CONTENT area only, excluding padding -- with the picker's own 4px
     left padding, `min-inline-size: var(--edge-w)` (32px) produced a 36px
     TOTAL rendered width (4 padding + 32 content-min), 4px wider than Iris's
     own real 32px pill. `border-box` makes `--edge-w` describe the WHOLE
     box (padding included), matching what the token is actually meant to
     mean everywhere else it's used (`.badge-bar-head` below, `.badge-bar`'s
     own `--badge-bar-w`) and what the owner's own "outer boxes equal width"
     requirement (S-14680) needs. */
  box-sizing: border-box;
  /* S-14280 (owner: "slightly smaller in height"): the Iris picker (canonical.css
     .theme-picker) stays 48px, vendored and untouched -- the wallet's own picker is
     40px. */
  height: 40px;
  /* S-14550 (owner, verbatim: "the wallet slider is missing the background being
     larger top and bottom... mirror iris"): `.edge-toggle` (below) shrank from
     `calc(var(--edge-w) - 4px)` (the picker's own full remaining content width) to a
     fixed 32px, so the picker's own CLOSED-state content (4px left padding + 0px
     tray, max-width: 0 -- + 32px toggle = 36px) would otherwise be narrower than
     `--edge-w` (48px), breaking the "same width as the Iris dock/#badge-bar" S-14280
     alignment. `min-inline-size` (not a plain `inline-size`) is deliberate: it only
     ever WIDENS the closed picker up to `--edge-w`, and has no effect once the tray
     opens and its own real content (`.edge-opt` row width, S-14560-frozen) already
     exceeds that minimum -- the open-state tray sizing this rule must not touch is
     untouched. */
  min-inline-size: var(--edge-w);
  /* S-14680 (owner, verbatim: "But all icons and borders in the slider are
     unaligned"): padding/radius read from Iris's own LIVE
     measurement now (app.js::centerThemePicker()) instead of a hand-typed
     copy of its canonical.css values -- see that function's own comment for
     the full reasoning. Fallback defaults match the PREVIOUS hardcoded
     values exactly, so pre-JS layout (before the first measurement pass)
     renders identically to before this ticket. */
  padding: 0 var(--edge-pad-right, 0px) 0 var(--edge-pad-left, 4px);
  border: 0;
  border-radius: var(--edge-radius-outer, 5px 0 0 5px);
  background: color-mix(in srgb, var(--shadow) 40%, transparent);
  backdrop-filter: saturate(120%) blur(2px);
  -webkit-backdrop-filter: saturate(120%) blur(2px);
  overflow: hidden;
  cursor: pointer;
  transition: background .3s ease;
}
.edge-picker[data-open="true"] {
  background: color-mix(in srgb, var(--shadow) 82%, transparent);
  backdrop-filter: saturate(130%) blur(4px);
  -webkit-backdrop-filter: saturate(130%) blur(4px);
}
/* IR-7540f: no hover/focus glow here -- it repainted the glass wallet panel and flickered as a hard
   shadow box under the slider (owner found it, IR-7540c). Keyboard focus keeps the global focus ring. */
/* S-16891 (owner: "all slide in buttons must get clear hover glow effects like Iris. Zero flicker or hidden shadows
   that are displaced."): the Iris glow (0 0 18px -3px --primary) on the wallet button, drawn ONCE on its own layer
   around the button (#edge-tabs holds only the wallet button, so this layer outlines exactly it, open or closed) and
   only faded in/out by opacity -- the glass button and panel are never repainted, so nothing can flicker (S-16878 M-4). */
.edge-tabs::after {
  content: ''; position: absolute; inset: 0; border-radius: 5px 0 0 5px;
  box-shadow: 0 0 18px -3px var(--primary); opacity: 0; transition: opacity .3s ease; pointer-events: none;
}
.edge-tabs:hover::after, .edge-tabs:focus-within::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .edge-tabs::after { transition: none; } }
.edge-tray {
  display: flex;
  align-items: center;
  max-width: 0;
  overflow: hidden;
  transition: max-width .5s cubic-bezier(.4, .8, .4, 1);
}
.edge-picker[data-open="true"] .edge-tray { max-width: 20rem; }
.edge-opt {
  /* S-14280 (owner: "slightly smaller in height"): shorter than the 40px toggle so the
     open tray reads as a lighter secondary row under it. */
  height: 34px;
  min-height: 34px;
  background: none;
  border: 0;
  color: var(--body);
  cursor: pointer;
  padding: 0 8px;
  opacity: .4;
  transition: opacity .2s ease;
  /* Pre-landing review fix: on EVERY option, not just `.edge-opt--active` -- an
     icon-only flex/align rule set on just the active variant left the icon sitting at
     the button's default (top) alignment on every option BUT the active one, an
     inconsistency that only showed once a resource was actually selected. All options
     now centre their icon identically, selected or not. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.edge-opt:hover { opacity: .8; }
/* S-14390 (owner): "add a slide in chevron to the left" -- a non-resource option
   prepended to the tray that only closes it. Reuses the shared `.ceres-chevron`
   disclosure-arrow mask (buttons.css, native `--ceres-chevron-art`, points down at
   rotate(0)) rotated -90deg to point right, towards the toggle handle it collapses
   into. It never carries `.edge-opt--active` (no `data-symbol`, so
   updateWalletTrayActive()'s comparison against it is always false). */
.edge-tray-chevron .ceres-chevron { transform: rotate(-90deg); }
/* S-14560 (owner ruling, verbatim: "the inside slide wallet that slides out looks
   perfect, dont touch that") froze `.edge-tray`/`.edge-opt` (above) -- this rule and
   the scoped override just below it are the one exception the owner's own follow-up
   ticket (IR-6770) explicitly asked for. Historical note (pre-landing review fix,
   now REVERSED): unlike Iris's own `.theme-opt--active` (which HIDES the selected
   option -- Iris always shows it separately as the toggle glyph), this project used
   to keep every resource reachable from the tray, including the selected one (marked
   by this rule's own opacity/background/box-shadow chip, S-14310). IR-6770 (owner,
   verbatim: "bug: the resource slide-out double the selected resource. iris handles
   that better") reverses that: the selected option now also HIDES in the tray, same
   as Iris -- it stays reachable via the toggle itself (S-14390: clicking the toggle
   while the tray is open opens ITS wallet). This rule (bare `.edge-opt--active`) is
   kept, UNUSED while hidden, only so a future context that reuses the class (none
   currently does, `regional-dec.js` only ever applies it inside `.edge-tray`) is not
   surprised by its absence -- the actual hide lives in the scoped rule below, which
   mirrors canonical.css's own `.theme-tray .theme-opt--active { display: none; }`
   exactly (selector shape and all). */
.edge-opt--active {
  opacity: 1;
  background: color-mix(in srgb, var(--primary) 22%, transparent);
  border-radius: 5px;
  box-shadow: 0 8px 22px color-mix(in srgb, var(--shadow) 35%, transparent), inset 0 1px 0 color-mix(in srgb, var(--hi) 6%, transparent);
}
.edge-tray .edge-opt--active { display: none; }
/* Pre-landing review fix: replicates canonical.css's `.theme-toggle` chip look
   (background/radius/box-shadow), not only its sizing -- the toggle is its own
   visible surface inside the picker, same as Iris's.
   S-14550/S-14570 (owner, verbatim: "the wallet slider is missing the background
   being larger top and bottom. for that the inside must be smaller because the
   filter row is not high enough. mirror iris." / "the background for the selected
   resource is too large"): the toggle used to be `inline-size: calc(var(--edge-w) -
   4px); block-size: 40px` -- the SAME size as `.edge-picker` itself (above), leaving
   no room for the pill's own glass to show as a frame around it, and making the
   primary-tint chip below fill the whole pill instead of hugging the icon. Iris's
   own real recipe is a 48px pill framing a 40px toggle (canonical.css) -- 4px of
   glass top/bottom; ours mirrors that PROPORTIONALLY for a shorter, filter-row-sized
   40px pill: a 32px toggle, the same 4px top/bottom margin Iris's own ratio gives
   (via `.edge-picker`'s own `align-items: center`, no padding change needed -- see
   that rule's own comment). `margin-inline-start: auto` pushes the now-smaller
   toggle to the picker's own right edge (flush, matching Iris and the old
   full-width toggle's own resting position) instead of leaving it stranded at the
   LEFT of the picker's newly wider (`min-inline-size`, `.edge-picker` above) empty
   closed-state box. The chip (background/box-shadow below) now hugs exactly this
   32px box instead of the full pill -- S-14570's own complaint resolved as a direct
   consequence, no separate chip rule needed. */
/* S-14680: size/radius/shadow all read from Iris's own LIVE `.theme-toggle`
   measurement (`--edge-chip-w`/`--edge-chip-h`/`--edge-radius-inner`/
   `--edge-chip-shadow`, app.js::centerThemePicker()) instead of hand-typed
   copies -- fallback
   defaults match the PREVIOUS hardcoded values (32px / the S-14550 radius /
   the exact canonical.css `.theme-toggle` shadow this already copied by
   hand).
   IR-6810 (owner, verbatim: "alignment fix not visible in latest version with
   hard refresh" -- root cause: Windows renders the Iris toggle's emoji via
   Segoe UI Emoji, a much larger glyph than headless Chromium's own, arriving
   late once that font loads). Team-lead correction: the owner's complaint is
   INNER *and* OUTER borders aligned -- capping BOTH dimensions at 32px left
   the chip's own left/right edges (its width) misaligned with Iris's real,
   wider chip whenever Iris measures larger. WIDTH is `var(--edge-chip-w,
   32px)` with no ceiling -- it always matches Iris's own chip width exactly,
   so the chip's own side borders line up with Iris's regardless of how large
   Iris's toggle gets. HEIGHT alone is capped, via `min(var(--edge-chip-h,
   32px), calc(40px - 2 * var(--edge-pad-left, 4px)))` -- MEASURED: Iris's own
   `.theme-toggle` is NOT square (canonical.css fixes its own `height: 40px`
   literal while its WIDTH varies with content), so width and height are two
   separate custom properties (`--edge-chip-w`/`--edge-chip-h`), not one
   shared square value. The Wallet pill's own height is FIXED at 40px
   (S-14550, "the filter row is not high enough"), so a chip taller than
   `40px - 2 * the framing margin` would eat into or exceed the pill's own
   edges; `--edge-pad-left` (Iris's own real measured left padding, reused
   here as the generic framing-margin unit, since no dedicated vertical
   padding token exists) keeps that ceiling derived from Iris's own live
   measurement too, not a second hardcoded literal alongside the `40px` pill
   height. `align-items: center` on `.edge-picker` still vertically centres
   whatever height the chip actually ends up at. */
.edge-toggle {
  inline-size: var(--edge-chip-w, 32px);
  block-size: min(var(--edge-chip-h, 32px), calc(40px - 2 * var(--edge-pad-left, 4px)));
  margin-inline-start: auto;
  border: 0;
  background: color-mix(in srgb, var(--primary) 22%, transparent);
  border-radius: var(--edge-radius-inner, 5px 0 0 5px);
  box-shadow: var(--edge-chip-shadow, 0 8px 22px color-mix(in srgb, var(--shadow) 35%, transparent), inset 0 1px 0 color-mix(in srgb, var(--hi) 6%, transparent));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.edge-toggle img { inline-size: 18px; block-size: 18px; object-fit: contain; }
/* S-14280: 18px, down from 22px, to match the tray's own smaller 34px option height. */
.edge-opt img { inline-size: 18px; block-size: 18px; object-fit: contain; }
/* S-16903 (owner: "make the wallet and flag badges higher, so that only 1 px top and bottom each is left in their
   respective rows ... making [the icons] bigger"): desktop only. app.js sets --edge-row-h to the controls row's height
   minus its 1px border and 2px top and bottom; the wallet box, its chip and the tray options follow it. The flag keeps 2px
   free above and 3px below (the header's 1px shadow plus 2px), app.js updateBadgeFlagPosition. */
html:not(.ceres-lw-le-1279) #wallet-picker { height: var(--edge-row-h, 40px); }
html:not(.ceres-lw-le-1279) #wallet-edge-tab { block-size: calc(var(--edge-row-h, 40px) - 2 * var(--edge-pad-left, 4px)); }
html:not(.ceres-lw-le-1279) #wallet-picker .edge-opt { height: calc(var(--edge-row-h, 40px) - 2 * var(--edge-pad-left, 4px)); min-height: 0; }
html:not(.ceres-lw-le-1279) #wallet-picker :is(.edge-toggle, .edge-opt) img { inline-size: 26px; block-size: 26px; }

/* `.badge-flag` (S-14380 measured follow-up: a standalone fixed handle in
   `#edge-tabs`) was RETIRED by S-14440, then REINSTATED by IR-6730 as `#badge-flag`
   -- see `.badge-bar-head`'s own rule (near `.badge-bar`) for the current design;
   it is no longer nested in `#edge-tabs`, a separate `<body>` child now. */
/* S-14460 (owner, verbatim: "add a scroll up button to the bottom right in the iris
   style") -- a fourth standalone fixed handle, pinned to the bottom-right corner
   instead of stacked with the others. Carries `.edge-toggle` for its shared
   40px/radius/cursor shape, and this rule overrides the two properties that assumed
   `.edge-toggle`'s usual `.edge-picker` padding context (`inline-size: calc(var(
   --edge-w) - 4px)`, `background`/`box-shadow`: the primary-tint TOGGLE chip look) --
   the owner asked for "the iris style" here, meaning the RESTING PILL recipe
   (`.edge-picker`'s own values: shadow-40% glass + the ambient primary glow), not
   the toggle's own chip look, since this handle is not nested inside a picker at
   all.
   Measured finding (2026-09-20): sharing the strip's own `--edge-w` column (`right:
   gutter`, same as the Iris dock/#edge-tabs) put this button directly over
   `#badge-bar`'s own bottom two rows while visible -- their dots hidden, their
   clicks taken (the button, `bottom: 1rem`, and the bar's own bottom edge occupy
   the same screen region at the bottom of the viewport). Fix: `right` is one whole
   handle-width plus an 8px gap, LEFT of the strip's own column, so this button can
   never share a column (and therefore never overlap) with `#badge-bar` at any
   scroll position. Still reads as "bottom right" per the owner's own wording -- it
   is the rightmost handle immediately beside the strip itself, not moved away from
   the corner.
   IR-6730 (owner, verbatim, high: "all three sliders must sit on the furthest
   right edge of the page. zero gap."): `right` is now a plain, STATIC CSS value
   (`calc(var(--edge-w) + 8px)`) instead of a JS-written gutter-based one -- the
   strip itself is flush at `right: 0` now (no JS-computed gutter anywhere any
   more, see app.js::centerThemePicker()'s own comment), so this button's own
   offset from it needs no live measurement either, just the same fixed formula.
   Hidden by default (`opacity: 0; pointer-events: none`) -- a passive,
   rAF-throttled scroll listener (app.js) toggles `.is-visible` once `scrollY > 400`,
   fading it in/out over .3s; `prefers-reduced-motion` drops the fade, not the
   show/hide itself (an accessibility affordance staying invisible-but-clickable
   would be worse than an instant appearance). */
/* IR-6730 (owner, verbatim, high: "all three sliders must sit on the furthest
   right edge of the page. zero gap."): team-lead correction -- the owner's "all
   three sliders" are the Iris dock, the wallet picker (#edge-tabs) and the
   badges flag/bar; `#scroll-top` is NOT one of them, and sharing the bar's own
   column re-creates the S-14460 measured overlap of the bar's bottom rows. Back
   to one handle-width left of the bar -- `right: calc(var(--badge-bar-w) + 8px)`
   (was `calc(var(--edge-w) + 8px)` pre-IR-6730, when the bar's own width WAS
   `--edge-w`; the bar shrank to `--badge-bar-w` under this same ticket, so the
   offset formula tracks that, not the wider handle width). S-14770: `--badge-bar-w`
   now equals `--edge-w` again (a live tracking token, not a literal), so this
   offset and the bar's own `inline-size` line up automatically -- no change
   needed here. */
.scroll-top {
  position: fixed;
  bottom: env(safe-area-inset-bottom, 0px);
  right: calc(var(--badge-bar-w) + 8px);
  inline-size: var(--edge-w);
  border-radius: 5px 5px 0 0;
  background: color-mix(in srgb, var(--shadow) 40%, transparent);
  backdrop-filter: saturate(120%) blur(2px);
  -webkit-backdrop-filter: saturate(120%) blur(2px);
  box-shadow: 0 0 18px -6px var(--primary);
  z-index: 40;
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s ease, box-shadow .2s ease;
}
.scroll-top.is-visible { opacity: 1; pointer-events: auto; }
.scroll-top:hover, .scroll-top:focus-visible { box-shadow: 0 0 18px -3px var(--primary); }
.scroll-top::before {
  content: '';
  display: inline-block;
  inline-size: 0.9rem; block-size: 0.9rem;
  background: var(--body);
  -webkit-mask: var(--ceres-chevron-art) center / contain no-repeat;
  mask: var(--ceres-chevron-art) center / contain no-repeat;
  transform: rotate(180deg); /* native chevron points down; 180deg points up */
}
@media (prefers-reduced-motion: reduce) {
  .edge-tray { transition: none; }
  .scroll-top { transition: none; }
  #edge-tabs.edge-tabs-revealed { animation: none; } /* S-14580 */
  #badge-bar.badge-bar-revealed, #badge-flag.badge-flag-revealed { animation: none; } /* IR-6910: id-qualified so this override always outranks the later un-scoped .badge-bar-revealed/.badge-flag-revealed rules, regardless of source order */
}

/* S-5880: the official Splinterlands spinner (assets/ui/loading-spinner.gif, S-5870) replaces the CSS ring. */
.spinner {
  display: inline-block;
  width: 2em;
  height: 2em;
  background: url(/static/assets/ui/loading-spinner.gif) center / contain no-repeat;
  vertical-align: -0.55em;
}
.spinner-sm { width: 1.8em; height: 1.8em; }

/* R-145 / S-198 — "Sixfold Harvest". His words on the box this replaces: "the ugly
   spinner in the ugly stretched out box that sticks under the filter row. Make it big
   anf full page, centered. Harvest themed."
   IT WAS "STRETCHED OUT" because it spanned the full 1920 for a spinner and one line of
   text, and it "STUCK" because it occupied flow space -- padding, border, a bottom
   margin -- so the plot table was shoved down every time it appeared and snapped back
   when it went. `position: fixed` removes that shift entirely: this layer has a visual
   size but contributes ZERO height to #board-main, so the virtualised table never moves.
   GLOBAL COLOURS ONLY, his instruction: "Use global colors to allow a clean
   auto-adjustment to the elected Iris theme." The approved mockup carried four
   hardcoded hexes (#f1cc756b, #e1bc4629, #e1bc4600, #0004) inside a design that
   otherwise used tokens correctly -- precisely the values that would NOT have followed
   a theme change, leaving a dark-theme amber glow over the pale light and wood grounds.
   Every colour below resolves from canonical.css.
   S-220, his ruling: "use primary color for the loading animation. It is a golden
   theme now." It was moved to --gold an hour earlier on the argument that he had
   approved gold frames -- true, and beside the point: --primary IS #c9a84c in
   alchemical, the same gold, so he keeps the frames he approved AND the loader
   follows the theme. It also restores coherence with S-196, which moved the header
   buttons to --primary precisely because --gold barely changes between themes while
   --primary genuinely does; on --gold the two surfaces would have drifted apart in
   three of his four themes.
   SUPERSEDED REASONING KEPT AS HISTORY -- THE GLOW WAS --gold, NOT --primary. Verification caught this and it matters: --primary
   is violet in the dark theme and green in wood, so a "harvest light" built on it would
   have pulsed violet around the grain. HE APPROVED A PICTURE, and every frame he saw was
   gold -- shipping the theme's generic UI accent here would ship something he never saw.
   His --primary ruling (S-196) was about the HEADER BUTTONS, where the point was that the
   button belongs to each of his four logos; a harvest glow has the opposite requirement.
   z-index 7 is deliberate and sits in the existing ladder: sticky table head is 9 (S-14340; expanded plot 8), scroll
   proxy 6, sticky filter row 30 (`.controls-row`, S-14980, was `position: relative`,
   not in the ladder at all), wallet stack 40/39 (#edge-tabs/.dec-drawer, S-14980,
   was 20/19 under IR-6930, was 40/39 before that -- MEASURED back up to 40/39
   because at 20 the now-sticky filter row (30) painted over and intercepted clicks
   on the fixed wallet pill), the header 50 (:51, IR-6930, was 10), theme dock 1250
   (:301, the Iris picker's OWN dock, vendored, untouched throughout), dropdowns 30
   (:1877, a SEPARATE stacking context -- `.controls-row`'s own children, competing
   only among themselves inside its z-index-30 context, never against this outer
   ladder), tooltips 200 (:2008). So this covers the table and never the header, the
   picker or a dropdown -- it is a passive status surface, not a modal. */
.board-loading:not([hidden]) { display: grid; }
.board-loading {
  position: fixed;
  inset: 0;
  /* IR-7570 (owner decision 2026-09-22, supersedes R-147): "the loading animation of the page must have the highest
     possible z-index and no single element must be on top of it. The background must be non-transparent and nothing
     below it must leak through." The layer sits above everything -- header, wallet, Iris, flag, tooltips -- and its
     veil (::before) covers the whole viewport, header included, fully opaque. The artwork keeps its place below the
     header band (the geometry below is unchanged). */
  z-index: 2147483000;
  /* Center the artwork below the header; reserve matching room on both sides
     for the status line so short landscape windows keep the full animation. */
  --loading-viewport: calc(var(--ceres-vh) * 100);
  --loading-room: max(0px, calc(var(--loading-viewport) - var(--hdr-h, 80px)));
  --harvest-s: min(44rem, calc(var(--ceres-vw) * 80), calc(var(--ceres-vh) * 74), max(0px, calc(var(--loading-room) - 8rem)));
  inset-block-start: var(--hdr-h, 80px);
  block-size: var(--loading-room);
  grid-template-rows: 1fr auto 1fr;
  grid-template-columns: 100%;
  margin: 0;
  padding: 0;
  align-content: stretch;
  justify-items: center;
  row-gap: 0;
  border: 0;
  background: none;
  color: var(--text);
  pointer-events: none;
}
/* The veil as its own painted box, so it can stop exactly where the header starts. S-16829b keeps it semi-transparent
   over the loading artwork; IR-7570's "nothing below it must leak through" is met by hiding the page chrome while loading. */
.board-loading::before {
  --loading-veil-opacity: 82%;
  content: ''; position: fixed;
  inset: calc(var(--hdr-h, 80px) + 1px) 0 0 0;
  background: color-mix(in srgb, var(--bg) var(--loading-veil-opacity), transparent);
  z-index: -1; pointer-events: none;
}
/* IR-7570: while the board loads nothing of the page sits on top of the animation or shows through its veil -- the wallet
   button, the badge column, flag and tray, scroll-to-top, the filter row and the table are hidden until it is done
   (the header stays as S-16829b set it). */
body:has(#board-loading:not([hidden]):not(.is-done)) :is(#edge-tabs, #badge-bar, #badge-flag, #badge-filter-tray, .scroll-top, .controls-row, #plot-table-scroll) { visibility: hidden; }
@supports (height: 100dvh) {
  .board-loading { --loading-viewport: 100dvh; }
}
.board-loading > .harvest-stage { grid-row: 2; }
.board-loading .board-loading-text {
  grid-row: 3;
  align-self: start;
  margin: 1.3rem 1rem 0;
  text-align: center;
  font-size: 1.02rem; letter-spacing: 0.04em; color: var(--body);
  text-shadow: 0 1px 3px var(--shadow);
}
.harvest-stage {
  /* IR-2112, his report: "the central circle is not a circle at all". The first version
     paired `aspect-ratio: 1/1` with a viewport-relative max block size -- and a max- clamp is a
     DEFINITE size, so it overrides the ratio rather than co-operating with it. At
     1920x1080 the stage measured 736x670, which turned the seal's `border-radius: 50%`
     into an ellipse and put the six stations on an oval.
     The size is now a single value used for BOTH axes, so the box cannot be anything but
     square whatever the viewport does. */
  position: relative;
  /* The loading overlay supplies its available space; the fallback serves
     no-land/start-page stages, which size themselves below. */
  inline-size: var(--harvest-s, min(44rem, calc(var(--ceres-vw) * 80), calc(var(--ceres-vh) * 74)));
  block-size: var(--harvest-s, min(44rem, calc(var(--ceres-vw) * 80), calc(var(--ceres-vh) * 74)));
}
/* The constellation: rings and spokes joining the six stations to the seal, from the
   approved mockup. Token-coloured like everything else here, and non-interactive. */
.harvest-progress {
  stroke: var(--primary); stroke-width: 4; stroke-linecap: round;
  /* pathLength=1000 makes the dash arithmetic independent of the radius. */
  stroke-dasharray: calc(var(--harvest-progress, 0) * 1000) 1000;
  transition: stroke-dasharray 0.45s ease-out;
  opacity: 0.95;
}
/* Before a total is known there is no fraction to draw. A zero-length arc would read
   as "nothing has happened"; the plain ring carries the waiting state instead. */
.board-loading[data-progress="indeterminate"] .harvest-progress { display: none; }
.harvest-paths {
  position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
  fill: none; stroke: color-mix(in srgb, var(--primary) 34%, transparent);
  stroke-width: 1.5; pointer-events: none;
}
/* The centre: a real Praetoria plate under the real grain worksite, both already
   shipped in assets/ and both gitignored -- IR-2106 -- which is why this had to be
   built in the main checkout rather than a lane worktree. */
.harvest-seal {
  position: absolute; inset: 27%; border-radius: 50%; overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--primary) 45%, transparent);
  background: var(--bg2);
  box-shadow: 0 0 0 12px color-mix(in srgb, var(--primary) 10%, transparent),
              0 0 0 13px color-mix(in srgb, var(--primary) 22%, transparent),
              0 18px 48px var(--shadow);
}
/* Preserve the plates' shared authored canvas; crop the pair around the farm.
   19.76% is the Grain plate's visible artwork centre in the original scene. */
.harvest-scene {
  position: absolute; top: 0; left: 50%; block-size: 100%;
  inline-size: calc(100% * 2829 / 1080);
  transform: translateX(-19.76%); pointer-events: none;
}
.harvest-scene .harvest-land,
.harvest-scene .harvest-farm {
  position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
}
.harvest-glow {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: radial-gradient(ellipse at 50% 18%,
              color-mix(in srgb, var(--primary) 42%, transparent), transparent 68%);
  animation: harvest-breathe 6s ease-in-out infinite;
}
@keyframes harvest-breathe { 0%, 100% { opacity: 0.16; } 50% { opacity: 0.58; } }
/* Fill and leading edge share one transform, so skipped frames cannot separate them.
   The seal clips the rectangular moving layer to its circular boundary. */
.harvest-fill {
  --harvest-edge-exit: 48px;
  position: absolute; inset: 0 0 calc(-1 * var(--harvest-edge-exit)); pointer-events: none;
  transform: translateY(calc((1 - var(--harvest-fill, 0)) * (100% - var(--harvest-edge-exit))));
  transition: transform 650ms cubic-bezier(0.2, 0.65, 0.3, 1);
  background: linear-gradient(to top,
              color-mix(in srgb, var(--primary) 48%, transparent),
              color-mix(in srgb, var(--primary) 24%, transparent));
  box-shadow: inset 0 0 32px color-mix(in srgb, var(--primary) 45%, transparent);
}
.board-loading .harvest-fill::after {
  content: ''; position: absolute; inset-inline: 0; top: 0;
  block-size: 2px; pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--primary) 18%, var(--hi) 50%, var(--primary) 82%, transparent);
  box-shadow: 0 0 9px 2px color-mix(in srgb, var(--primary) 70%, transparent),
              0 4px 20px color-mix(in srgb, var(--primary) 55%, transparent);
}
/* Handover: the whole panel fades out over 300 ms once board.js marks it done; the
   `hidden` attribute follows after the fade (the display switch stays in board.js). */
.board-loading { transition: opacity 300ms ease-out; }
.board-loading.is-done { opacity: 0; }
/* S-7620 (v0.9.376): the ready signal's own sweep to 1 -- one 400 ms ease, not a tick step. */
/* Carry the surface glow fully past the rim; the extra depth keeps the seal filled. */
.board-loading.is-ready .harvest-fill {
  transform: translateY(calc(-1 * var(--harvest-edge-exit)));
  transition: transform 400ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .harvest-fill, .board-loading.is-ready .harvest-fill { transition: none; }
}

/* The six stations hold their places -- nothing orbits. A soft emphasis passes between
   them on a 6s loop, one sixth each, which is decoration and not a progress claim:
   no station is a stage, a completed job or a percentage. */
.harvest-node {
  position: absolute; inline-size: 22%; aspect-ratio: 1 / 1;
  left: var(--hx); top: var(--hy); transform: translate(-50%, -50%);
  display: grid; place-items: center; border-radius: 50%;
  background: radial-gradient(circle,
              color-mix(in srgb, var(--primary) 8%, var(--bg2)), var(--bg));
  border: 1px solid color-mix(in srgb, var(--primary) 32%, transparent);
}
.harvest-node::after {
  content: ''; position: absolute; inset: -9px; border-radius: inherit;
  border: 1px solid color-mix(in srgb, var(--primary) 62%, transparent);
  background: radial-gradient(circle,
              color-mix(in srgb, var(--primary) 16%, transparent), transparent 72%);
  box-shadow: 0 0 34px 2px color-mix(in srgb, var(--primary) 20%, transparent),
              inset 0 0 22px color-mix(in srgb, var(--primary) 12%, transparent);
  animation: harvest-station 6s ease-in-out infinite;
  animation-delay: var(--hd);
}
.harvest-node img {
  inline-size: 68%; block-size: 68%; object-fit: contain;
  filter: drop-shadow(0 6px 6px var(--shadow));
}
@keyframes harvest-station { 0%, 16.66% { opacity: 0.92; } 33.33%, 83.33% { opacity: 0; } 100% { opacity: 0.92; } }
/* S-7620 (IR-5340 phase 2), his words: "make the outer resource bubbles start glowing stronger
   and permanently one by one in a timed manner". v0.9.381, his words on v0.9.378: "never tied
   to progress. All six flash up as soon as the board has all data and is rendered". board.js
   runs a continuous clockwise flash-up (is-pulsing, one bubble at a time) while the board loads;
   on the rendered signal all six flash up at once and get is-lit.
   A lit station holds the glow the pulse peaks at and stops the decorative pass. */
#board-loading .harvest-node.is-lit::after {
  animation: none; opacity: 1;
  border-color: color-mix(in srgb, var(--primary) 85%, transparent);
  box-shadow: 0 0 40px 4px color-mix(in srgb, var(--primary) 34%, transparent),
              inset 0 0 22px color-mix(in srgb, var(--primary) 18%, transparent);
}
#board-loading .harvest-node.is-lit img {
  filter: drop-shadow(0 6px 6px var(--shadow)) drop-shadow(0 0 10px color-mix(in srgb, var(--primary) 70%, transparent)) brightness(1.2);
}

/* S-7620 (v0.9.371): handover lights all six regardless of the last phase seen. */
#board-loading.is-done .harvest-node::after {
  animation: none; opacity: 1;
  border-color: color-mix(in srgb, var(--primary) 85%, transparent);
  box-shadow: 0 0 40px 4px color-mix(in srgb, var(--primary) 34%, transparent),
              inset 0 0 22px color-mix(in srgb, var(--primary) 18%, transparent);
}
#board-loading.is-done .harvest-node img {
  filter: drop-shadow(0 6px 6px var(--shadow)) drop-shadow(0 0 10px color-mix(in srgb, var(--primary) 70%, transparent)) brightness(1.2);
}
/* S-7620 (v0.9.371): a newly lit station pulses ONCE -- a short bright dash runs down its
   spoke toward the seal while the station itself brightens for 600 ms (class is-pulsing,
   set by board.js on the measured phase change, removed on animationend). --ha is the
   station's own bearing from the centre, so rotate() points the dash inward.
   v0.9.372: board.js queues the pulses 140 ms apart in station order; v0.9.381: the same
   pulse is the loading cycle's flash (160 ms apart, clockwise, looping) and the concluding
   flash-up of all six at once on the rendered signal, which the handover fade waits for. */
.harvest-node[data-node="1"] { --ha: 0deg; }
.harvest-node[data-node="2"] { --ha: 60deg; }
.harvest-node[data-node="3"] { --ha: 120deg; }
.harvest-node[data-node="4"] { --ha: 180deg; }
.harvest-node[data-node="5"] { --ha: 240deg; }
.harvest-node[data-node="6"] { --ha: 300deg; }
.harvest-node::before {
  content: ''; position: absolute; left: 50%; top: 50%;
  inline-size: 4px; block-size: 26%; border-radius: 2px;
  background: linear-gradient(to bottom, transparent, var(--primary));
  box-shadow: 0 0 12px 2px color-mix(in srgb, var(--primary) 70%, transparent);
  opacity: 0; pointer-events: none;
  transform: translate(-50%, -50%) rotate(var(--ha, 0deg)) translateY(180%); /* 180% of the dash = the station's own edge */
}
.harvest-node.is-pulsing::before { animation: harvest-spoke-pulse 600ms ease-out 1 both; }
.harvest-node.is-pulsing { animation: harvest-node-bump 600ms ease-out 1 both; }
@keyframes harvest-spoke-pulse {
  0%   { opacity: 0;   transform: translate(-50%, -50%) rotate(var(--ha, 0deg)) translateY(180%); }
  20%  { opacity: 1; }
  100% { opacity: 0;   transform: translate(-50%, -50%) rotate(var(--ha, 0deg)) translateY(310%); } /* ~the seal's ring */
}
/* S-7910 (v0.9.384) tried a --primary glow on every theme because brightness(1.55) clips the light
   themes' pale disc (#F3F1FA / #EDE4CE) to pure white. Owner on v0.9.385: "the flashing explosion
   before was superb, AAA. Now it is annoying and hurts the eye" -- he uses the dark themes, where the
   original disc flash was exactly right. So: the ORIGINAL brightness flash is the default (alchemical,
   dark, and no attribute); the glow variant is scoped to the two light themes only. Same 600 ms. */
@keyframes harvest-node-bump {
  0%   { filter: brightness(1); }
  25%  { filter: brightness(1.55); }
  100% { filter: brightness(1); }
}
@keyframes harvest-node-bump-glow {
  0%   { filter: brightness(1);    box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent), inset 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent); }
  25%  { filter: brightness(1.08); box-shadow: 0 0 30px 8px color-mix(in srgb, var(--primary) 62%, transparent),
                                               inset 0 0 18px color-mix(in srgb, var(--primary) 34%, transparent); }
  100% { filter: brightness(1);    box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent), inset 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent); }
}
[data-theme="light"] .harvest-node.is-pulsing,
[data-theme="wood"] .harvest-node.is-pulsing { animation-name: harvest-node-bump-glow; }

@media (prefers-reduced-motion: reduce) {
  /* Reduced motion holds the composition still and lights all six evenly -- the
     information content is identical, because there was never any in the motion. */
  .harvest-glow { animation: none; opacity: 0.3; }
  .harvest-node::after { animation: none; opacity: 0.24; }
  /* S-7620: no pulse, instant fill, no fade (board.js also skips the fade timer). */
  .harvest-node.is-pulsing, .harvest-node.is-pulsing::before { animation: none; }
  .harvest-node::before { display: none; }
  .harvest-fill { transition: none; }
  .board-loading { transition: none; }
}
@media (forced-colors: active) {
  .board-loading { background: Canvas; }
  .harvest-glow, .harvest-node::after { display: none; }
}
.player-go-btn:has(#player-loading:not([hidden])) > .hdr-ic { display: none; }
#player-loading[hidden] { display: none; }
.player-go-btn > #player-loading { inline-size: var(--ceres-control-icon-size); block-size: var(--ceres-control-icon-size); margin: 0; flex: none; }
/* IR-4800/S-6220: a player without land -- no filters, no table; the loading scene, held still, and one line. */
body.no-land .controls-row, body.no-land #table-head-sticky, body.no-land #plot-table-scroll, body.no-land #plot-card-list, body.no-land #mobile-action-bar, body.no-land #edge-tabs, body.no-land #scroll-top, body.no-land #badge-bar, body.no-land #badge-flag { display: none !important; }
#no-land-state { display: grid; justify-items: center; gap: 1.2rem; padding-block: 2.5rem 3rem; }
#no-land-state .harvest-stage { inline-size: min(34rem, calc(var(--ceres-vw) * 70), calc(var(--ceres-vh) * 60)); block-size: min(34rem, calc(var(--ceres-vw) * 70), calc(var(--ceres-vh) * 60)); }
#no-land-state .harvest-progress { display: none; }
#no-land-state .harvest-glow { animation: none; opacity: 0.3; }
#no-land-state .harvest-node::after { animation: none; opacity: 0.24; }
/* S-6390: no player in the URL -- a start page, no table, no board controls. */
html.start-page header.board-header, html.start-page #table-head-sticky, html.start-page #plot-table-scroll, html.start-page #plot-card-list { display: none !important; }
body.start-page .controls-row, body.start-page #table-head-sticky, body.start-page #plot-table-scroll, body.start-page #plot-card-list, body.start-page #mobile-action-bar, body.start-page #edge-tabs,
body.start-page #harvest-btn, body.start-page #board-notices, body.start-page #scroll-top, body.start-page #badge-bar, body.start-page #badge-flag { display: none !important; }
/* S-16790: terrain wallpaper WebPs (499 KB / 113 KB), darkened toward the bottom so the title and field stay readable. */
body.start-page, body:has(#board-loading:not([hidden]):not(.is-done)) { background: linear-gradient(180deg, rgb(10 8 20 / .25) 0%, rgb(10 8 20 / .55) 45%, rgb(10 8 20 / .9) 100%), url('/static/assets/ui/ceres-terrain-wallpaper-4k.webp') center 30% / cover no-repeat fixed, var(--bg); }
@media (max-width: 1000px) { body.start-page, body:has(#board-loading:not([hidden]):not(.is-done)) { background-image: linear-gradient(180deg, rgb(10 8 20 / .25) 0%, rgb(10 8 20 / .55) 45%, rgb(10 8 20 / .9) 100%), url('/static/assets/ui/ceres-terrain-wallpaper-1000.webp'); } }
@media (max-width: 700px), (orientation: portrait) and (max-width: 1000px) { body.start-page, body:has(#board-loading:not([hidden]):not(.is-done)) { background-image: linear-gradient(180deg, rgb(10 8 20 / .25) 0%, rgb(10 8 20 / .55) 45%, rgb(10 8 20 / .9) 100%), url('/static/assets/ui/ceres-terrain-wallpaper-phone.webp'); } }
body:has(#board-loading:not([hidden]):not(.is-done)) header.board-header { background: transparent; }
body.start-page .start-title, body.start-page .start-lead { text-shadow: 0 2px 10px rgb(0 0 0 / .75); }
body.start-page .start-lead { color: var(--heading); }
/* The art is the scene now: the loading ring would sit on the character's head -- hide it and set the title and field
   in the darkened lower part of the picture. */
body.start-page #start-page .harvest-stage { display: none; }
body.start-page #start-page { box-sizing: border-box; min-block-size: calc(var(--ceres-vh) * 100 - 3rem); align-content: end; padding-block: 2rem 9vh; }
/* S-16791: no header on the start screen; its large logo sits above the account field. */
body.start-page header.board-header { display: none !important; }
.start-logo { display: block; inline-size: clamp(220px, 28vw, 520px); max-block-size: 32vh; block-size: auto; object-fit: contain; filter: drop-shadow(0 4px 14px rgb(0 0 0 / .6)); }
@media (max-width: 1000px) { .start-logo { inline-size: clamp(180px, 40vw, 320px); max-block-size: 22vh; } }
#start-page { display: grid; justify-items: center; gap: 1rem; padding-block: 2rem 3rem; text-align: center; }
#start-page .harvest-stage { inline-size: min(26rem, calc(var(--ceres-vw) * 60), calc(var(--ceres-vh) * 42)); block-size: min(26rem, calc(var(--ceres-vw) * 60), calc(var(--ceres-vh) * 42)); }
#start-page .harvest-progress { display: none; }
#start-page .harvest-glow { animation: none; opacity: 0.3; }
#start-page .harvest-node::after { animation: none; opacity: 0.24; }
.start-title { margin: .5rem 0 0; color: var(--heading); font-size: 2rem; font-weight: 800; letter-spacing: .01em; }
.start-lead { margin: 0; color: var(--body); font-size: 1.1rem; max-inline-size: 100%; }
.start-form { display: flex; flex-wrap: wrap; justify-content: center; gap: .6rem; margin-block-start: .6rem; }
.start-input { min-block-size: var(--ceres-control-min-block); inline-size: min(20rem, 80vw); }
@media (max-width: 600px) {
  .start-form { inline-size: min(20rem, 80vw); align-items: stretch; }
  .start-form .start-input, .start-form .start-go { box-sizing: border-box; inline-size: 100%; }
}
.no-land-text { margin: 0; color: var(--heading); font-size: 1.25rem; font-weight: 700; letter-spacing: .02em; }
.expand-loading { display: flex; align-items: center; gap: 0.5rem; }

.player-switch { display: inline-flex; align-items: center; gap: 0.35rem; }
.player-input {
  background: var(--bg2);
  color: inherit;
  font-weight: 600;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.25rem 0.5rem;
  width: 8rem;
  font: inherit;
  /* Header round 3 (2026-08-24, Florian: the two header rows should "take about
     the same height" -- measured row1 29px vs row2 16px). This input, at the
     inherited body font-size, was row1's tallest child and the sole reason row1
     out-measured row2 by that much (the row's siblings -- Go/reload/checkout
     controls -- are already 0.85rem, ~27px tall). Matching this input's font-size
     to those buttons' brings row1 down to ~27px without shrinking padding (which
     would risk the "badges must never wrap" constraint by tightening tap
     targets) and without touching `font: inherit` above, which the shorthand
     would otherwise reset -- this declaration must stay AFTER it. */
  font-size: 0.85rem;
}
.player-input:focus { border-color: var(--accent); } /* IR-8457: states come from the shared .ceres-wide-input class */
/* IR-8457 / S-16774a: the phone header keeps its player field exactly as before (the shared class brings phone text-box rules). */
html.ceres-lw-le-1279 .board-header #player-input { font-size: 0.82rem; line-height: 1; padding-block: 0; padding-inline-end: 0.6rem; background: color-mix(in srgb, var(--bg2) 75%, transparent); }

/* Version display in the board header. */
/* Pinned to the bottom-right corner of `.board-logo` -- see that rule's own comment
   and index.html's comment on this span for why that box IS the logo artwork's box
   (header-controls polish, Florian: "Move the version number to the bottom right of
   the logo"). Small inset (not flush to 0,0) keeps the text off the image's own
   edge. Checked against all four theme PNGs -- the lower-right quadrant of the mark
   is empty in every one, so this never sits over the wheat/wordmark artwork. */
.board-version {
  position: absolute;
  right: 0.2rem;
  bottom: 0.05rem;
  font-size: 0.75rem;
  /* `.board-logo` (the containing block above) sets `line-height: 0` for its own
     image-only layout -- without an override here that inherits onto this text
     span too and collapses its line box to zero height (found while measuring
     this change: `getBoundingClientRect().height` came back 0 in headless
     Chromium even though the glyphs still paint). A real line-height makes the
     bottom-right position deterministic instead of resting on zero-height-line-box
     baseline behavior. */
  line-height: 1;
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
}

/* Totals strip (Florian, 2026-08-21: "the stats in the header break the header
   height when the browser is too narrow"). Was `flex-wrap: wrap`, so at a narrow
   viewport the five figures wrapped onto a second/third line and grew
   `header.board-header` — which `updateHeaderHeightVar()` measures into
   `--ceres-header-height`, so the sticky table header moved down with it. Now it
   stays on ONE line and scrolls horizontally instead: header height is stable at
   every width, and the figures are still all reachable. `min-width: 0` is what
   actually lets this flex item shrink below its content width (auto is the
   default and would keep forcing it wider than its container); the children are
   `flex: 0 0 auto` so they scroll rather than squeezing into unreadable columns.

   Header round 4 (2026-08-24): no longer a direct grid child of
   `header.board-header` -- it's the second child of `.board-header-rows`
   (flex column), which is what now separates it from row 1 (that wrapper's
   own `gap`) and centres the two-row pair against the logo (see
   `header.board-header`'s own comment for the full round-4 story). Left-
   alignment is unaffected: `justify-content` is still unset -> `flex-start`,
   and this element still starts flush with `.board-header-rows`'s left edge,
   which is still the PLAYER SWITCH's left edge (`.board-header-row`'s first
   child) -- re-verified by measurement in work/ceres-header4/, not just by
   this layout argument. `min-width: 0`/`overflow-x: auto`/hidden-scrollbar
   all stay: a narrow viewport can still make this row's own content wider
   than the header, and the "no visible scrollbar in the header" ruling from
   2026-08-21 still applies. */
.totals-strip {
  display: flex;
  /* 1.25rem -> 0.65rem (v0.5.4, header "Plots" clipping fix): measured 1440
     had this strip at clientWidth 671 vs scrollWidth 708 -- with its own
     scrollbar deliberately hidden (below), the trailing "Plots: N" figure sat
     scrolled out of view with no way back in short of a hidden wheel-scroll
     (work/ceres-width-diagnosis.md, part C). This strip has 5 children (4
     gaps); shrinking the gap alone reclaims (1.25-0.65)*16*4 = 38.4px, more
     than the 37px deficit, without touching `flex-wrap` (still `nowrap` --
     letting this wrap would grow the header's own measured height, exactly
     the instability Florian rejected on 2026-08-21) and without adding a
     visible scrollbar (also explicitly rejected that day). */
  gap: 0.65rem;
  font-size: 0.85rem;
  flex-wrap: nowrap;
  white-space: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  min-width: 0;
  max-width: 100%;
  scrollbar-width: none;
  /* Header round 4 (2026-08-24): round 3's `padding-top: 0.65rem` hack grew
     this row's box by eating into what was then leftover GRID space between
     the two rows -- that mechanism is gone along with the grid-row split
     (see `header.board-header`'s own comment). Reusing it here would instead
     ADD to `.board-header-rows`'s now-literal, deliberately small `gap`,
     re-inflating the exact spacing Florian just flagged as too wide. Same
     goal (row 1 and row 2 "about the same height", MUST NOT REGRESS) is now
     met with `min-height` sized to row 1's own measured height
     (buttons/input at 0.85rem, ~27px, see `.player-input`'s comment) plus
     `align-items: center` to vertically centre this row's plain 16px text
     inside that taller box -- the box grows in place instead of the gap
     absorbing it. */
  min-height: 1.7rem;
  align-items: center;
}
.totals-strip::-webkit-scrollbar { display: none; }
.totals-strip > * { flex: 0 0 auto; }
.totals-strip strong { font-variant-numeric: tabular-nums; }

.checkout-dock { position: relative; flex: 0 0 auto; display: flex; gap: 0.4rem; }
.checkout-waiting { color: var(--text-dim); font-size: .8rem; }
.checkout-waiting b { color: var(--heading); font-variant-numeric: tabular-nums; }
/* S-6650: every button in a panel or modal gives clear hover feedback -- one global rule, so new buttons get it too. */
:is(.checkout-panel, [role="dialog"]) button:not(.ceres-btn):not(:disabled):not(.ch-link, .ch-plot-badge) { transition: filter .12s ease, box-shadow .12s ease, transform .12s ease; }
:is(.checkout-panel, [role="dialog"]) button:not(.ceres-btn):not(.candidate-grid-select):not(:disabled):not(.ch-link, .ch-plot-badge):hover { filter: brightness(1.18); box-shadow: 0 0 0 1px var(--accent), 0 3px 10px rgb(0 0 0 / .35); transform: translateY(-1px); }
:is(.checkout-panel, [role="dialog"]) button:not(.ceres-btn):not(:disabled):not(.ch-link, .ch-plot-badge):active { filter: brightness(.95); transform: translateY(0); box-shadow: 0 0 0 1px var(--accent); }
.checkout-count { color: var(--hi); font-variant-numeric: tabular-nums; }
/* Items 21/22 -- Florian: "showed two vertical scroll bars wich is bad style" and "rounded corners in the
   checkout are broken": the panel clips to its own radius (overflow: hidden) and is a flex column, so exactly one
   descendant may scroll. The old `.checkout-scroll-body` that did so went with the checkout body (IR-9154). */
.checkout-panel {
  position: absolute;
  top: calc(100% + 0.55rem);
  right: 0;
  z-index: 20;
  width: fit-content; min-width: min(34rem, calc(var(--ceres-vw) * 100 - 2rem)); max-width: calc(var(--ceres-vw) * 100 - 2rem);
  max-height: min(calc(var(--ceres-vh) * 70), 38rem);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  background: var(--bg2);
  color: var(--text);
}
/* Item 41 bisect finding (2026-08-24): the `display: flex` added above by the
   Items 21/22 fix (commit 0ca81bc, "showed two vertical scroll bars") gave
   `.checkout-panel` an author-specified `display` value for the first time.
   That competes DIRECTLY with the browser's own `[hidden] { display: none }`
   UA-stylesheet rule at equal (0,0,1,0) specificity -- and an author rule
   always wins a tie against the UA sheet, regardless of source order within
   this file. Before that commit `.checkout-panel` had no `display` property
   at all, so `[hidden]` alone controlled it cleanly; after it, the element
   stayed visible even while `hidden` was set (which app.js's
   `renderCheckoutSurface()` keeps true whenever the panel has no content or
   is collapsed -- `panel.hidden = !CERES_CHECKOUT_OPEN`, app.js ~2050). The
   result: an empty, absolutely-positioned flex box collapsed to just its own
   1px top + 1px bottom `border-bright` -- a 2px sliver that read as a stray
   underline near the totals strip/header at every width, in every theme.
   `border: 1px solid var(--border-bright)` above is legitimate (the panel's
   own visible-state border) -- the fix is restoring `[hidden]`'s effect, not
   removing that border. */
.checkout-panel[hidden] { display: none; }
/* Disables the nested scroll only inside the checkout panel -- the SAME
   class also renders inside the per-row slot-editor's own review section
   (a plain, non-scrolling context, never nested inside another scroller),
   where its own bounded scroll is fine and untouched. */
.checkout-panel .op-preview-review pre { max-height: none; overflow: visible; }
.checkout-move { display: flex; align-items: center; gap: 0.55rem; min-width: 0; padding: 0.3rem 0; border-bottom: 1px solid var(--border); }
.checkout-move:last-child { border-bottom: 0; padding-bottom: 0; }
.checkout-move-copy { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.checkout-move-copy strong { overflow: hidden; color: var(--text); font-size: 0.8rem; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.checkout-move-copy span { color: var(--text-dim); font-size: 0.69rem; text-transform: capitalize; }
.checkout-remove { flex: 0 0 auto; padding: .15rem .5rem; border: 1px solid var(--border); border-radius: 4px; background: var(--bg3); color: var(--text); font: inherit; font-size: .7rem; cursor: pointer; line-height: 1.2; }
/* S-6530: set-aside moves stay listed (dimmed, with their reason); the footer line says what is and is not signed. */
.checkout-move.is-set-aside { opacity: .78; }
/* S-8350: one row per staged worksite build in the Changes panel. */
.ch-worksite-build-row { display: flex; align-items: center; justify-content: space-between; gap: .6rem 1rem; flex-wrap: wrap; }
/* S-6750: Changes as a harvest-style modal */
.ch-plot-head { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem 1rem; flex-wrap: wrap; margin-bottom: .5rem; }
.ch-plot-name { font-size: 1.25rem; font-weight: 800; }
.ch-plot-badge { display: inline-block; padding: .05em .5em; border: 0; border-radius: 4px; font: inherit; font-variant-numeric: tabular-nums; cursor: pointer; vertical-align: baseline; }
.ch-plot-badge { transition: background-color .18s ease, color .18s ease; }
/* Interactive plaque behavior is registered separately in buttons.css. */

.ch-pp.is-up { color: var(--ok); }
.ch-pp.is-down { color: var(--danger); }
.ch-plot-badge.territory-pristine-northwest { background: var(--territory-pristine-northwest); color: var(--territory-pristine-northwest-fg); border: 1px solid var(--territory-pristine-northwest-accent); }
.ch-plot-badge.territory-broken-lands { background: var(--territory-broken-lands); color: var(--territory-broken-lands-fg); }
.ch-plot-badge.territory-land-of-prosperity { background: var(--territory-land-of-prosperity); color: var(--territory-land-of-prosperity-fg); }
.ch-plot-badge.territory-central-fire { background: var(--territory-central-fire); color: var(--territory-central-fire-fg); }
.ch-plot-badge.territory-wild-northeast { background: var(--territory-wild-northeast); color: var(--territory-wild-northeast-fg); }
.ch-plot-badge.territory-great-lowlands { background: var(--territory-great-lowlands); color: var(--territory-great-lowlands-fg); }
.ch-plot-badge.territory-shimmering-coasts { background: var(--territory-shimmering-coasts); color: var(--territory-shimmering-coasts-fg); }
.ch-plot-badge:is(.territory-broken-lands, .territory-land-of-prosperity, .territory-central-fire, .territory-wild-northeast, .territory-great-lowlands, .territory-shimmering-coasts) { text-shadow: 0 1px 2px color-mix(in srgb, var(--shadow) 85%, transparent), 0 0 4px color-mix(in srgb, var(--shadow) 55%, transparent); }
.ch-link { text-align: left; }

.ch-link { text-decoration-color: transparent; text-underline-offset: .2em; }

.ch-link:hover,
.ch-link:focus-visible { text-decoration-color: currentColor; }

.ch-table { table-layout: fixed; }
.ch-table tr.checkout-move { display: table-row; padding: 0; border: 0; } /* the old dropdown's flex row rules must not reach the table */
.ch-table td.checkout-move-copy { display: table-cell; }
.ch-table .ch-status, .ch-table .checkout-move-copy .ch-status { display: inline; font-size: 1rem; text-transform: none; }
.changes-panel .ch-table td { font-weight: 600; vertical-align: middle; text-align: left; padding: .7rem .6rem; }
.changes-panel .ch-table tr:last-child td { padding-bottom: 0; } /* S-6930: bottom space equals the top space */
.changes-panel .ch-plot-head { margin-bottom: 0; }
.changes-panel .ch-plot { padding-bottom: .75rem; }
/* IR-5020: the footer's buttons stay on one row; the note takes what is left and may wrap to a second line */
.changes-panel .hm-foot { flex-wrap: nowrap; }
.changes-panel .hm-foot .hm-note { flex: 1 1 auto; min-width: 0; white-space: normal; overflow: visible; text-overflow: clip; }
.changes-panel .ch-signing-status{box-sizing:border-box;min-inline-size:0;overflow-wrap:anywhere}
.changes-panel .ch-signing-status>.op-preview-review.is-compact{box-sizing:border-box;min-block-size:0;margin-block-end:0}
.changes-panel .hm-foot .ch-signing-note{box-sizing:border-box;display:flex;align-items:center;block-size:2.8rem;overflow-y:auto}
.changes-panel .hm-foot .hm-btns { flex: 0 0 auto; flex-wrap: nowrap; }
.ch-signing-only { display: block; }
.ch-signing-focus { display: grid; gap: 1rem; min-inline-size: 0; }
.ch-signing-focus > header { display: flex; flex-wrap: wrap; align-items: start; justify-content: space-between; gap: .5rem 1rem; }
.ch-signing-focus > header > div { flex: 1 1 16rem; min-inline-size: 0; }
.ch-signing-focus h2, .ch-signing-focus h3 { margin: 0; color: var(--heading); }
.ch-signing-focus p { margin: 0; line-height: 1.5; }
.ch-signing-live { flex: none; padding: .35rem .7rem; border: 1px solid color-mix(in srgb, var(--warn) 60%, transparent); border-radius: 999px; color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); box-shadow: inset 0 1px 0 color-mix(in srgb, var(--text) 14%, transparent); font-weight: 700; }
/* IR-9344 (owner: "beautify the screen using our glass theme"): the page glass of the Cards page (C-9: --cb-glass recipe,
   density --glass-row, inset light edge, no backdrop-filter); state colours only on edges, dots and tints. */
.ch-signing-focus { --ch-glass: linear-gradient(160deg, color-mix(in srgb, var(--text) 9%, transparent), color-mix(in srgb, var(--text) 2%, transparent)), color-mix(in srgb, var(--bg) var(--glass-row, 60%), transparent); --ch-edge: color-mix(in srgb, var(--text) 20%, transparent); }
.ch-signing-operation { display: grid; gap: .65rem; padding: 1rem; border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent); border-radius: var(--radius); background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%), var(--ch-glass); box-shadow: inset 0 1px 0 var(--ch-edge), 0 4px 14px color-mix(in srgb, var(--shadow) 22%, transparent); }
.ch-signing-operation > p { opacity: .8; font-size: .85rem; }
.ch-signing-operation ol { display: grid; gap: .4rem; margin: 0; padding: 0; list-style: none; }
.ch-signing-operation li { min-inline-size: 0; border: 1px solid var(--ch-edge); border-radius: calc(var(--radius) - 2px); background: var(--ch-glass); box-shadow: inset 0 1px 0 var(--ch-edge); }
.ch-signing-op > summary { display: flex; align-items: center; gap: .65rem; padding: .55rem .75rem; cursor: pointer; }
.ch-signing-op > summary:hover { background: color-mix(in srgb, var(--text) 6%, transparent); }
.ch-signing-op-n { flex: none; display: inline-grid; place-items: center; inline-size: 1.5rem; block-size: 1.5rem; border: 1px solid var(--ch-edge); border-radius: 999px; color: var(--heading); font-size: .75rem; font-weight: 700; }
.ch-signing-op-text { display: grid; gap: .1rem; min-inline-size: 0; }
.ch-signing-op-text strong { color: var(--heading); overflow-wrap: anywhere; }
.ch-signing-op-text small { opacity: .75; font-size: .75rem; overflow-wrap: anywhere; }
.ch-signing-op pre { max-block-size: 14rem; margin: 0; padding: .6rem .75rem; overflow: auto; border-block-start: 1px solid var(--ch-edge); background: color-mix(in srgb, var(--bg) 55%, transparent); white-space: pre-wrap; overflow-wrap: anywhere; font-size: .75rem; }
.ch-signing-progress { display: grid; gap: .4rem; margin: 0; padding: 0; list-style: none; }
.ch-signing-progress li { display: flex; align-items: center; gap: .65rem; padding: .55rem .75rem; border: 1px solid var(--ch-edge); border-radius: calc(var(--radius) - 2px); background: var(--ch-glass); box-shadow: inset 0 1px 0 var(--ch-edge); }
.ch-signing-progress li small { display: block; opacity: .75; font-size: .75rem; }
.ch-signing-progress li.is-current { border-color: color-mix(in srgb, var(--warn) 60%, transparent); background: linear-gradient(color-mix(in srgb, var(--warn) 12%, transparent), color-mix(in srgb, var(--warn) 12%, transparent)), var(--ch-glass); }
.ch-signing-progress li.is-signed { border-color: color-mix(in srgb, var(--accent) 60%, transparent); background: linear-gradient(color-mix(in srgb, var(--accent) 10%, transparent), color-mix(in srgb, var(--accent) 10%, transparent)), var(--ch-glass); }
.ch-signing-progress li.is-confirmed { border-color: color-mix(in srgb, var(--ok) 60%, transparent); background: linear-gradient(color-mix(in srgb, var(--ok) 12%, transparent), color-mix(in srgb, var(--ok) 12%, transparent)), var(--ch-glass); }
.ch-signing-progress li.is-attention { border-color: color-mix(in srgb, var(--danger) 60%, transparent); background: linear-gradient(color-mix(in srgb, var(--danger) 12%, transparent), color-mix(in srgb, var(--danger) 12%, transparent)), var(--ch-glass); }
.ch-signing-dot { flex: none; inline-size: .7rem; block-size: .7rem; border-radius: 50%; background: var(--border-bright); }
.ch-signing-progress li.is-current .ch-signing-dot { background: var(--warn); box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 25%, transparent); }
.ch-signing-progress li.is-signed .ch-signing-dot { background: var(--accent); }
.ch-signing-progress li.is-confirmed .ch-signing-dot { background: var(--ok); }
.ch-signing-progress li.is-attention .ch-signing-dot { background: var(--danger); }
.ch-signing-footer { justify-content: stretch; }
.ch-signing-footer .ch-signing-note { inline-size: 100%; }
.changes-panel .hm-btn .hdr-ic { inline-size: 1.2rem; vertical-align: -.25rem; margin-right: .3rem; }
 /* S-6940 */
.changes-panel .ch-table th { text-align: left; }
.changes-panel .ch-col-card { width: 30%; }
.changes-panel .ch-col-change { width: 24%; }
.changes-panel .ch-col-act { width: 6.5rem; }
.changes-panel .ch-table td.ch-act { text-align: right; white-space: nowrap; }
.changes-panel .ch-table td:nth-child(2) { color: var(--body); white-space: nowrap; }
.changes-panel .op-preview-review.is-compact, .changes-panel .op-preview-review.is-compact * { font-size: 1rem; }
.changes-panel .op-sign-summary strong { font-size: 1.1rem; }
.ch-status { display: inline; text-transform: none; font-size: 1rem; }
.ch-status.is-ok { color: var(--ok); }
.ch-status.is-warn { color: var(--warn); }
.ch-status.is-bad { color: var(--danger); }
.ch-status.is-waiting { color: var(--warn); }  /* IR-9174: waiting = warning colour (owner-approved state matrix) */
/* IR-8459: while saved changes are checked after a load, the open window says so (the button already looks final). */
.ch-checking { display: flex; align-items: center; justify-content: center; gap: .6rem; min-block-size: 8rem; color: var(--body); } /* S-16786: waiting has its own colour, the pending clock's gold */
.ch-status.is-dim { color: var(--body); }
.ch-btn { min-height: 2.1rem; padding: 0 .9rem; font-size: .9rem; }
.changes-panel .op-preview-review.is-compact { border: 0; padding: 0; background: none; }
.changes-panel .op-sign-row { display: block; }
/* S-7010: Fill plot modal (harvest / Changes style) */
/* S-7110: no scrolling -- one row of switches, one strip of figures, the tiles; nothing else */
.fill-panel .hm-body { gap: .7rem; padding-block: .8rem .9rem; }
.fill-panel .hm-card { padding: .7rem .9rem; }
/* S-7470: the settings never widen the modal (the tiles set its width: inline-size 0 + min 100%); three lines on one
   grid -- a label column and six equal option columns -- so the switches line up. */
/* S-7490: ONE grid for the three lines -- each column as wide as its widest label, the columns spread over the
   full width -- so every switch sits under the one above it and no label is ever clipped. */
.fill-panel .fp-settings { contain: inline-size; display: grid; grid-template-columns: max-content repeat(6, minmax(max-content, 1fr)); align-items: center; gap: .45rem .4rem; } /* S-7520: spare width is shared by the columns, not piled up before a narrow one */
.fill-panel .fp-settings .fp-row { display: contents; }
.fill-panel .fp-settings .hm-check, .fill-panel .fp-settings .hm-lbl { font-size: .85rem; white-space: nowrap; gap: .25rem; }
.fill-panel .fp-settings .fp-row > .hm-lbl { grid-column: 1; } /* each line's label starts a new grid row */
.fill-panel .fp-settings .fp-row .hm-check { white-space: nowrap; min-inline-size: 0; }
.fill-panel .fp-settings .hm-lbl { margin-right: 0; }
.fill-panel .fp-sep { inline-size: 1px; block-size: 1.2rem; background: var(--border); margin-inline: .2rem; }
.fill-panel .hm-checks label.fp-min input[type="number"] { inline-size: 3.4rem; block-size: auto; margin: 0 .3rem; padding: .1rem .35rem; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--heading); font: inherit; font-size: .9rem; }
.fill-panel .fp-settings .hm-check.is-off { opacity: .5; }
.fill-panel .fp-totals { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem 1.8rem; margin-bottom: .7rem; padding-bottom: .6rem; border-bottom: 1px solid var(--border); }
.fill-panel .fp-totals > div { display: flex; align-items: baseline; gap: .45rem; }
.fill-panel .fp-totals b { font-size: 1.35rem; font-weight: 800; color: var(--heading); line-height: 1.1; }
.fill-panel .fp-totals small { font-size: .8rem; }
/* S-7260: donor plots' loss under the figures */
.fill-panel .fp-donors { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem 1.2rem; margin: -.2rem 0 .7rem; padding-bottom: .6rem; border-bottom: 1px solid var(--border); font-size: .95rem; }
.fill-panel .fp-donor { display: inline-flex; align-items: baseline; gap: .4rem; }
.fill-panel .fp-donor b { font-weight: 800; }
.fill-panel .fp-net { margin-left: auto; }
.fill-panel .fp-gain { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* S-7140: the proposal IS the plot view's tile row -- the same .slot-grid, tiles, Power slot, divider and Base PP
   badge row, only with the modal's own spacing so eight tiles fit its width. */
/* S-7180/S-7240: the modal is as wide as its tile row -- no spare width right of the last tile, whatever the font or zoom */
.harvest-panel.fill-panel { width: fit-content; min-width: min(56rem, calc(100vw - 2rem)); max-width: calc(100vw - 2rem); }
.fill-panel .hm-body > .hm-card { width: fit-content; min-width: 100%; box-sizing: border-box; }
:is(.fill-panel, .changes-panel) .fp-tiles.slot-grid { --slot-w: 6.7rem; --slot-h: 9.4rem; padding: 1.3rem 0 0; gap: .8rem; min-width: 0; inline-size: max-content; overflow: visible; pointer-events: auto; } /* S-7470: the tiles set the modal's width */
:is(.fill-panel, .changes-panel) .fp-tiles .slot-tile, :is(.fill-panel, .changes-panel) .fp-tiles .slot-power { pointer-events: auto; }
:is(.fill-panel, .changes-panel) .fp-tiles .slot-tile.is-empty:hover { border-color: var(--border); } /* S-7200: nothing to do on an empty preview tile */
/* S-7210: a proposed tile is a button -- click leaves it out (dimmed), click again brings it back */
:is(.fill-panel, .changes-panel) .fp-slot { display: contents; }
:is(.fill-panel, .changes-panel) .fp-slot > .slot-tile, :is(.fill-panel, .changes-panel) .fp-slot > .slot-power-satellite .power-satellite { cursor: pointer; }
:is(.fill-panel, .changes-panel) .fp-slot.is-fp-excluded > .slot-tile { opacity: .4; filter: grayscale(70%); border-style: dashed; }
:is(.fill-panel, .changes-panel) .fp-slot.is-fp-excluded > .slot-power-satellite .power-satellite { opacity: .4; filter: grayscale(70%); }
:is(.fill-panel, .changes-panel) .fp-slot:focus-visible > .slot-tile { outline: 2px solid var(--accent); outline-offset: 2px; }
:is(.fill-panel, .changes-panel) .fp-tiles .slot-grid-divider { margin: 0 .2rem; }
:is(.fill-panel, .changes-panel) .fp-badge-row { display: flex; align-items: stretch; gap: .8rem; margin: .45rem 0 0; inline-size: max-content; }
:is(.fill-panel, .changes-panel) .fp-badge-row .editor-base-pp-cell { flex: 0 0 6.7rem; box-sizing: border-box; min-height: 1.7rem; }
:is(.fill-panel, .changes-panel) .fp-badge-row .editor-base-pp-divider { flex: 0 0 1px; margin: 0 .2rem; }
/* S-7540: the Changes modal's plot strip -- unchanged tiles live but dimmed; the state cell under a changed tile
   in the stage colour; a changed tile is the Undo. */
.changes-panel .ch-tiles .slot-tile.is-ch-unchanged, .changes-panel .ch-tiles .power-satellite.is-ch-unchanged { opacity: .42; filter: saturate(.5); }
.changes-panel .ch-tiles .slot-tile.is-ch-unchanged.is-empty { opacity: .3; }
.changes-panel .fp-slot > .slot-tile:hover, .changes-panel .fp-slot > .slot-power-satellite .power-satellite:hover { box-shadow: 0 0 0 2px var(--accent); }
.changes-panel .fp-slot.is-in-flight > .slot-tile { cursor: default; }
.changes-panel .ch-cells .ch-state b { font-size: .78rem; font-weight: 800; white-space: nowrap; }
.changes-panel .ch-cells .ch-state b.is-unstake { white-space: normal; }
.changes-panel .ch-cells .ch-state.is-included { border-color: var(--ok); color: var(--ok); }
.changes-panel .ch-cells .ch-state.is-orange { border-color: var(--warn); color: var(--warn); }
.changes-panel .ch-cells .ch-state.is-refused { border-color: var(--danger); color: var(--danger); }
.changes-panel .ch-cells .ch-state.is-waiting { border-color: var(--warn); color: var(--warn); }  /* IR-9174: waiting is orange, refused stays red */
.changes-panel .ch-cells .ch-state.is-working { border-color: var(--border); color: var(--body); }
.changes-panel .ch-plot { padding-bottom: .7rem; }
/* S-7550 (1): wide enough for the strip; (3): no pulse in this modal -- the coloured outline only. */
.harvest-panel.changes-panel { width: fit-content; min-width: min(68rem, calc(var(--ceres-vw, 1vw) * 100 - 2rem)); max-width: calc(var(--ceres-vw, 1vw) * 100 - 2rem); }
/* IR-6740 (owner, verbatim, high: "regression in the changes modal, the tiles
   where changed" -- five worker tiles, then Power Core/Rare Totem/"No title"
   stretched to 122-196px wide instead of their base 107x150): the S-14190 block
   this comment replaces stretched every tile/power-slot/badge-row cell in
   `.changes-panel` to fill the modal's own `min-width: 68rem` (above), so a plot
   group with fewer tiles than that filled the leftover width by growing each
   tile instead of leaving it -- reverted, tiles are back to their base
   `flex: 0 0 var(--slot-w)` / `inline-size: max-content` row (S-7470, above).
   S-14190's own intent ("the empty space ... is bad") is kept, the other way:
   `.changes-panel .ch-plot` (the per-plot card -- checkout.js's `<div
   class="hm-card ch-plot">`, `.hm-card` has no width rule of its own so it
   otherwise spans the full modal as any block element would) sizes to its own
   content instead -- `inline-size: fit-content` (capped at the modal's own
   width by `max-inline-size: 100%` for a plot with MORE tiles than the strip
   table needs). The card now ends at its own last tile instead of the modal's
   `min-width: 68rem` edge; `.ch-plot-head` (the badge/count header line, `display:
   flex; justify-content: space-between`, above) is that card's own child, so it
   now spans exactly the card's (tile-row) width too, not the modal's -- no
   separate rule needed for it. `.harvest-panel.changes-panel`'s own `min-width:
   68rem` (S-7550, above) is UNCHANGED -- it exists for the wide strip table
   BENEATH the tile row, a real width need unrelated to this regression. */
.changes-panel .ch-plot { box-sizing: border-box; inline-size: 100%; min-inline-size: 0; max-inline-size: 100%; }
.changes-panel .ch-tiles .slot-tile, .changes-panel .ch-tiles .slot-tile::after, .changes-panel .ch-tiles .power-satellite, .changes-panel .ch-tiles .power-satellite::after { animation: none !important; }
.changes-panel .ch-tiles .slot-tile::after { opacity: .35; }
.changes-panel .ch-cells .ch-state.is-undone { border-style: dashed; color: var(--body); }
/* S-7560 (5): while any modal is open, the board's staged tiles stop pulsing behind it. */
body:has(.harvest-overlay:not([hidden]), .slot-hazard-overlay:not([hidden]), #worksite-modal-overlay:not([hidden])) :is(.editor-band .slot-tile, .editor-band .slot-tile::after, .editor-band .power-satellite, .editor-band .power-satellite::after) { animation-play-state: paused !important; }
.fill-panel .fp-lines { margin: .9rem 0 0; padding: 0; list-style: none; display: grid; gap: .3rem; font-size: .95rem; color: var(--body); }
.fill-panel .fp-lines b { color: var(--heading); }
.fill-panel .ch-table td:nth-child(2) { white-space: normal; }
.fill-panel .ch-col-change { width: 38%; }
.fill-panel .hm-check.is-off { cursor: not-allowed; }
.fill-panel .hm-checks label input { accent-color: var(--primary); inline-size: 1.05rem; block-size: 1.05rem; }
/* IR-4990 phase 2: the one Changes panel (superseded by S-6750; kept for the shared classes) */
.checkout-move-copy .checkout-move-status { text-transform: none; }
.checkout-move-copy .checkout-move-status.is-included { color: var(--ok); }
.checkout-move-copy .checkout-move-status.is-refused { color: var(--danger); }
.checkout-move-copy .checkout-move-status.is-muted { color: var(--text-dim); }
.nav-include-notice { position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%); z-index: 30; max-width: calc(100vw - 2rem); padding: .45rem .8rem; border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg2); color: var(--text); font-size: .78rem; box-shadow: 0 4px 14px rgb(0 0 0 / .4); }
.nav-include-notice button { margin-left: .4rem; }
:is(.checkout-panel, [role="dialog"]) :is(button, input):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  :is(.checkout-panel, [role="dialog"]) button:not(.ceres-btn):not(:disabled) { transition: none; }
  :is(.checkout-panel, [role="dialog"]) button:not(.ceres-btn):not(:disabled):is(:hover, :active) { transform: none; }
}
:is(.slot-tile, .power-satellite).is-nav-target { outline: 3px solid var(--accent); outline-offset: 3px; }
.checkout-move-copy .checkout-move-status.is-set-aside { color: var(--danger); text-transform: none; }
.checkout-move-copy .checkout-move-status.is-elsewhere { color: var(--warn); text-transform: none; }
.checkout-chunk-warning { flex: 1 0 100%; margin: 0; padding: 0.45rem 0.55rem; border-left: 3px solid var(--warn); background: var(--bg3); color: var(--heading); font-size: 0.72rem; font-weight: 600; }

@keyframes reload-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {

}

.controls-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  padding: 0.5rem 0.75rem;
  align-items: center;
  font-size: 0.85rem;
  /* Containing block for .theme-picker-dock's absolute centering (see that rule's
     own comment) -- 2026-08-21, theme switcher moved to sit over this row. */
  position: relative;
  /* Ties the filter row to the header above it as one coherent control cluster
     (2026-08-21 header/filter tidy) rather than two visually unrelated bands. */
  border-bottom: 1px solid var(--border);
  /* Own background (0.3.4 board-fix batch, Florian: "Add a different background to
     the filter row, so it does not look like the table header"). Before this it had
     none, so it showed the plain page --bg -- the EXACT same token the sticky table
     header (`table.plot-table th`) paints, which is what made the two
     read as one band. `header.board-header` above it is already --bg2, so --bg3
     (Iris's third background step) is the only token left that's distinct from
     BOTH neighbours -- filter row now reads as its own band between them. */
  background: var(--bg3);
  padding-right: 8rem;
}
.controls-row > .close-all-btn { margin-left: auto; }
/* S-13990: vertical group dividers in the filter row -- search | dropdowns | pills | buttons. Desktop only; the mobile sheet wraps into a grid. */
.controls-row .controls-sep { flex: 0 0 1px; align-self: stretch; min-block-size: 1.4rem; background: var(--border-bright); }
html.ceres-lw-le-1279 .controls-row .controls-sep { display: none; }
/* IR-6300 (S-14040): a divider that lands at the start or end of a wrapped line separates nothing; app.js syncControlsDividers() marks it.
   IR-6330: made invisible rather than removed -- visibility:hidden keeps its width, so a neighbour never reflows onto its line and re-triggers the wrap. */
.controls-row .controls-sep.is-line-edge { visibility: hidden; }
.controls-row select {
  background: var(--bg2);
  color: inherit;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.25rem 0.5rem;
}
/* One-line fit at 1920px (0.3.4 board-fix batch, Florian: the row wraps to two
   lines and shouldn't). Gap/padding tightened above (0.6rem->0.4rem gap,
   0.6rem 1rem -> 0.5rem 0.75rem padding); the text filter gets an explicit,
   content-sized width instead of the browser's default ~20-character input box
   (visibly wider than "plot / name…" ever needs). See the .csel-wrap/.csel-trigger
   min-width reduction below for the other big contributor (an 8rem floor on
   every one of the five dropdowns regardless of how short their content is).
   Gap bumped 0.4rem->0.55rem and each control's own padding increased (below)
   2026-08-21 board-polish batch 19 item 3, Florian: "give each filter slightly
   more padding, so it looks less crammed" -- the row still fits on one line at
   1920/1280 (verified), and already wrapped at 1024 before this change so the
   extra padding only changes ITS wrap point, not whether it wraps. */
.controls-row input[type=text]#text-filter { width: 8rem; }
/* S-16855b: every text box uses the dropdown's own tokens -- same text size, weight (600, as
   .csel-value), font, height, border and hover/active states as the buttons and dropdowns
   beside it, never a pixel taller; the start field reads the comfortable tokens of its Open board button. */
.ceres-wide-input { appearance: none; box-sizing: border-box; block-size: var(--ceres-control-min-block); min-block-size: var(--ceres-control-min-block); padding-block: 0; padding-inline: var(--ceres-control-inline-pad); background: var(--bg3); color: var(--heading); border: 1px solid color-mix(in srgb, var(--heading) 70%, var(--bg3)); border-radius: 8px; box-shadow: inset 0 1px color-mix(in srgb, var(--hi) 6%, transparent); font-family: var(--font-body); font-size: var(--ceres-control-font-size); font-weight: 600; line-height: 1.25; letter-spacing: normal; transition: background 150ms ease, border-color 150ms ease, box-shadow 150ms ease, transform 100ms ease; }
/* IR-8457 (owner: "weird quadruple movement when hovering, clicking and moving away"): a text box never moves or casts a
   shadow -- only its border and fill answer hover and press (the button lift/press was wrongly copied here in 0.9.904). */
.ceres-wide-input:not(:disabled):hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--bg3)); }
.ceres-wide-input:not(:disabled):active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, var(--bg3)); }
/* S-16876 (phone is a separate design; never changed on the side): at <=1279 the text boxes keep their look from
   before S-16855b until the phone redesign (S-16774) -- the dropdown-matched tokens above are a desktop change. */
html.ceres-lw-le-1279 .ceres-wide-input { appearance: auto; block-size: auto; min-block-size: 0; padding: 0.35rem 0.55rem; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: none; font: inherit; font-size: 1.1rem; font-weight: inherit; line-height: normal; transition: none; }
html.ceres-lw-le-1279 .ceres-wide-input:not(:disabled):is(:hover, :active) { border-color: var(--border); background: var(--bg2); box-shadow: none; transform: none; }
html.ceres-lw-le-1279 .start-input.ceres-wide-input { padding-inline: var(--ceres-control-inline-pad); min-block-size: var(--ceres-control-min-block); }
.ceres-wide-input::placeholder { color: revert; font-size: inherit; font-weight: inherit; letter-spacing: inherit; opacity: revert; }
.ceres-wide-input:not(:placeholder-shown) { border-color: var(--primary); }
.picker-filters input:is([type=text], [type=number], [type=search], :not([type])):not(:placeholder-shown) { border-color: var(--primary); }
.controls-row input[type=text]#text-filter:not(:placeholder-shown) { border-color: var(--primary); }
.ceres-wide-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.picker-filters input:focus-visible, .picker-filters select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.start-input.ceres-wide-input { padding-inline: var(--ceres-control-inline-pad); }
.controls-row label { display: flex; align-items: center; gap: 0.35rem; }
.controls-row .board-filter-pill { position: relative; gap: 0; text-transform: none; }

.controls-row .board-filter-pill input { position: absolute; inline-size: 1px; margin: 0; pointer-events: none; }

/* S-8430 Look 2 (owner-accepted plan, Variant A -- "beautiful and compact, use icons
   but don't show them all at once"): the outer captions (Sort/Filter/Region/Rarity/
   Terrain/Worksite/Type) are now `.sr-only` inside `.board-filter-field` -- this rule
   just lets the label shrink to its one visible child's own width instead of the
   fixed-width caption+control pairing the row used to lay out. */
.controls-row .board-filter-field { flex: 0 0 auto; min-width: 0; }
.board-clear-filters-row { display: contents; }
.controls-row .board-clear-filters { flex: none; text-decoration-color: transparent; }

.controls-row .board-clear-filters:hover,
.controls-row .board-clear-filters:focus-visible { text-decoration-color: currentColor; }

/* Reserve Clear's footprint so activating a filter cannot change row geometry. */
html:not(.ceres-lw-le-1279) .controls-row {
  box-sizing: border-box;
  inline-size: 100%;
  /* S-16855b follow-up: the filter row keeps its approved height (0.9.893: 50.19px, set by the old
     33.19px text box). The text box now matches the 30px dropdowns and is centred like them, so the
     table, the flag's wallet-derived inset and the badge column stay exactly where they were. */
  min-block-size: calc(2.07421875rem + 1rem + 1px);
  flex-wrap: nowrap;
  white-space: nowrap;
  padding-inline-end: 0.75rem;
  overflow-x: auto;
  scrollbar-width: none;
  /* S-14980 (owner, verbatim: "the wallet and flag slider are still not sticky in
     their respective rows"): S-14440's "wallet scrolls away with the filter row"
     design is superseded -- the filter row itself now sticks under the page header,
     the same mechanism `thead.table-head-sticky` already uses (same `top` var, so
     the two rows stick at the identical offset and never overlap each other).
     `.controls-row` is a direct `<body>` child with no clipping/overflow-hiding
     ancestor between it and the viewport (unlike the old `th`-inside-`.table-scroll`
     failure mode documented above `thead.table-head-sticky`'s own rule), so plain
     `position: sticky` here needs none of that rule's workarounds. z-index 30: below
     the page header (50) and the Iris dock (1250), above the sticky table head (9)
     -- the filter row must stay reachable/visible over the table once both are
     stuck, and it never visually overlaps the header (same disjoint-band reasoning
     `.badge-bar-head`'s own z-ladder comment already documents for that pair).
     Desktop only -- mobile's `.controls-row` is a slide-up filter sheet
     (`.is-mobile-open`, below) with its own `position: fixed` override, untouched. */
  position: sticky;
  top: var(--ceres-header-height, 3.4rem);
  z-index: 30;
}
html:not(.ceres-lw-le-1279) .controls-row::-webkit-scrollbar { display: none; }
html:not(.ceres-lw-le-1279) .controls-row .board-filter-field { flex: 0 0 auto; min-inline-size: min-content; }
html:not(.ceres-lw-le-1279) .controls-row .csel-wrap { inline-size: max-content; min-inline-size: min-content; }
html:not(.ceres-lw-le-1279) .controls-row .csel-trigger {
  box-sizing: border-box;
  min-inline-size: max-content;
  white-space: nowrap;
}
html:not(.ceres-lw-le-1279) .controls-row input[type=text]#text-filter { box-sizing: border-box; min-inline-size: 0; max-inline-size: 100%; }
html:not(.ceres-lw-le-1279) .controls-row .board-filter-pill,
html:not(.ceres-lw-le-1279) .controls-row > .close-all-btn { flex-shrink: 0; white-space: nowrap; }
html:not(.ceres-lw-le-1279) .controls-row .board-clear-filters[hidden] { display: inline-flex; visibility: hidden; pointer-events: none; }

/* `flex: 1 1 auto; min-height: 0;` removed with `body`'s viewport height above
   (board-polish batch 19, item 16) -- those existed only to give `main` a
   bounded height to hand down to `.table-scroll`'s internal scroll region.
   `main` now sizes to its own content like any normal block; `display: flex;
   flex-direction: column` stays purely to stack its children vertically
   with the row `gap` semantics already relied on elsewhere in this file,
   not for height distribution. */
main#board-main {
  padding: 0 0.5rem; /* S-9360: one padding on every desktop width (was 1rem below a 1400 px board) */
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* Horizontal padding halved at >=1400px only (v0.5.4, 1440 overflow fix): the
   15 locked column widths sum to 1441px, 33px over the 1408px available at a
   1440 viewport with the old 1rem (16px each side) padding -- see
   work/ceres-width-diagnosis.md. This recovers 16px of the 33px; the rest
   comes from the Badges column itself (see `.badge-row`/`.badge` below).
   Gated behind the media query (not a bare rule) because
   tests/ceres/test_live_filter_reflow.py's 1280-viewport
   test_v0413_width_scroll_and_window_position_guarantees_hold pins
   `scroller.scrollLeft` to `min(180, scrollWidth - clientWidth)` -- at 1280
   the pre-fix margin (scrollWidth-clientWidth 194px) comfortably clears that
   180px probe, but this fix's combined ~34px reclaim (16px here + ~18px from
   the Badges column) drops it to 160px, UNDER 180 -- which pins scrollLeft to
   exactly the live max instead of a value with headroom, and that exact-max
   pin is sensitive to <1px sub-pixel jitter across lockColumnWidths()'s
   re-measurement on a filter round-trip (deterministically reproduced:
   160 -> 159 after filter-then-clear, confirmed via git-worktree bisection
   against the ceres.css this commit follows). 1400px keeps the fix scoped to
   the viewport it was diagnosed for (1440/1920) without touching that
   pre-existing 1280 test's own margin at all. */

/* Table column sizing (Florian, 2026-08-21 board-polish batch 9, item 2):
   "Make all columns use only as much space as they need with decent
   padding... right now they scale dynamically with the browser width but in
   an inconsistent way, where some columns are weirdly wide, like BasePP."
   Root cause: `width: 100%` on the table forced the browser to distribute
   any leftover horizontal space across columns (default `table-layout: auto`
   still sizes each column by its own content first, but then stretches the
   whole set to fill 100% of the container width) -- so a narrow viewport and
   a wide one produced different column widths for the SAME content, and the
   distribution wasn't proportional to what each column actually needed.
   Fixed by dropping the 100% stretch (`width: auto` -- the table now sizes
   to its own content, full stop) and wrapping it in `.table-scroll`
   (overflow-x: auto) so a table wider than the viewport scrolls WITHIN its
   own region -- the page body itself never gains a horizontal scrollbar,
   per Florian's explicit constraint ("horizontal scrolling on the table is
   acceptable, page-body horizontal scroll is not"). Numeric columns
   additionally get an explicit min-width sized in `ch` to the largest
   REALISTIC value from the live account's own observed range (harvest PP
   reaches ~546,350, base PP ~101,853 -- docs/ceres.md), not to whatever
   happens to be in the current snapshot, so the column doesn't visibly
   resize the next time a bigger number is captured. */
/* Sticky-header regression fix (Florian, 2026-08-21, batch 10): batch 9's
   `.table-scroll { overflow-x: auto }` above made this element the nearest
   non-`visible`-overflow ancestor of `table.plot-table th`, which is
   `position: sticky` -- per spec that makes THIS element the sticky containing
   block instead of the viewport. But `.table-scroll` had no bounded height, so it
   never actually scrolled vertically (the page/body did), which left the `th` row
   painted statically at its `top: 3.4rem` offset -- a value that only ever made
   sense as a compensation for `header.board-header`'s own sticky height, not as an
   offset inside this wrapper -- so the header rendered mid-table, over the first
   data rows, instead of pinned at the viewport top.
   Fixed (at the time) by making the page a viewport-height flex column so
   `.table-scroll` had a bounded region to scroll vertically within, with its
   sticky `th` re-sticking to ITS OWN top edge (`top: 0`).

   SUPERSEDED (board-polish batch 19, item 16, Florian: "make the main table on
   Ceres not fixed in height but fully scrollable. I dont want to have empty
   space there."). `.table-scroll` no longer BOUNDS its own height -- the
   table grows to its full content height and the PAGE scrolls it (see
   `body`'s own comment for why that no longer leaves empty space under a
   short table).

   LIVE REGRESSION, same-day correction (Florian: "the header row moved now a
   few plots into the main table and cannot be clicked"): the first attempt
   here set `overflow-y: visible` on `.table-scroll`, on the reasoning that a
   visible-y axis would stop this element from being a sticky containing
   block, handing `th`'s `position: sticky` back to the viewport. That
   reasoning was WRONG, and shipped without checking the computed value in a
   real browser -- caught after Florian saw it live, not before. CSS Overflow
   spec rule: when one axis is a non-`visible` value and the OTHER is
   `visible`, the `visible` one computes to `auto` instead (`overflow-x: auto`
   here forces `overflow-y: visible` -> `overflow-y: auto`, confirmed via
   `getComputedStyle` on the live element: `overflowY: "auto"`). Worse, that
   holds for ANY non-`visible` x-value including `clip` -- a scroll container
   is a per-BOX concept, not per-axis, so as long as `.table-scroll` provides
   horizontal scrolling at all, it unavoidably remains a scroll container and
   therefore the sticky containing block for `position: sticky` descendants on
   BOTH axes, no matter what the y-axis overflow value says. A `th` sticky to
   `.table-scroll`'s own top edge, `top: ~89px` (the measured header height),
   lands ~89px below the TABLE's own top -- squarely on top of the first 2-3
   data rows -- exactly the "moved a few plots into the main table" Florian
   saw. So as long as this element does horizontal scrolling, no CSS-only
   `position: sticky` on a `th` living inside it can ever pin to the viewport.

   REAL FIX: stop trying to sticky the `th` at all. The header row now lives
   [SUPERSEDED by S-9360 below: one table with a sticky <thead>, no head mirror]
   in a SEPARATE table (`#plot-table-head` inside `#table-head-sticky` in
   index.html) that is NOT a scroll-scoped descendant of `.table-scroll` --
   it's `.table-scroll`'s own sibling inside `main`, and `main` has no
   non-`visible` overflow, so ITS sticky containing block correctly is the
   viewport. `#table-head-sticky` itself carries `position: sticky; top:
   var(--ceres-header-height)` (see below) and its horizontal scroll offset is
   kept in sync with the real body table's `scrollLeft` by app.js
   (`syncHeadScroll()`) -- so the header appears to move left/right in lockstep
   with the data columns while staying vertically pinned, without ever being a
   descendant of the element that actually owns x-scrolling. `#plot-table`
   (the body table, in `.table-scroll`) no longer has a `<thead>` of its own;
   `lockColumnWidths()` was extended to measure column widths from BOTH tables
   (widest header label vs. widest mounted body cell, same contract as before)
   and writes the same pixel widths onto both tables' colgroups so the two
   stay column-aligned. `overflow-x: auto` stays here for the same FR-1670
   horizontal-scroll-without-page-scroll reason as originally, just no longer
   asked to also host a sticky element -- see `#table-head-sticky` and
   `#table-hscroll-proxy` below for the two things that now live outside it. */
/* S-9360 (owner: "the table must scale to all sizes gracefully ... Never special
   conditions, fully flexible"): `.table-scroll` no longer scrolls. It clips on x
   (`overflow-x: clip` is NOT a scroll container, so the table's sticky <thead>
   still refers to the viewport and no side-scroll bar or head-mirror sync is
   needed) and is the inline-size query container `ceres-table` whose width the
   table's fluid type, padding and icons read (`cqi`) and whose container query
   switches the short header labels. The separate head-mirror table, its scroll
   sync and the sticky bottom side-scroll proxy are gone. */
.table-scroll {
  overflow-x: clip;
  overflow-anchor: none;
  max-width: 100%;
  min-width: 0;
  container: ceres-table / inline-size;
  position: relative; /* S-13840: containing block for #badge-overlay */
}
/* S-14180 amendment (pre-landing review fix): without this, #badge-bar floats OVER the
   table's own last `col-num` cell (DEC stake) instead of beside it. Reserving the
   bar's own width plus a gap as end padding shrinks the table's content box by
   exactly that much, so its last column stops short of the bar's own left edge with
   visible breathing room instead of running flush underneath it. Gap widened
   S-14230 -> S-14290 (owner, S-14230: "Dec column must get small gap to the badges
   slider"; owner, S-14290: "push the DEC column further to the left, it is too
   close to the slider" -- `.5rem` read as still too tight): `1.25rem`. S-14340: the
   whole reserve now lives in ONE custom property, `--table-end-reserve`, so every
   consumer (this padding, and the expand-row's full-width override below) can never
   drift out of sync with each other. Desktop only -- `#plot-table-scroll` is
   already `display: none` below this tier (html.ceres-lw-le-1279
   #plot-table-scroll below), so the reservation would just be dead space there.
   S-14370 (owner, verbatim: "The tabe must continue under the slide-in badges."):
   the scroller no longer reserves this space as a gap AT THE EDGE OF THE TABLE --
   the table is `100%` of the scroller again, so its rows, zebra stripes, borders
   and header run all the way under `#badge-bar`. `--table-end-reserve` itself is
   kept in the last column: see the `td:last-child`/`th:last-child` rule below.
   S-16768k: Lock is now last and contains the reserve; DEC keeps its master width.
   IR-6730 (owner, verbatim, high: "the entire slide bar must be thinner because of
   that [sitting flush at the page edge]"): `var(--edge-w)` -> `var(--badge-bar-w)`
   (the bar's own new, narrower width) `+ var(--page-gutter, 0px)` -- `#badge-bar`
   moved OUT to `<body>` and now sits flush at the TRUE page edge (see that
   element's own comment), which is further right than `#plot-table-scroll`'s own
   right edge by exactly `--page-gutter` (the scrollbar-width gap, set by
   app.js::centerThemePicker()) -- without this extra term the DEC numbers would
   keep clearance from where the bar USED to sit, not where it sits now. `.75rem`
   (was `1.25rem`) -- the spec's own literal value for this ticket; the bar itself
   moving further right (flush) already restores most of the visible separation the
   larger gap used to provide by itself.
   S-14770: `--badge-bar-w` widened back to track `--edge-w` live, so this reserve
   grows with it automatically -- the DEC column keeps a visible gap from the bar
   at the wider width too, no formula change needed. */
html:not(.ceres-lw-le-1279) .table-scroll {
  --table-end-reserve: calc(var(--badge-bar-w) + var(--page-gutter, 0px) + .75rem);
}
html:not(.ceres-lw-le-1279) #plot-table-scroll {
  /* IR-7930: mirror S-15190's left-gutter cancellation at the right edge too. */
  margin-inline: -0.5rem;
  inline-size: calc(100% + 1rem);
  max-width: none;
}
html:not(.ceres-lw-le-1279) #plot-table-scroll > table.plot-table {
  /* Keep IR-7550's 8px column budget and add the historical half-pixel edge correction. */
  margin-inline: -0.5px;
  /* IR-7550: the two new ordinary columns use the table's spare desktop width
     budget so existing columns keep their measured widths. */
  inline-size: calc(100% + 8.5px);
}
/* IR-8371: equal inline padding gives every adjacent pair the same content gap.
   The last-cell end reserve remains separate from that inter-column gap. */
html:not(.ceres-lw-le-1279) table.plot-table :is(thead th, tbody tr.plot-row td) {
  padding-inline: clamp(.25rem, .3125cqi, .375rem);
}
html:not(.ceres-lw-le-1279) table.plot-table :is(col.col-w-dec, th[data-key="dec_needed"], tbody tr.plot-row td:nth-child(18)) {
  box-sizing: border-box;
  /* IR-8578: was calc(147.0579375px + 1.346875cqi), the master width from when this column carried the end reserve (now the Lock column's); the widest value is about 70px. */
  inline-size: calc(78px + 1.346875cqi);
  min-inline-size: calc(78px + 1.346875cqi);
}
/* Keep Terrain's measured track share while its content uses the shared inset. */
html:not(.ceres-lw-le-1279) table.plot-table :is(col.col-w-terrain, th[data-key="terrain"], tbody tr.plot-row td.terrain-cell) {
  box-sizing: border-box;
  inline-size: calc(102px + 1.23cqi);
  min-inline-size: calc(102px + 1.23cqi);
}
/* IR-8434: S-10990's left inset for the Terrain cell's icon, lost in 0.9.863 when the shared
   IR-8371 padding above replaced it. Cells only; the header label keeps the shared inset. */
html:not(.ceres-lw-le-1279) table.plot-table tbody tr.plot-row td.terrain-cell { padding-inline-start: 1.1rem; }
/* S-14420: the Wood/hr, Stone/hr, Iron/hr material-input columns (plan §3/§5 Version B,
   owner decision S-14320: three separate columns, not one combined Inputs/hr). CURRENT
   threshold: hidden through 1759px, shown from 1760px up (`html.ceres-lw-le-1759`, set
   by the pre-paint width script in index.html, not app.js). The `<col>` element's own
   `display: none` support for hiding a whole column is inconsistent across engines --
   this selector list matches every `.col-w-wood/-stone/-iron`-classed element (the <col>,
   the <th>, and every row's <td>), so the th/td rules are what actually hide the column;
   the <col> match is redundant belt-and-braces, not load-bearing. The display:none cells
   still count toward colSpan (PLOT_TABLE_COLUMN_COUNT, state.js) since colSpan counts DOM
   columns, not visible ones.
   History: MEASURED (owner review) with the frozen 0.9.677 text/row sizes, the three
   columns overflowed `.table-scroll` (clip, no scrollbar) from 1440 up to ~1650 -- DEC
   numbers cut off; clean again from 1680, giving the original `ceres-lw-le-1679` tier.
   S-14600 review fix (2026-09-20): the inline sort chevron then added real inline-flow
   width to every sortable header (~4.8px each), pushing that reveal point out -- MEASURED
   headless with the chevron change live: 1680 overflowed by ~51px (DEC numbers run under
   the badges bar), clean again from 1760. Threshold and tier name raised to the CURRENT
   1759/1760 to match; the old `ceres-lw-le-1679` name is retired (grepped clean, nothing
   else read it). */
html.ceres-lw-le-1759 .col-w-wood,
html.ceres-lw-le-1759 .col-w-stone,
html.ceres-lw-le-1759 .col-w-iron { display: none; }
/* The header row is #plot-table's own <thead>, pinned under the sticky board
   header (`--ceres-header-height`, measured by table.js updateHeaderHeightVar()).
   S-14340 pre-landing review fix: raised from 6 to 9 -- an expanded plot's own `td`
   (S-14340, z-index 8, below) must paint over `.badge-bar-row` (7, S-14440 z-fix
   round 2 moved this value onto the bar's real children -- see that rule's own
   z-ladder comment) while still staying BELOW the real sticky thead, or the thead
   disappears under an expanded plot while scrolled. `.badge-bar-head` is 10, ABOVE
   the thead (S-14440, the fix for the flag being unclickable) -- still below the
   app header (10, same value, never overlapping -- disjoint vertical bands) and its
   tabs (11). */
/* S-14440 z-fix history (unchanged at 9 throughout): round 1's working-but-
   compromised fix gave `.badge-bar` itself a real z-index of 10 (out-ranking this
   thead as one unit, for `.badge-bar-head` to stay clickable), which created a
   genuine cycle with S-14340's "expanded plot covers the bar" requirement (bar >
   thead > expand-row td > bar, impossible to satisfy with flat z-index values) and
   was accepted as a disclosed trade-off for one release. ROUND 2 (2026-09-20,
   owner-approved: "avoid the cycle") removes it at the source instead -- the bar's
   own glass fill (`backdrop-filter`, the actual forcing culprit) moved to a
   `.badge-bar::before` pseudo-element, so `.badge-bar` itself no longer forces a
   stacking context at all, and its children (`.badge-bar-head`/`.badge-bar-row`)
   compete directly against this thead with their OWN real z-index values instead of
   needing the whole bar raised. See `.badge-bar`'s own z-ladder comment for the
   full chain (head 10 > thead 9 > expand-row td 8 > row 7 > glass 6 > overlay 5,
   no cycle). */
thead.table-head-sticky {
  position: sticky;
  /* S-14980: was `var(--ceres-header-height, 3.4rem)` (clears the header alone) --
     `.controls-row` (the filter row) is now ALSO sticky at that same offset (see
     that rule's own comment), so this thead needs to clear BOTH, or its own
     column-label row sticks underneath the now-permanently-stuck filter row.
     `--ceres-header-row-height` (table.js updateHeaderHeightVar()) is the real
     measured header height plus the real measured filter-row height, combined --
     never a hardcoded guess, same reasoning `--ceres-header-height` itself already
     documents just above. Fallback covers the brief pre-JS window only. */
  top: var(--ceres-header-row-height, 6.5rem);
  z-index: 9;
}
.table-scroll.board-content-hidden, .table-head-sticky.board-content-hidden, .controls-row.board-content-hidden, .plot-card-list.board-content-hidden, .mobile-action-bar.board-content-hidden { visibility: hidden; } /* IR-5380: board presentations hidden until reveal */

/* Column sizing (board-polish batch 19, item 13 -- Florian DIRECTION CHANGE,
   2026-08-21: "remove any fixed width for all columns"... "many columns are
   STILL weirdly wide and use too much width! Performance is key!"). SUPERSEDES
   the `table-layout: fixed` + hand-maintained `.col-w-*` ch/px/rem block that
   used to live here (see git history if that math is ever needed again) and
   the column-width study's "keep ch for numeric columns" recommendation.

   No column carries a hardcoded ch/px/rem width any more. `table-layout: auto`
   (the default, set explicitly below) sizes every column from its own real
   content -- the header label vs. the widest mounted body cell, whichever is
   bigger -- exactly the browser behaviour Florian asked for. `width: auto` on
   the table itself (not `100%`) is deliberate: `100%` is what caused the
   ORIGINAL "some columns are weirdly wide, like BasePP" defect (see the
   `.table-scroll` comment above) by stretching content-sized columns to fill
   leftover viewport space -- content-sized columns without that stretch don't
   have that failure mode, so this table never reintroduces `width: 100%`.

   Virtualized windows can expose different body content as the page scrolls.
   app.js's `lockColumnWidths()` measures the initial real window once, then
   reuses the explicit pixel widths so later windows cannot move the columns. */
/* S-9360: `table-layout: auto; width: 100%` -- the browser gives every column
   its content width and shares the rest of the board width across ALL columns in
   proportion to their content (no JS widths, no column with special slack, no
   side scrolling).
   S-14330 (version A, ceres-table-redesign plan §2/§5): body type is a true
   14-15px clamp regardless of board width -- `clamp(.875rem, .75rem + .15625cqi,
   .9375rem)`, exactly 14px at a 1280 board and 15px at 1920, replacing the old
   ~12.57-14.85px `max()` formula. Line-height 1.2 for the calmer row rhythm below. */
table.plot-table { table-layout: auto; width: 100%; border-collapse: collapse; font-size: clamp(.875rem, .75rem + .15625cqi, .9375rem); line-height: 1.2; }
table.plot-table th, table.plot-table td {
  text-align: left;
  /* S-14330: .375rem block padding; inline padding stays fluid, `clamp(.25rem,
     .3125cqi, .375rem)` (4-6px over 1280-1920), replacing the old tighter
     0.25rem/max() pairing now that the taller row has room to breathe. */
  padding: .375rem clamp(.25rem, .3125cqi, .375rem);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* S-14330 pre-landing fix: `min-block-size` is inert on a table cell in Chromium
   (rows still measured 33.8px against a 36px target). `block-size` on the DATA
   ROW's own td forces the 36px (36 rows still grow taller when content wraps --
   this is not a max). Scoped to `tr.plot-row td` only -- NOT `tr.virtual-spacer td`
   (padding:0/border:0, must stay collapsed to its measured spacer height) and NOT
   `tr.expand-row td` (the worker sub-table cell, `colSpan`, must never be capped).
   Virtualized row measurement (table.js::measureMountedRowHeights) still records
   the real mounted height for anything taller than this. */
table.plot-table tbody tr.plot-row td { block-size: 2.25rem; box-sizing: border-box; /* review fix: block-size is the border box, not the content box, so 36px/44px render as measured */ }
/* S-14370: reserve inside the last cell keeps the row background under the bar.
   Virtual spacers and expanded rows retain their own padding. */
html:not(.ceres-lw-le-1279) table.plot-table thead th:last-child {
  padding-inline-end: calc(var(--table-end-reserve) + clamp(.25rem, .3125cqi, .375rem));
}
html:not(.ceres-lw-le-1279) table.plot-table tbody tr.plot-row td:last-child {
  padding-inline-end: calc(var(--table-end-reserve) + clamp(.25rem, .3125cqi, .375rem));
}
/* Header labels wrap instead of ellipsis-truncating (2026-08-21, found live
   while verifying the fixed-width table; re-verified after batch 19 item 12's
   font-size increase -- item 5 still holds, no header may wrap to a second
   line at 1920/1280/1024, checked with `Range.getClientRects()` line
   counting). Safe to wrap ONLY here, not in tbody -- every mounted body row
   must stay a uniform single-line height (renderRow()'s own contract), but
   `<thead>` sits outside that, so a would-be-2-line header just gets a wider
   auto-sized column instead (table-layout: auto sizes to the header's own
   single-line min-content by default; this rule exists as a safety net, not
   the primary sizing mechanism any more now that item 13 removed the
   `ch`-per-column budget that used to make wrapping likely). */
table.plot-table thead th { white-space: nowrap; overflow: visible; text-overflow: clip; } /* S-9360: a header never wraps; narrow boards get the short labels (container query below) */
/* Right-align numeric columns (Florian, 2026-08-21 fifth polish pass: "Align
   PP and DEC numbers right, so it is easier to read"). Base PP, Boostable PP,
   Harvest PP, Mult, Workers, DEC needed, PP/DEC in the main table; CC/CC max,
   Base PP, Terrain%, Harvest PP, DEC in the expanded worker sub-rows --
   header text right-aligned to match its column. `font-variant-numeric: tabular-nums` forces uniform digit width
   so thousands separators stack vertically -- right-alignment alone still
   looks ragged without it, since most fonts render "1" and "8" at different
   widths. Honest-null `—` cells inherit the same right-alignment, never
   converted to zero. Same specificity lesson as the icon-centering block
   above: `table.plot-table td.col-num` matches the base rule's own
   `#plot-table` prefix so this reliably overrides `text-align: left`. */
table.plot-table th.col-num,
table.plot-table td.col-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* Sticky header highlight + stacking fix (2026-08-21, Florian: "Table header needs
   a highlight background and must be higher than the icons in the table to avoid
   the icons moving above the fixed table header"). Two separate problems, both
   visible while scrolling a real board: (1) the header painted `var(--bg)` -- the
   plain PAGE background, the one token every other band in this UI (table rows
   included) already reads as its own base colour -- so the sticky header had no
   visual weight of its own, distinct from `var(--bg2)` used everywhere else a band
   needs to read as "its own row" (`header.board-header`, `.controls-row`'s --bg3).
   (2) `position: sticky` alone leaves `z-index: auto`, and at the time this was
   written this table's per-column icons (`.totem-icon` etc.) carried their own
   `filter: drop-shadow(...)` -- filter creates a stacking context for that icon,
   and an auto-z-index sticky ancestor does NOT reliably out-rank a descendant
   stacking context in every engine, so a row scrolling up from underneath could
   paint its icon in front of (instead of behind) the sticky header. An explicit
   `z-index` on the sticky `th` removed the ambiguity outright, and stays -- FR-1672
   (2026-08-21) later baked that `filter` into the icon assets themselves and
   removed the runtime CSS rule, but an explicit z-index costs nothing and is one
   less thing to re-verify if a future icon ever grows its own stacking context
   again (opacity < 1, a transform, etc.). */
/* Header row painted in the primary token (2026-08-21 board-polish batch 19,
   item 9, Florian: "color the table header in primary color"). `--bg` is used as
   the text colour -- Iris has no dedicated on-primary/contrast token (checked
   canonical.css directly; none exists in any of the four themes), so `--bg` is
   the closest available token: in every theme it's the page's own background
   extreme (near-black in alchemical/dark, near-white in light/wood), which is
   what a hand-picked on-primary colour would approximate anyway. Measured
   WCAG contrast of `--primary` vs `--bg` per theme (relative-luminance
   formula): alchemical 8.71:1, dark 4.86:1, wood 4.56:1, light 3.57:1 -- worst
   case is light theme, which clears AA-large-text (3:1) but falls short of
   AA-normal-text (4.5:1); no other Iris token scores better in light theme
   (checked --bg2/--body/--heading/--hi, all >=1 pt worse) and there's no local
   hex fallback to reach for (see this file's own top-of-file rule against
   that). Flat `background-color`, no `filter`/`backdrop-filter`/`box-shadow`
   per FR-1672 (those cost 7068ms of raster on 700 icons; a solid fill is
   free). Fully opaque on both tokens in all four themes -- verified no row
   content bleeds through at a mid-scroll offset. */
/* `top: 0` -> `top: var(--ceres-header-height)` (board-polish batch 19, item 16).
   `.table-scroll` no longer scrolls vertically, so `th`'s sticky containing
   block is the viewport again, and it must clear `header.board-header`'s own
   sticky band (also viewport-pinned, at `top: 0`) instead of sitting under it.
   `--ceres-header-height` is set on `:root` by app.js from the header's real
   measured `getBoundingClientRect().height` (on load, on resize, and on any
   ResizeObserver-detected content change, e.g. the totals-strip re-wrapping) --
   never a hardcoded rem value; a stale hardcoded `3.4rem` is exactly what
   caused FR-1670 the first time. The `3.4rem` fallback below only covers the
   brief window before app.js's first measurement runs. */
/* `position: sticky; top: ...` REMOVED from `th` itself (board-polish batch 19,
   item 16 correction) -- the sticky pinning now happens one level up, on
   `.table-head-sticky` (see its own comment above for why: a `th` inside
   `.table-scroll` can never correctly sticky to the viewport while that
   ancestor also does horizontal scrolling). `th` now only carries its own
   visual styling; the wrapper's `position: sticky` does the pinning for the
   whole header row at once. `table.plot-table` (not `#plot-table`) throughout
   this file as of this same correction -- `#plot-table` (the id) now names
   ONLY the body table (tbody, no thead); the header row's own table
   (`#plot-table-head`) shares the `.plot-table` CLASS so it inherits every
   cell-styling rule below without duplicating them, while the two tables'
   different ids keep JS's per-table lookups (colgroup widths, scrollWidth,
   etc.) unambiguous. */
/* S-14330 (version A, ceres-table-redesign plan §2/§5, owner S-14320: "subtle gradient"
   header): replaces the flat `var(--primary)` fill (and the light-only contrast-fix
   override that used to sit here, board-polish batch 19 item E) with a two-stop Iris
   gradient in EVERY theme -- 65/35 primary/heading at the top fading to 50/50 at the
   bottom. Both stops measured with the WCAG relative-luminance formula against `--bg`
   (the header text colour): alchemical 8.71->8.71, dark 6.55->7.43, light 4.86->5.58,
   wood 6.51->7.56 -- all clear AA-normal-text (4.5:1) at both ends, so light theme no
   longer needs its own override and dark/wood gain contrast margin they didn't have
   before. Taller header (2.75rem/44px, owner-approved S-14320), bold weight, tighter
   line-height/letter-spacing for the calmer, more legible look; bottom divider in
   `--border-bright` and a 1px top highlight in `--hi` replace the old plain header. */
table.plot-table th {
  cursor: pointer; user-select: none;
  /* S-14330 pre-landing fix: `min-block-size` is inert on a table cell in
     Chromium (header measured 28.4px against a 44px target) -- `block-size`
     forces it, same fix as the data-row td rule above. */
  block-size: 2.75rem; box-sizing: border-box; /* review fix: block-size is the border box, not the content box, so 36px/44px render as measured */
  font-weight: 700; line-height: 1.1; letter-spacing: .025em;
  padding-inline: clamp(.25rem, .3125cqi, .375rem);
  background: linear-gradient(180deg, color-mix(in srgb, var(--primary) 65%, var(--heading) 35%), color-mix(in srgb, var(--primary) 50%, var(--heading) 50%));
  color: var(--bg);
  border-bottom: .125rem solid var(--border-bright);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--hi) 10%, transparent);
}
/* S-14210 gave `.badge-bar-head` this same header gradient treatment; S-14440
   (owner: "same look as the sliders") replaced it with the shared Iris strip recipe
   instead (see that rule, near `.badge-bar`) -- this header's own gradient is
   unaffected, only the head glyph no longer copies it. */
/* S-14330 (version A, plan §2): every sortable header carries an accessible sort
   indicator, maintained via `aria-sort` (app.js, near the existing `th[data-key]`
   click binding) and a `.sort-chevron` span (index.html) reusing the same
   `--ceres-chevron-art` mask buttons.css already defines for every other disclosure
   control in this codebase -- no new icon family, no Unicode arrow. `.6rem`, 55%
   opacity when unsorted (`aria-sort="none"`), full `var(--bg)` opacity when active;
   rotated 180deg for ascending (arrow up), unrotated for descending (arrow down, the
   chevron's native direction) -- no layout shift between sort states, only
   opacity/rotation change.
   S-14600 (owner, verbatim: "make sure the chevrons for sorting the table header
   columns sits right after the header text, currently it is at the far right edge of
   the header cell"): the chevron moved from an absolutely positioned inline-end slot
   to an inline-flow element that sits directly after the label text -- `position:
   static`, `display: inline-block` so the rotate transform still applies, `vertical-
   align: middle` to sit on the text baseline, and a small `.3rem` start margin as the
   gap instead of the old fixed inline-end inset. This also retires the `.6rem`
   inline-end padding reserve `th[data-key]` used to carry (no longer needed now the
   chevron is in flow) and the S-14370 last-column inset override below (the last
   column now only reserves `--table-end-reserve` for the badges bar, same as every
   other column's own end padding). */
table.plot-table th[data-key] .sort-chevron,
table.plot-table .table-sort-control .sort-chevron {
  position: static; display: inline-block; vertical-align: middle;
  margin-inline-start: .3rem; inline-size: .6rem; block-size: .6rem;
  background: var(--bg); opacity: .55;
  -webkit-mask: var(--ceres-chevron-art) center / contain no-repeat;
  mask: var(--ceres-chevron-art) center / contain no-repeat;
  transition: transform .18s ease, opacity .18s ease;
  pointer-events: none;
}
table.plot-table th[data-key][aria-sort="ascending"] .sort-chevron,
table.plot-table th[data-key][aria-sort="descending"] .sort-chevron,
table.plot-table .table-sort-control[aria-pressed="true"] .sort-chevron { opacity: 1; }
table.plot-table th[data-key][aria-sort="ascending"] .sort-chevron,
table.plot-table .table-sort-control[aria-pressed="true"] .sort-chevron { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  table.plot-table th[data-key] .sort-chevron,
  table.plot-table .table-sort-control .sort-chevron { transition: none; }
}
table.plot-table .table-sort-control {
  border: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
table.plot-table .table-sort-control + .table-sort-control { margin-inline-start: .55rem; }
 table.plot-table tbody tr.plot-row { cursor: pointer; }
 table.plot-table tbody tr.virtual-spacer td { padding: 0; border: 0; }
/* Zebra striping (0.3.4 board-fix batch, Iris tokens only). `.row-alt` is set from
   app.js's own filtered/sorted row index (see renderRow()), never CSS nth-child --
   this table's collapsed expand-row siblings stay in the DOM, which would throw off
   an nth-child parity. Base rows are transparent (page --bg shows through); the
   stripe uses --bg2 and hover promotes to --bg3, so all three states (plain/
   striped/hovered) stay visually distinct. Terrain/territory cells keep their own
   explicit background (a data colour, set with two-class specificity below) and are
   unaffected -- this only paints the transparent gap behind cells that don't set
   their own fill. */
/* S-14330 (version A): zebra/hover recipes moved onto Iris color-mix() (owner-approved
   S-14320 values) rather than a flat --bg2/--bg3 swap -- a calmer stripe (72% --bg2 over
   --bg) and a warmer, more visible hover (8% --primary tint over --bg3) plus a left accent
   bar so keyboard focus-within a row reads the same as mouse hover. Terrain/territory
   cells keep their own explicit background per the comment above, unaffected. */
table.plot-table tbody tr.plot-row.row-alt { background: color-mix(in srgb, var(--bg2) 72%, var(--bg)); }
table.plot-table tbody tr.plot-row:hover,
table.plot-table tbody tr.plot-row:focus-within {
  background: color-mix(in srgb, var(--primary) 8%, var(--bg3));
  box-shadow: inset .1875rem 0 0 var(--accent);
}
/* Overrides the fixed-width table's own `overflow: hidden; text-overflow:
   ellipsis` (above) -- that rule exists to truncate a MAIN row's single-line
   cell content gracefully when it's wider than its fixed column, but this
   `td` spans all 16 columns (colSpan) and holds the full multi-row worker
   subtable, which must never be clipped. */
/* padding: 0 (item 14, Florian: "it must sit at the upmost top left corner,
   hitting exactly the pixels from the row of the plots which opens the
   worker table, with zero padding or free space") -- overrides the generic
   `th, td { padding: 0.25rem 0.45rem }` rule above, which this cell was
   still inheriting. Measured live before this fix: that inherited padding
   (4px/7.2px) was the FIRST of three stacked insets between the parent plot
   row's true edge and the terrain photo -- see `.slot-editor`'s own comment
   for the other two. The loading/error/"not loaded" text states
   (`workerSubtable()` in app.js) render a bare `<p>` directly into this now
   padding-less cell, so they get their own padding restored just below. */
table.plot-table tbody tr.expand-row td { background: var(--bg2); white-space: normal; overflow: visible; text-overflow: clip; padding: 0; }
table.plot-table tbody tr.expand-row td > p { padding: 0.65rem 0.8rem; margin: 0; }
/* S-14340 (owner, verbatim): "the new badges bar must be fully hidden behind an
   opened plot and can never use space away like now from plot and picker." An
   expanded plot's own details/picker (this `td`) must paint OVER `#badge-bar`
   instead of stopping short of it. Stacking this `td` above `.badge-bar-row` (7)
   and `#badge-overlay` (5) -- `z-index: 8` on a `position: relative` containing
   block, below the sticky board thead (9) -- makes the expansion cover the bar's
   own rows, while the real sticky header still stays on top while scrolled.
   S-14440 z-fix history: round 1 briefly raised this to 11 (a disclosed trade-off,
   needed only because `.badge-bar` itself had been given a real z-index of 10 at
   the time); round 2 (2026-09-20) removed that need at the source (see `.badge-bar`
   's own z-ladder comment) and this rule is back to its ORIGINAL, un-traded-off
   value -- the S-14340 guarantee this comment describes is fully restored.
   S-14370 revision: the `td` itself now already spans the table's full width (the
   `.table-scroll` end reserve that used to narrow it is gone -- see that rule's own
   comment), so `.slot-editor` no longer needs to widen itself past its parent
   cell; plain `inline-size: 100%` is enough. `max-inline-size: none` stays --
   without it, the unconditional `.slot-editor { max-inline-size: 100% }` rule
   (below, unrelated to this table context) is redundant here but harmless; kept
   so this rule does not depend on cascade order with that one. Desktop only,
   matching the reserve rule's own scope -- below that tier the table (and the
   bar) don't exist at all. */
html:not(.ceres-lw-le-1279) table.plot-table tbody tr.expand-row td { position: relative; z-index: 8; }
html:not(.ceres-lw-le-1279) table.plot-table tbody tr.expand-row td > .slot-editor {
  inline-size: 100%;
  max-inline-size: none;
  box-sizing: border-box;
  background: var(--bg2);
}

.badge {
  display: inline-block;
  font-size: 0.7rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  margin-right: 0.25rem;
  border: 1px solid transparent;
  cursor: help;
}
/* Padding 0.4rem -> 0.3rem each side at >=1400px only (v0.5.4, 1440 overflow
   fix): part 2 of reclaiming the table's 33px deficit (see
   `main#board-main`'s own >=1400px override above for part 1, INCLUDING why
   this is gated behind the same media query rather than a bare rule -- the
   1280-viewport filter-reflow test's scrollLeft-pinning fragility). R8
   ("Badges is the ONLY column allowed to change width", the old slot-editor
   plan section 13) is RETIRED by IR-5790: which columns receive slack is now
   declared per-column via `data-grow` on the colgroup (see table.js
   distributeColumnWidths()), not a positional/last-column rule. Badges is
   currently the one growable column, so the accepted Full HD look is
   unchanged. This rule does NOT enable wrapping -- `.badge-row` below stays
   `flex-wrap: nowrap` at desktop widths per Florian's 2026-08-21 "badges must
   never stack and double the row height" ruling; the le-1439 tier's own rule
   (FR-1946) is the one wrap exception. */
html.ceres-lw-ge-1400 .badge { padding: 0.05rem 0.3rem; }
.badge.badge-staged-cooldown { background: transparent; font-weight: 700; cursor: help; }

/* BLOODLINE ("Toil and Kin") companion status in the worker sub-table's Abilities
   cell (board-polish batch 16, 2026-08-21) -- Florian: a bloodline check so an
   inert bloodline (no companion sharing the holder's sub_type -- contributes ZERO
   per library/land-worker-abilities.md §3) doesn't render identically to an active
   one. Active gets --ok (same "this is working" green as badge-available);
   inactive is deliberately QUIET (--meta, reduced opacity), same treatment as
   badge-building_in_box above -- present as fact, not alarming.
   IMPORTANT (Florian correction, 2026-08-21, §3a): inactive is common (54/57 live
   holder instances) and is very often DELIBERATE, not a mistake -- his VoV/Land
   cards are frequently staked solo for their OTHER abilities (skin drops, glint
   bombs), never for farming, and their low base_pp is the tell. This colour choice
   must stay purely informational -- never style it as a warning/error state, and
   never let it read as "this needs fixing." */
.ability-bloodline-active { color: var(--ok); }
.ability-bloodline-inactive { color: var(--meta); opacity: 0.75; }

/* Badge row -- one line, fixed row height (Florian, 2026-08-21 polish pass: "make
   sure the badges are not stacked and forcing the table row to be double-sized").
   The old problem/property split rendered as two stacked `display:block` lanes,
   doubling row height on any plot carrying both kinds. Now every badge is a short,
   fixed-width abbreviation chip (ceres/static/app.js:BADGE_ABBR) in one
   `inline-flex` row -- only BiB and UT use dashed borders; all other badges use solid borders but never wraps to a second line. Full label + tooltip text
   stay in `title`, nothing is lost. */
.badge-row { display: inline-flex; flex-wrap: nowrap; align-items: center; gap: 0.2rem; }
/* gap 0.2rem -> 0.1rem at >=1400px only (v0.5.4, 1440 overflow fix, second
   half of the Badges-column shrink alongside `.badge`'s own >=1400px padding
   cut above -- same media-query gating rationale, see `main#board-main`'s
   comment). Still `flex-wrap: nowrap` at every width, unchanged from
   Florian's 2026-08-21 ruling. */
html.ceres-lw-ge-1400 .badge-row { gap: 0.1rem; }
.badge-row .badge { margin-right: 0; min-width: 1.6rem; text-align: center; font-weight: 600; letter-spacing: 0.02em; }
.badge-problem { border-style: solid; }
.badge-property { border-style: solid; }
.badge-building_in_box, .badge-unstable_totem { border-style: dashed; }

/* S-13840: badges leave the table's own sizing -- see library/ceres-badge-overlay.md.
   The full list moves into #badge-overlay below (and stays, unchanged, in the mobile
   card via .plot-card-badges .badge-row). S-14180 amendment: the fixed-width indicator
   itself is no longer a table cell -- it is #badge-bar, right below -- so the table
   has no badge column at all any more (index.html's colgroup/thead, rows.js). */

/* S-14180 (owner, verbatim): "Make the entire right column where the current badge
   group dots and the tick are, the entire slide-out. Separated from the table. Any
   click on the right column will slide it out... Make the icon in the header fully
   connected in the design of the slider-bar." #badge-bar is a standalone vertical bar,
   rounded only on its near-table side, so it reads as a strip attached to the
   table's right edge, not a full pill. The WHOLE bar is the click/keyboard target
   (role="button"/tabindex, index.html; badge-overlay.js wires click + Enter/Space).
   S-14210: the open state mirrors the Iris tray's own open-state fill (canonical.css
   `.theme-picker.is-open`).
   IR-6730 (owner, verbatim, high: "you broke iris -- all three sliders must sit on
   the furthest right edge of the page. zero gap."): `#badge-bar` moved OUT of
   `#plot-table-scroll` to a direct child of `<body>` (index.html) -- the scroller
   only ever offered its OWN right edge, never the true page edge the owner is
   asking for: the scroller lives inside `main#board-main`, which carries its own
   `padding: 0 0.5rem` (8px each side) -- a real, permanent page margin, not a
   scrollbar artefact. `#badge-bar` moved OUT of `main` entirely (to `<body>`,
   which carries NO horizontal padding of its own) specifically to escape that
   margin -- a plain `right: 0` here IS already flush with the true page edge
   (measured: `body`'s own bounding rect and a `position: fixed` element's `right:
   0` land at the exact same x in this codebase's environment -- there is no
   separate scrollbar-vs-body-edge mismatch left to compensate for once `main`'s
   own padding is out of the equation). `--page-gutter` (below, and see
   `centerThemePicker()`'s own comment) is a SEPARATE thing -- the real gap between
   the scroller's own (still `main`-padded) right edge and the bar's new, flush
   position -- consumed only by `--table-end-reserve` and `.badge-overlay`'s own
   `right`, which both still need to reach all the way from the table's own
   content to the bar's new home. It is `position: absolute` (document
   coordinates, so it still scrolls WITH the page -- its rows track specific table
   rows, unlike the always-fixed Iris/Wallet/scroll-top handles), with `top`/
   `block-size` JS-computed (app.js::centerThemePicker()) from the scroller's own
   live rect every layout pass. `.badge-bar-row`'s own position (badge-overlay.js) is
   UNCHANGED -- still the plain `<tr>`-rect-relative value it always was: both the
   row's own document top and the bar's own document top are read in the SAME
   synchronous pass, so `window.scrollY` cancels out of the difference exactly as it
   always did, no compensation needed for the bar's new document-absolute home.
   The head glyph moved OUT of the bar entirely, into a separate `#badge-flag`
   (`.badge-bar-head`, below) -- `position: fixed`, not nested/sticky inside this
   now-document-scrolling bar, because only a genuinely `position: fixed` element's
   `right: 0` reaches the true, scrollbar-proof viewport edge the Iris/Wallet/
   scroll-top handles already use (nesting it inside `#badge-bar` would inherit
   THIS element's own gutter-adjusted right edge instead, reintroducing the exact
   mismatch this ticket removes). `--badge-bar-w` (then 1.5rem/24px, near `--edge-w`
   above) replaced `--edge-w` for this bar and its flag -- "the entire slide bar
   must be thinner because of that [sitting flush]." S-14770 supersedes the
   narrower value: `--badge-bar-w` now equals `--edge-w` again (see that token's
   own comment above), so this bar is exactly as wide as the Iris/Wallet pills. */
/* Z-LADDER (S-14440 z-fix, ROUND 2, 2026-09-20 -- owner-approved: "avoid the cycle,
   don't accept the trade-off"): `.badge-bar-head` 10 (above the real thead, so the
   flag stays clickable), `thead.table-head-
   sticky` 9, `tr.expand-row td` 8 (S-14340, back to its ORIGINAL value -- see that
   rule's own comment for why this no longer needs raising), `.badge-bar-row` 7,
   `.badge-bar::before` 6 (the bar's own glass fill, see below), `#badge-overlay` 5.
   Full chain, no cycle: head (10) > thead (9) > expand-row td (8) > row (7) >
   glass (6) > overlay (5).
   IR-6930 (2026-09-20) update: `header.board-header` was 10 (same value as
   `.badge-bar-head`, disjoint vertical bands, never a real collision) -- it is now
   50, because the wallet stack (`#edge-tabs`/`.dec-drawer`) must paint BELOW it
   whenever the filter row scrolls under the sticky header. That header change does
   not touch this bar/thead/overlay chain at all -- none of it ever competed with
   the header -- so the chain above is unchanged; only the header's own absolute
   number moved, see its own comment.
   S-14980 (2026-09-20) update: the wallet stack itself moved twice more after that
   (40/39 -> 20/19 under IR-6930 -> back to 40/39 here, once `.controls-row` also
   became sticky at z-index 30 and needed outranking for clicks) and `.controls-row`
   joined the ladder at 30 -- none of that touches this bar/thead/overlay chain
   either, since the filter row sits above the table entirely, in its own band; see
   `.controls-row`'s own comment and the `.edge-tabs`/`.dec-drawer` rules for the
   current numbers.
   ROUND 1 (measured FAIL, kept here for the history): removing `.badge-bar`'s
   stacking context by setting `z-index: auto` ALONE did not work --
   `document.elementFromPoint` at the head's own centre kept returning the real
   `<th>`, even with the head's OWN z-index forced to 99999 inline. Root cause:
   `backdrop-filter` (the bar's own glass look, formerly declared directly on
   `.badge-bar`) forces a stacking context REGARDLESS of the element's z-index --
   `z-index: auto` does not opt out of that the way it would for a plain
   `position: absolute` box with no filter. This codebase already carries prior art
   for exactly this kind of stacking surprise (`table.plot-table th`'s own "Sticky
   header highlight + stacking fix" comment, 2026-08-21): "position: sticky alone
   leaves z-index: auto, and ... an auto-z-index sticky ancestor does NOT reliably
   out-rank a descendant stacking context in every engine" -- confirmed here for the
   INVERSE case: a `position: sticky` DESCENDANT cannot escape a `backdrop-filter`
   ANCESTOR's forced context by raising its OWN z-index either.
   ROUND 1's working-but-compromised fix (`.badge-bar` given a real z-index of its
   own, out-ranking the thead AS ONE UNIT) created a genuine cycle with S-14340's two
   existing requirements (bar > thead, expand-row td > bar, thead > expand-row td --
   three that cannot all hold with flat z-index values) and was ACCEPTED as a
   disclosed trade-off for one release. ROUND 2 removes the cycle instead: the glass
   fill (`background`/`backdrop-filter`, the actual forcing culprit) moved OFF
   `.badge-bar` onto a dedicated `.badge-bar::before` pseudo-element (below) --
   `.badge-bar` itself carries NEITHER any more, so it no longer forces a stacking
   context at all, and its real children (`.badge-bar-head`/`.badge-bar-row`) now
   compete DIRECTLY in the scroller's own shared context, each with its own REAL
   (not merely local) z-index. `tr.expand-row td` is restored to its original 8 --
   the S-14340 "real thead never disappears under an expanded plot" guarantee is
   fully restored, no trade-off left. */
/* S-14660 (owner, verbatim: "flag button appears, then disappears behind the
   loadig animation, then reappears with the full board. Stop that flashing,
   it must first be visible when the board has loaded"): the SAME S-14580
   treatment the Wallet picker (`#edge-tabs`) already has -- hidden by default,
   pre-JS, no `transition` (nothing to animate FROM until deliberately shown).
   `#badge-bar` and `#badge-flag` (`.badge-bar-head`, below) join it: all three
   are revealed TOGETHER, in the same tick, by one shared gate
   (`app.js::maybeRevealEdgeHandles()`) once EVERY ONE of the Wallet's own
   position, the bar's own position, the bar's own rows (badge-overlay.js's
   first `syncBadgeOverlay()` pass), and the board's own reveal are all true --
   see that function's own comment for the full "reveals wallet, flag and bar
   together" reasoning. */
.badge-bar {
  position: absolute;
  /* IR-6800 review question, answered: `--badge-bar-top`/`--badge-bar-h` are
     NEVER `setProperty`'d anywhere -- checked, deliberately not vestigial.
     app.js::centerThemePicker() writes this element's real `top`/`height`
     directly as INLINE styles (`badgeBarEl.style.top`/`.style.height`),
     which always win over these var()-with-fallback declarations regardless
     of whatever the custom properties themselves hold. These two vars exist
     ONLY as the pre-JS/no-JS fallback (0px / 100%, before the first
     measurement pass has run) -- the exact same idiom `--badge-flag-top`/
     `--badge-flag-h` already use on `.badge-bar-head` below (also never
     `setProperty`'d, also always overridden by that element's own inline
     `flag.style.top`/`.style.height` writes). Kept as-is; this is the
     established pattern, not a bug or missing wiring. */
  top: var(--badge-bar-top, 0px);
  right: 0;
  inline-size: var(--badge-bar-w); block-size: var(--badge-bar-h, 100%);
  z-index: auto;
  cursor: default;
  visibility: hidden;
  opacity: 0;
}
.badge-bar-revealed {
  visibility: visible;
  opacity: 1;
  animation: edgeTabsFadeInRight .6s 1 cubic-bezier(.77, 0, .175, 1);
}
/* The bar's own glass fill, now painted on a pseudo-element instead of the bar
   itself -- see the z-ladder comment above for why (`backdrop-filter` forces a
   stacking context on whatever carries it; moving it here keeps `.badge-bar` itself
   free of one). `position: absolute; inset: 0` fills the bar's own box exactly;
   `pointer-events: none` keeps it purely decorative -- the bar's own click/keyboard
   target (`role="button"`, index.html) is unaffected, still the whole `.badge-bar`
   element. `z-index: 6` -- see the z-ladder above for the full chain this sits in. */
.badge-bar::before {
  content: '';
  position: absolute; inset: 0;
  z-index: 6;
  border-radius: 5px 0 0 5px;
  background: color-mix(in srgb, var(--shadow) 40%, transparent);
  backdrop-filter: saturate(120%) blur(2px);
  -webkit-backdrop-filter: saturate(120%) blur(2px);
  pointer-events: none;
}
/* S-14280 (owner: "remove the dark left border of the slider over the table"): the
   shared resting-state chrome (above) used to also carry the ambient
   `box-shadow: 0 0 18px -6px var(--primary)` glow that `.edge-picker` still keeps.
   That glow is a soft, all-around halo on a compact ~40-48px pill (the Iris picker,
   the wallet toggle) -- but `.badge-bar` is `block-size: 100%` (the WHOLE table
   height), flush to the viewport's right edge, so only the glow's TABLE-FACING (left)
   portion is ever visible; over the bar's full height that reads as a long, solid,
   `--primary`-tinted stripe rather than a halo -- exactly the "dark left border"
   reported. No literal `border-left`/`border-inline-start` was ever set on
   `.badge-bar` or `.badge-bar-head` (checked). Fix: the ambient glow is dropped from
   the bar's RESTING state (removed from the shared rule above); the interactive
   hover/focus glow below is unaffected -- it is a deliberate, transient affordance,
   not a permanently-visible edge. `box-shadow` does NOT force a stacking context
   (unlike `backdrop-filter`), so it stays directly on `.badge-bar` itself -- no need
   to move it to the `::before` pseudo-element too. */
/* S-15930 / S-16100 item 2 (owner, verbatim: "make sure the hover over the flag as well as the active
   slide-out state both trigger the current glowing hover effect we use when hovering any single badge"):
   #badge-flag is a separate element (IR-6730), so hovering it never reached `.badge-bar:hover`. The same
   declaration is reused, never a second look: the column glows while the flag is hovered or focused and
   while the slide-out is open. badge-overlay.js sets the two classes on `.badge-bar` itself; a
   `body:has(#badge-flag...)` selector did the same job but made every flag state change restyle the whole
   page, measured at +0.5 s on the first open. */
/* S-16892 (owner 2026-09-28: "Bring back the super cool glow for the badge column at flag hover"): restored from
   0.9.881 exactly as approved (S-15930/S-16100, S-16550) -- it was removed unasked by 0.9.882. The column glows on
   hover, while the flag is hovered or focused, and while the slide-out is open; no transition, so nothing flickers. */
.badge-bar:hover, .badge-bar.is-flag-hover, .badge-bar.is-group-open { box-shadow: 0 0 18px -3px var(--primary); }
.badge-bar-head:hover, .badge-bar-head:focus-visible { box-shadow: 0 0 18px -3px var(--hi); }
.badge-bar:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* S-14380 (owner, verbatim: "they are neither aligned in style nor vertically") --
   the unconditional --bg2 fill this used to carry (every theme) was the visible
   mismatch against the Iris picker's own --shadow-tinted resting/open fill: a
   lighter, more opaque patch read as a seam/stripe at the bar's own left edge next
   to the darker Iris and Wallet pills above it. Resting state now falls through to
   the shared S-14210 rule above (`color-mix(in srgb, var(--shadow) 40%,
   transparent)`, same as `.edge-picker`/canonical.css `.theme-picker`); open state
   below now mirrors `.theme-picker.is-open`'s own 82% shadow tint too. The --bg2
   fill this rule used to carry is NOT dropped -- it still solves real contrast for
   the coloured dots in the two light-ground themes, so it moves onto
   `.badge-bar-row` (a small per-row chip behind the dots) further down, scoped to
   `[data-theme="light"]`/`[data-theme="wood"]` only, where the dots actually need
   it -- see that rule. All fills below target `::before` now, alongside the resting
   fill above, for the same z-fix reason. */
/* Owner: "what about the badge column glass background that is somewhat hidden when badges are slid out?" -- the column
   keeps its own glass while slide-outs are out (no darker "expanded" fill), as the slide-outs keep theirs (S-16906). */
[data-theme="light"] .badge-bar::before, [data-theme="wood"] .badge-bar::before {
  background: color-mix(in srgb, var(--bg2) 88%, transparent);
}
/* S-14440 (owner ruling, verbatim: "flag slider must sit centered in ... the table
   header"): `.badge-bar-head` reads centred on the real table header band.
   IR-6730 (owner, verbatim, high: "all three sliders must sit on the furthest
   right edge of the page. zero gap."): this rule now styles `#badge-flag`, a
   SEPARATE standalone element (index.html), not a child of `#badge-bar` any more
   -- `position: fixed; right: 0` (the true, scrollbar-proof viewport edge, same
   mechanism the Iris dock/`#edge-tabs`/`#scroll-top` already use) replaces the old
   `position: sticky` (which depended on being nested inside the bar, now
   document-absolute and gutter-adjusted -- nesting would have inherited THAT
   element's own right edge instead of the true one). `top`/`block-size` are now
   JS-computed each sync (app.js::centerThemePicker(), PLUS a passive scroll
   listener -- unlike the always-sticky-at-0 app header the Iris dock centres on,
   the real thead's own viewport position keeps changing with scroll until it
   settles/"sticks", so a layout-events-only recompute is not enough here) from
   `#table-head-sticky`'s own live rect -- `top` = that rect's own top, `block-size`
   = that rect's own height, so the flag is vertically centred ON the header band
   by construction (flex centring inside a box the same height as the band it sits
   over) exactly as before, just measured directly off the real element's rect
   instead of via the `--ceres-header-height`/`--ceres-thead-height` vars (which
   describe the SAME geometry but are one measurement pass further removed).
   Look: the owner asked for "the same look as the sliders" here, not
   the table-header gradient S-14210/S-14330 gave the old head -- background/
   box-shadow are the shared Iris strip recipe (`.edge-picker`'s own resting-state
   values, `color-mix(in srgb, var(--shadow) 40%, transparent)` + the ambient
   glow), same radius (`5px 0 0 5px`) as the Iris pill and Wallet toggle.
   `font-size` reduced (1rem -> 0.75rem) to fit the narrower box -- the glyph is a
   single Unicode character, no separate icon asset to resize.
   Z-INDEX 10 (unchanged from the S-14440 z-fix's own value): still needs to
   out-rank the real thead (9) to stay clickable -- now trivially true, since
   `#badge-flag` is a genuinely independent element with no ancestor context to
   escape at all (the whole reason the z-fix saga existed is moot for a standalone
   fixed element).
   S-14610 (owner, verbatim: "the inner and outer border of the slider background
   for iris, wallet and flag must be aligned... currenly it is missing the outer
   background"): widens back from `--badge-bar-w` (IR-6730's own "thinner bar"
   width) to `--edge-w` -- the owner's later, more specific ruling is that the
   FLAG's own OUTER pill matches the Wallet/Iris pill width exactly (`.badge-bar`
   itself, the vertical strip's own body, keeps `--badge-bar-w` -- this widening
   is scoped to the flag/head only). `min-inline-size: max(var(--edge-w), 36px)`,
   not a plain `inline-size: var(--edge-w)` -- MEASURED (headless): `.edge-picker`
   (Wallet) never actually renders at bare `--edge-w` either, since its own real
   content (4px left padding + its 32px `.edge-toggle`, S-14550) needs 36px and
   `.edge-picker`'s own `min-inline-size: var(--edge-w)` is a FLOOR, not a fixed
   value -- whenever `--edge-w` (tracking Iris's own live toggle measurement)
   comes out narrower than 36px, the Wallet pill still renders at its own natural
   36px minimum. A plain `inline-size: var(--edge-w)` here would have pinned the
   flag to the bare `--edge-w` value regardless, landing it narrower than the
   Wallet pill whenever that gap exists (measured: flag 32px vs Wallet pill 36px
   in this environment's own `--edge-w` measurement) -- `max(var(--edge-w), 36px)`
   makes the flag track the EXACT SAME effective floor the Wallet pill's own
   content already enforces, so the two are identical in every case, not just
   when `--edge-w` happens to be wide enough on its own.
   `padding: 0 0 0 4px` and `justify-content: flex-end` mirror `.edge-picker`'s
   own recipe exactly (4px left inset, content flush against the right/rounded
   edge) so the flag's own INNER chip (`.badge-bar-head::before`, below) lands
   at the exact same inset the Wallet toggle (`.edge-toggle`, `margin-inline-
   start: auto` inside its own picker) does -- see that element's own comment
   for the "align the inner chip edges" half of this ticket. */
/* S-14680 (owner, verbatim: "But all icons and borders in the slider are
   unaligned"): `inline-size`/padding/radius all read from Iris's own LIVE
   measurement (`--edge-w`/`--edge-pad-left`/`--edge-pad-right`/
   `--edge-radius-outer`, app.js::centerThemePicker()) --
   `--edge-w` IS the pill's own real closed width directly now (no longer a
   `toggleWidth + 4` guess), so a plain `inline-size` here matches the Wallet
   pill's own `min-inline-size: var(--edge-w)` exactly (that safety-net
   `max(var(--edge-w), 36px)` this replaces existed only because the OLD
   derivation could under-measure). Fallback defaults match the PREVIOUS
   hardcoded values exactly. */
.badge-bar-head {
  position: fixed; right: 0; top: var(--badge-flag-top, var(--ceres-header-height, 3.4rem)); z-index: 10;
  /* Explicit, not relied-on-by-accident: a `<button>` element (index.html)
     already gets `box-sizing: border-box` from the browser's own UA default
     stylesheet (form controls, unlike ordinary elements, default to
     border-box) -- `.edge-picker` (a plain `<div>`) does NOT get that for
     free, which is exactly the S-14680 measured bug its own comment
     documents. Declared here too so this rule's own correctness never
     depends on an implicit default that could change if the element type
     ever does. */
  box-sizing: border-box;
  inline-size: var(--edge-w, 36px);
  block-size: var(--badge-flag-h, calc(var(--ceres-thead-height, 2.75rem) - 2 * var(--badge-flag-inset, 4px)));
  /* S-14720 (owner, verbatim: "the flag and the badge icons are too small and
     unalignd and the flaf must use global colors"): `justify-content: center`
     instead of `flex-end` -- the content box already equals the chip box
     (inline-size == --edge-w, left padding == --edge-pad-left), so centring
     the glyph in the content box lands it exactly on `::before`'s chip
     (below) instead of clipped at the right edge. Glyph size now tracks
     `--edge-chip-font` (Iris's own live-measured glyph size, app.js), not a
     fixed 0.75rem. */
  display: flex; align-items: center; justify-content: center;
  padding: 0 var(--edge-pad-right, 0px) 0 var(--edge-pad-left, 4px);
  font-size: var(--edge-chip-font, 1.25rem); line-height: 1; color: var(--body);
  border: 0;
  /* S-14730 (owner amendment, verbatim: "the flag must use the IDENTICAL
     recipe as the wallet pill, top to bottom"): back to the SAME translucent
     `color-mix` the Wallet's own `.edge-picker` uses -- S-14720's opaque
     `var(--bg)`-mix experiment is superseded; the owner's actual ask was the
     Wallet/Iris recipe exactly, not a new opaque variant.
     S-14920 (owner, verbatim: "the flag background must be the same dark
     background iris and the wallet use. Right now, half of the table row
     shimmers through. Give it two chips... Exactly like the iris and wallet
     behave"): the Wallet/Iris `.edge-picker` sits over the plain page
     background, so its 40%-shadow-over-transparent glass reads as a solid
     dark chip; `#badge-flag` sits over the table header band instead, so the
     SAME translucent recipe lets that band show through -- same recipe,
     different ground, different look, which is exactly the bug. Fix keeps
     the identical 40% `--shadow` mix but swaps the transparent second stop
     for `--bg3` (the darkest/most-saturated of the three surface tokens),
     which MEASURED (headless, 1536x864 DPR 1.25, four themes) renders within
     2 RGB units of the Wallet pill's own live outer-pill pixel in every
     theme (dark/light/wood/alchemical) -- opaque, so the header band can no
     longer shimmer through, while still reading as "the same dark chip" the
     Wallet/Iris pill shows. The brighter inner chip (`::before`, 22%
     `--primary`) is untouched. */
  background: color-mix(in srgb, var(--shadow) 40%, var(--bg3));
  border-radius: var(--edge-radius-outer, 5px 0 0 5px);
  /* S-14730 review follow-up: `.edge-picker`'s own glass filter, copied
     verbatim -- `#badge-flag` is `position: fixed` on the document body (its
     own independent stacking context already, not a descendant of
     `.badge-bar`), so none of the "backdrop-filter forces a stacking context
     that traps a sticky descendant" caveat (`.badge-bar`'s own comment,
     above) applies here. */
  backdrop-filter: saturate(120%) blur(2px);
  -webkit-backdrop-filter: saturate(120%) blur(2px);
  cursor: pointer;
  /* S-14660: hidden by default, pre-JS -- see `.badge-bar`'s own comment above
     for the full "reveals wallet, flag and bar together" gate. */
  visibility: hidden;
  opacity: 0;
}
.badge-flag-revealed {
  visibility: visible;
  opacity: 1;
  animation: edgeTabsFadeInRight .6s 1 cubic-bezier(.77, 0, .175, 1);
}
.badge-bar-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* S-15220: opening the group changes the inner chip only; the outer glow remains
   a hover/focus-visible affordance. */
/* S-14610 (owner, verbatim: "give the flag the inner background after it was
   clicked and badges are slided out. remove it when clicked again and badges
   slide-in"), SUPERSEDED by S-14730 (owner amendment, verbatim: "the flag
   must use the IDENTICAL recipe as the wallet pill, top to bottom" -- "the
   flag chip ::before is ALWAYS painted like the wallet's .edge-toggle...not
   only when aria-expanded=true"): the INNER chip -- `.edge-toggle`'s own
   recipe (primary-22% fill, its radius/shadow), byte-identical, sized/
   positioned to land in the exact same spot the glyph text itself sits (both
   centred via this element's own `justify-content: center`, S-14720), so the
   glyph reads as sitting ON the chip. Always painted now, not gated on
   `aria-expanded` -- Painted BEHIND the glyph text by default DOM-order
   stacking (a `::before` pseudo is generated before the element's own
   content, so it paints first/underneath at the same z-index -- no explicit
   z-index needed here). */
/* S-14680: size/radius match the Wallet's own inner chip exactly
   (`--edge-chip-w`/`--edge-chip-h`/`--edge-radius-inner`, same vars
   `.edge-toggle` reads) -- `right: var(--edge-pad-right, 0px)` (was a bare
   `right: 0`) so the chip's own inset from this element's right edge matches
   Iris's live-measured inset too, not just an assumed zero.
   IR-6810: WIDTH tracks `--edge-chip-w` with no ceiling (matching
   `.edge-toggle`'s own width exactly, so the chip's own side borders line up
   with Iris's regardless of size); HEIGHT alone is capped at this element's
   OWN pill height minus the framing margin (`calc(var(--badge-flag-h, var(
   --ceres-thead-height, 2.75rem)) - 2 * var(--edge-pad-left, 4px))` -- the
   SAME `var(--badge-flag-h, ...)` expression `.badge-bar-head`'s own
   `block-size` above already uses for its own pill height, so this chip's
   ceiling always tracks whatever the flag's real pill height currently is
   (dynamic, unlike the Wallet's fixed 40px -- see `.edge-toggle`'s own
   comment for the full "why a ceiling at all" reasoning). */
/* S-14720: chip box == glyph box -- now that `.badge-bar-head` centres its
   glyph inside its own content box (inset by `--edge-pad-left` on the left,
   `--edge-pad-right` on the right, above), the chip needs BOTH insets too
   (was `right` alone, floating free of the left edge) with no explicit
   `inline-size` -- it spans exactly the content box, left pad to right pad,
   the same box the glyph is centred in, so the two always coincide. */
.badge-bar-head::before {
  content: '';
  position: absolute;
  left: var(--edge-pad-left, 4px);
  right: var(--edge-pad-right, 0px);
  block-size: min(var(--edge-chip-h, 32px), calc(var(--badge-flag-h, var(--ceres-thead-height, 2.75rem)) - 2 * var(--edge-pad-left, 4px)));
  top: 50%;
  transform: translateY(-50%);
  border-radius: var(--edge-radius-inner, 5px 0 0 5px);
  /* S-16908 (owner: "the flag ... must get the same background clip with the same style and behavior as the wallet and
     iris"): `.edge-toggle`'s own chip fill and shadow, always on, like the wallet and Iris toggles. */
  background: color-mix(in srgb, var(--primary) 22%, transparent);
  box-shadow: var(--edge-chip-shadow, 0 8px 22px color-mix(in srgb, var(--shadow) 35%, transparent), inset 0 1px 0 color-mix(in srgb, var(--hi) 6%, transparent));
  pointer-events: none;
}
.badge-bar-head .hdr-ic { position: relative; z-index: 1; display: block; inline-size: 1.25rem; block-size: 1.25rem; overflow: visible; }
/* S-16903: the flag's chip fills its taller box (no 32px cap) and its icon grows with it (desktop). */
html:not(.ceres-lw-le-1279) .badge-bar-head::before { block-size: calc(var(--badge-flag-h, var(--ceres-thead-height, 2.75rem)) - 2 * var(--edge-pad-left, 4px)); }
html:not(.ceres-lw-le-1279) .badge-bar-head .hdr-ic { inline-size: 26px; block-size: 26px; }
/* S-16904 (owner): the flag's status tray (its flag option shares the flag button's colours, above) -- slides out to the flag's left in the table-header row, the wallet tray's glass
   and the column glow; only transform and opacity animate (S-16878 M-4). A picked status icon replaces the flag. */
.badge-filter-tray {
  position: fixed; right: var(--edge-w, 32px); top: var(--badge-flag-top, 0px); z-index: 10; /* the flag's level, placed before it in the page so the flag stays on top */
  display: flex; align-items: center; gap: 2px; box-sizing: border-box;
  block-size: var(--badge-flag-h, 2.5rem); padding-inline: 4px;
  border-radius: 5px 0 0 5px;
  /* One pill with the flag, like Iris and the wallet: exactly the flag's own ground, no seam (owner: "somewhat
     disconnected from the flag badge"). */
  background: color-mix(in srgb, var(--shadow) 40%, var(--bg3));
  box-shadow: -7px 0 14px -6px var(--primary);
  transform: translateX(calc(100% + var(--edge-w, 32px))); opacity: 0; visibility: hidden; pointer-events: none;
  transition: transform .35s cubic-bezier(.4, .8, .4, 1), opacity .2s ease, visibility 0s linear .35s;
}
.badge-filter-tray[data-open="true"] { transform: none; opacity: 1; visibility: visible; pointer-events: auto; transition: transform .35s cubic-bezier(.4, .8, .4, 1), opacity .2s ease, visibility 0s; }
#badge-flag.is-tray-open { border-start-start-radius: 0; border-end-start-radius: 0; }
.badge-filter-opt { display: inline-flex; align-items: center; justify-content: center; inline-size: 32px; block-size: calc(var(--badge-flag-h, 2.5rem) - 8px); padding: 0; border: 0; border-radius: calc(var(--radius) - 3px); background: none; color: var(--body); cursor: pointer; }
.badge-filter-opt:hover, .badge-filter-opt:focus-visible, .badge-filter-opt.is-active { background: color-mix(in srgb, var(--primary) 22%, transparent); }
.badge-filter-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.badge-filter-opt :is(.badge-status-glyph, .hdr-ic) { inline-size: 22px; block-size: 22px; min-inline-size: 22px; min-block-size: 22px; }
#badge-flag .badge-status-glyph { position: relative; z-index: 1; inline-size: 26px; block-size: 26px; min-inline-size: 26px; min-block-size: 26px; }
/* S-16909 (owner: "make the flag clock not pulse. Only [clocks] inside the badges column pulse"): less noise. */
:is(#badge-flag, .badge-filter-opt) .badge-status-glyph.is-pending { animation: none; }
@media (prefers-reduced-motion: reduce) { .badge-filter-tray, .badge-filter-tray[data-open="true"] { transition: none; } }
html.ceres-lw-le-1279 .badge-filter-tray { display: none; }
/* S-16903 (owner: "There must a clear color jump visible"): the column's shade starts below the table-header row, so the
   flag's 1px gaps show the header's own colour instead of the shade (measured: dark 18,15,39 before, header 122,109,184). */
html:not(.ceres-lw-le-1279) .badge-bar::before { inset-block-start: var(--badge-head-shade, 2.75rem); }
:is(.badge-bar-head, .badge-filter-opt) .hdr-ic .f { fill: color-mix(in srgb, var(--hi) 84%, var(--primary)); }
:is(.badge-bar-head, .badge-filter-opt) .hdr-ic .d { fill: none; stroke: color-mix(in srgb, var(--primary) 62%, var(--shadow)); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
:is(.badge-bar-head, .badge-filter-opt) .hdr-ic .a.h { fill: none; stroke: var(--gold); stroke-width: 2.7; stroke-linecap: round; }
#badge-flag[data-state="unused"] .hdr-ic .a.h { stroke: var(--meta); opacity: .55; }
#badge-flag[data-state="used"] .hdr-ic .a.h { stroke: var(--gold); opacity: 1; }
:root:not([data-theme]) :is(.badge-bar-head, .badge-filter-opt) .hdr-ic .a.h, [data-theme="alchemical"] :is(.badge-bar-head, .badge-filter-opt) .hdr-ic .a.h { stroke: var(--hi); }
/* Z-INDEX 7 (z-fix ROUND 2, 2026-09-20 -- see `.badge-bar`'s own z-ladder comment):
   a REAL value now, since `.badge-bar` no longer forces a stacking context -- below
   `thead.table-head-sticky` (9) on purpose, so a row scrolled up into the header
   band disappears fully behind the OPAQUE real thead (restored -- round 1's
   translucent-glass compromise is gone); below the expanded-plot `td` (8, restored
   to its original value) too, so an expanded plot still covers the bar's own rows
   exactly as before S-14440. Above `.badge-bar::before` (6, the bar's own glass
   fill) so the rows' own content still reads over the bar's background. */
.badge-bar-row {
  position: absolute; left: 0; right: 0; box-sizing: border-box; display: flex;
  align-items: center; justify-content: center; overflow: hidden;
  /* Owner 2026-09-28: "background clip must be removed, dark semi-transparent for all badges" -- no per-row
     chip; every badge sits on the column's own dark semi-transparent glass (.badge-bar::before). */
  cursor: pointer;
  /* IR-6730: 0.2rem -> 0.1rem -- the bar itself shrank from --edge-w (48px) to
     --badge-bar-w (24px); the old padding alone would leave almost no room for
     the (also shrunk) dots below.
     S-14720: dots grew back to 0.5rem (below) -- padding trimmed further to
     0.05rem and the row now wraps (`flex-wrap`), so up to three still fit
     the 24px `--badge-bar-w` bar on two lines instead of squeezing/
     overflowing on one.
     S-14770: `--badge-bar-w` widened back to `--edge-w` (~32px) -- three 0.5rem
     dots (24px) plus their 0.06rem end-margins fit comfortably on one line at
     that width, so padding-inline moves back to a small symmetric value
     (0.15rem) instead of the IR-6730 squeeze value; `flex-wrap` stays as a
     safety net for any theme where the measured `--edge-w` comes out narrower.
     S-14780 (owner amendment, verbatim: "All must be same width, the single row
     badges as well") checked separately: this rule's own `left: 0; right: 0`
     (above) spans the full bar width, while S-15220's 1px top/bottom inset
     keeps the rounded cell stack visually separated without moving its content.
     The dots/tick remain centred via `justify-content: center`. */
  /* S-16000 (owner, verbatim: "The dots in the slider badges are not centered but left aligned"): the
     visible chip (`::before`, below) is inset --edge-pad-left on the left and --edge-pad-right on the right,
     so symmetric padding centred the dots on the ROW box, 2px left of the chip's centre (measured 5.3px
     left / 9.3px right). Adding the same insets to the padding centres them on the chip itself. */
  padding-inline: calc(0.15rem + var(--edge-pad-left, 4px)) calc(0.15rem + var(--edge-pad-right, 0px)); text-align: center;
  flex-wrap: wrap; align-content: center; row-gap: 0.1rem;
  z-index: 7;
}
/* S-15440: the row has the same outer/inner two-layer paint as #badge-flag. Its
   inline height is the mounted table row's live height, so the inner chip caps
   against this box rather than the taller header flag box. */
.badge-bar-row::before {
  content: '';
  position: absolute;
  z-index: 0;
  left: var(--edge-pad-left, 4px);
  right: var(--edge-pad-right, 0px);
  block-size: min(var(--edge-chip-h, 32px), calc(100% - 2 * var(--edge-pad-left, 4px)));
  top: 50%;
  transform: translateY(-50%);
  border-radius: var(--edge-radius-inner, 5px 0 0 5px);
  background: transparent;
  pointer-events: none;
}
.badge-bar-row.is-open::before { background: color-mix(in srgb, var(--primary) 22%, transparent); }
.badge-bar-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.badge-bar-row > .badge-bar-content {
  position: relative;
  z-index: 1;
}
/* S-16874 CANDIDATE, opt-in only: active solely under `body.badges-candidate`, which app.js sets only for
   `?badges=candidate`. Without that query not one of these rules matches and the column is the frozen,
   owner-approved 0.9.893/Atlas look (library/ceres-badge-overlay.md, "Frozen reference").
   S-16851 (owner: "the border ... does not flow continously into the badge bar"): the resting bar glass
   (40% shadow) darkened the table's own divider and header lines under the column. Here the resting glass
   is transparent, so the table lines that already run under the bar (S-14370) show unchanged between the
   chips; the open-state glass stays (S-16851a). The approved dark rounded chip keeps its exact box and
   colour but moves off `.badge-bar-row` itself (S-16851: "must not use any background at all") onto a
   `::after` layer at z -1 inside the row's own stacking context, so the open-state inner chip (`::before`)
   and the glyph paint over it exactly as before. */
body.badges-candidate .badge-bar:not([aria-expanded="true"])::before {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
body.badges-candidate .badge-bar-row { background: none; }
body.badges-candidate .badge-bar-row::after {
  content: '';
  position: absolute; inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--shadow) 40%, var(--bg3));
  pointer-events: none;
}
/* S-16460 (owner, verbatim: "the dots move up on flag slid out. make them non moving up. but moved up is the
   correct position"): the wrapper was centred as inline-flex only on OPEN rows, so idle dots sat 1.5px lower and
   jumped up on slide-out. The centred (open) layout now applies in both states, so nothing moves. */
.badge-bar-content { display: inline-flex; align-items: center; justify-content: center; }
/* IR-6730 (owner, verbatim, high: "the entire slide bar must be thinner"): 0.5rem
   -> 0.4rem, the spec's own literal value; spacing now belongs to the flagged
   group's gap so the final dot has no phantom trailing width. */
/* S-14720 (owner, verbatim: "the flag and the badge icons are too small"):
   0.4rem -> 0.5rem -- `.badge-bar-row` (above) now wraps, so a wider dot no
   longer forces a squeeze. */
.badge-strip { display: inline-flex; align-items: center; justify-content: center; }
.badge-status-glyph { inline-size: 1rem; block-size: 1rem; overflow: visible; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.badge-strip.is-pending { color: var(--gold); }
.badge-strip.is-critical, .badge-status-glyph.is-critical { color: var(--danger); }
.badge-status-glyph.is-critical .badge-status-mark { stroke: currentColor; stroke-width: 3; }
.badge-status-glyph.is-critical circle { fill: currentColor; stroke: none; }
.badge-strip.is-warning, .badge-status-glyph.is-warning { color: var(--warn); }
.badge-status-glyph.is-warning > path:first-child { fill: color-mix(in srgb, var(--warn) 16%, transparent); stroke: currentColor; }
.badge-status-glyph.is-warning .badge-status-mark { stroke: currentColor; stroke-width: 2.4; }
.badge-strip.is-clean { color: var(--ok); }
.badge-status-glyph.is-clean { color: var(--ok); }
.badge-strip.is-loading { color: var(--meta); }
.badge-status-glyph.is-loading { color: var(--meta); }
/* S-16830 / S-16770e: the status glyph reads at a glance -- a fixed 14px box that the lane cannot squeeze, heavier strokes, a clear "!" dot. */
.badge-status-glyph { inline-size: 14px; block-size: 14px; min-inline-size: 14px; min-block-size: 14px; flex: 0 0 auto; }
.badge-status-glyph.is-critical .badge-status-mark { stroke-width: 5; stroke-linecap: butt; }
.badge-status-glyph.is-critical circle { fill: currentColor; stroke: none; }
.badge-status-glyph.is-warning .badge-status-mark { stroke-width: 3.4; stroke-linecap: butt; }
.badge-status-glyph.is-warning circle { fill: currentColor; stroke: none; }
.badge-status-glyph.is-clean, .badge-status-glyph.is-pending, .badge-status-glyph.is-loading { stroke-width: 3.2; }
/* Owner 2026-09-28: "clock must be gold and much stronger pulsing" -- gold, dim-to-full with a gold glow; no size
   change (he rejected "smaller" on 2026-09-27). Stays on the shared pulse timeline (IR-8435). */
.badge-status-glyph.is-pending { animation: badge-clock-pulse 1.3s ease-in-out infinite; color: var(--gold); transform-origin: center; }
@keyframes badge-clock-pulse {
  0%, 100% { opacity: .3; filter: drop-shadow(0 0 0 transparent); }
  50% { opacity: 1; filter: drop-shadow(0 0 5px var(--gold)) drop-shadow(0 0 2px var(--gold)); }
}
@media (prefers-reduced-motion: reduce) {
  .badge-status-glyph.is-pending { animation: none; opacity: 1; filter: none; }
}
/* IR-8528 / IR-8531 (owner): a plot row with a pending change glows gold INSIDE, along its edges, pulsing with the clock. It
   is a glow, not a tint: the row's own background is never touched and the centre stays clear, so no row detail is covered
   (inset shadows paint behind the cell content). Every cell glows along the top and bottom edge, the first cell also along
   the left edge and the last along the right, so the row reads as one lit frame. Reduced motion: a steady, slightly softer glow. */
table.plot-table tbody tr.plot-row.row-pending > td {
  --glow-left: 0%; --glow-right: 0%; --glow-peak: 100%;
  animation: row-pending-glow 1.3s ease-in-out infinite;
}
table.plot-table tbody tr.plot-row.row-pending > td:first-child { --glow-left: 100%; }
table.plot-table tbody tr.plot-row.row-pending > td:last-child { --glow-right: 100%; }
@keyframes row-pending-glow {
  0%, 100% {
    box-shadow: inset 0 17px 10px -12px color-mix(in srgb, var(--gold) 0%, transparent),
                inset 0 -17px 10px -12px color-mix(in srgb, var(--gold) 0%, transparent),
                inset 17px 0 10px -12px color-mix(in srgb, var(--gold) 0%, transparent),
                inset -17px 0 10px -12px color-mix(in srgb, var(--gold) 0%, transparent);
  }
  50% {
    box-shadow: inset 0 17px 10px -12px var(--gold),
                inset 0 -17px 10px -12px var(--gold),
                inset 17px 0 10px -12px color-mix(in srgb, var(--gold) var(--glow-left), transparent),
                inset -17px 0 10px -12px color-mix(in srgb, var(--gold) var(--glow-right), transparent);
  }
}
@media (prefers-reduced-motion: reduce) {
  table.plot-table tbody tr.plot-row.row-pending > td {
    animation: none;
    box-shadow: inset 0 17px 10px -12px color-mix(in srgb, var(--gold) 70%, transparent),
                inset 0 -17px 10px -12px color-mix(in srgb, var(--gold) 70%, transparent),
                inset 17px 0 10px -12px color-mix(in srgb, var(--gold) calc(var(--glow-left) * .7), transparent),
                inset -17px 0 10px -12px color-mix(in srgb, var(--gold) calc(var(--glow-right) * .7), transparent);
  }
}
.badge-strip.is-flagged {
  display: inline-flex; align-items: center; justify-content: center;
}
.badge-strip.is-unknown { color: var(--meta); }
.badge-strip.is-none { color: var(--ok); }

/* #badge-overlay -- a child of #plot-table-scroll (position: relative above) so it
   scrolls with the table -- IR-6730 did NOT move this element (only `#badge-bar`
   itself and its head moved out to <body>; the overlay's own row-vs-`<tr>`
   alignment still depends on staying inside the scroller). S-14270 (owner,
   verbatim): "give each row an individual slideout with, based on its badges.
   They each slide out individually on click or behave as a group when triggerd
   via the flag." The container itself paints NOTHING any more -- no background/
   blur/border/radius/transform/transition of its own -- it is purely a
   positioning root each `.badge-overlay-row` is absolutely positioned inside
   (right-anchored beside where `#badge-bar` NOW sits, covering only the
   Workers/DEC stake columns, never production or asset information). It stays
   permanently un-hidden (badge-overlay.js sets `hidden = false` once, at init,
   and never toggles it again) -- what used to be the panel's own open/close state
   is now each ROW's own. The old IR-6600 shared-panel-width measurement pass this
   comment used to describe is gone with it -- see the v4 section of
   ceres-badge-overlay.md for the full history.
   IR-6730: `right: var(--edge-w)` -> `calc(var(--badge-bar-w) + var(--page-gutter,
   0px))` -- the bar shrank (`--badge-bar-w`, not `--edge-w`) AND moved out to
   <body>, landing flush at the true page edge instead of at this element's own
   containing block's (the scroller's) right edge; `--page-gutter` (the same value
   `#badge-bar`'s own positioning and `--table-end-reserve` use, set by
   app.js::centerThemePicker()) reaches past that same scrollbar-width gap so this
   overlay's own rows still land exactly beside the bar's new left edge, not the
   old inset one. S-14770: `--badge-bar-w` widened back to `--edge-w`; this offset
   reads the same token so it stays exactly beside the bar's (now wider) edge.
   S-14780 measured fix (owner: "All must be same width, the single row badges
   as well" -- review found a real 16px gap, row right edge 1473 vs bar left
   1489 at 1536 width): `+ var(--page-gutter, 0px)` -> `- var(--page-gutter,
   0px)`. This container's OWN containing block (`#plot-table-scroll`, still
   `main`-padded) is already inset from the true page edge by `--page-gutter`
   itself -- adding the gutter on top of `--badge-bar-w` counted it TWICE (once
   from the containing block's own inset, once again in this formula), pushing
   the rows' right edge `--page-gutter` further left than the bar's now-flush
   left edge. Subtracting it instead cancels the containing block's own inset
   exactly, landing the rows flush against the bar with zero gap -- the same
   correction `.badge-bar`/`.scroll-top`/`--table-end-reserve` never needed
   because they are NOT inside that inset containing block (`.badge-bar` moved
   to `<body>`, the other two are `position: fixed`), only this element is. */
/* Closed rows remain mounted and translated right; clip only their container's
   horizontal overflow. The extra 8px leaves room for an open row's left glow. */
.badge-overlay {
  position: absolute; top: 0; right: calc(var(--badge-bar-w) - var(--page-gutter, 0px)); block-size: 100%;
  inline-size: calc(45vw + 8px); overflow-x: clip;
  pointer-events: none; z-index: 5;
}
/* Each row follows its mounted plot, keeps a 3px vertical inset and caps its
   own one-line content at 45vw. */
.badge-overlay-row {
  position: absolute; top: 0; right: 0; left: auto; box-sizing: border-box;
  inline-size: max-content; max-inline-size: 45vw; overflow: hidden; text-overflow: ellipsis;
  display: flex; align-items: center; gap: 0.3rem; padding: 0 8px;
  white-space: nowrap;
  background: color-mix(in srgb, var(--shadow) 82%, transparent);
  backdrop-filter: saturate(130%) blur(4px);
  -webkit-backdrop-filter: saturate(130%) blur(4px);
  border-radius: 5px 0 0 5px;
  transform: translateX(100%);
  pointer-events: none;
  transition: transform .5s cubic-bezier(.4, .8, .4, 1), visibility 0s linear .5s, background-color .5s cubic-bezier(.4, .8, .4, 1);
  visibility: hidden;
}
[data-theme="light"] .badge-overlay-row, [data-theme="wood"] .badge-overlay-row {
  background: color-mix(in srgb, var(--bg2) 92%, transparent);
}
.badge-overlay-row.is-open {
  transform: translateX(0);
  transition: transform .5s cubic-bezier(.4, .8, .4, 1), visibility 0s, background-color .5s cubic-bezier(.4, .8, .4, 1);
  visibility: visible;
  /* S-16906 (owner: "keep the nice dark glass background"): an open slide-out keeps the row's own glass -- no tint on open. */
}
.badge-overlay-row.is-open .badge-row > *, .badge-overlay-row.is-open [data-tip] { pointer-events: auto; }
/* S-16905 (owner: "the beautiful glow on the edge of the badges column must be added to the slide-outs left border"):
   the column's Iris glow (18px, --primary), on the left edge only; static, it rides the slide's transform (S-16878 M-4). */
.badge-overlay-row { box-shadow: -7px 0 14px -6px var(--primary); }
@media (prefers-reduced-motion: reduce) {
  .badge-overlay-row, .badge-overlay-row.is-open { transition: none; }
}
.badge-overlay-row.is-loading { color: var(--meta); font-style: italic; }
.badge-overlay-none { color: var(--ok); }
/* Slimmer badges INSIDE each row-slideout only -- the table strip cell and the mobile
   card's .plot-card-badges keep their existing badge sizing untouched. */
.badge-overlay-row .badge-row > * {
  padding-block: .05rem; padding-inline: .45rem; font-size: .78rem; line-height: 1.3;
}
/* IR-8529: no special background for the pending badges inside an open slide-out either: they are standard outlined badges. */

.provenance-tag {
  font-size: 0.7rem;
  color: var(--meta);
  border: 1px dotted var(--border);
  border-radius: 999px;
  padding: 0.05rem 0.4rem;
}

.dec-efficiency-flag, .totals-note { cursor: help; color: var(--meta); }

/* Per-row render-error fallback (Florian/team-lead, 2026-08-21 urgent report:
   "it shows no workers at all now"). ceres/static/app.js:safeMap and the
   per-plot try/catch in render() degrade one bad record to this single
   visible line instead of throwing and blanking the whole table -- this
   should never normally appear; if it does, it names exactly which record
   and error to investigate. */
.render-error-row td { background: var(--bg3); color: var(--heading); font-style: italic; white-space: normal; }

/* Inline row-expand failure, replacing an alert() (2026-08-21, `work/
   ceres-plot-expand-bug.md`) -- a transient network blip (the auto-reload
   watcher restarting mid-request was the live cause) shouldn't need a modal
   dismissed; the Retry link lets the same row recover without collapsing it. */
.expand-error { color: var(--danger); font-style: italic; }
.expand-retry-link { color: var(--danger); text-decoration: underline; margin-left: 0.5rem; font-style: normal; }

/* Content-sized, like the main plot table (Florian, 2026-08-21 board-polish batch
   15, item 4: "shrink the dynamic width of the worker table the same way we did
   before... and keep a decent padding" -- see table.plot-table's own
   `width: auto` comment above for the root cause `width: 100%` fixes). No scroll
   wrapper here: the worker sub-table is narrower than the main table and already
   fits inside `table.plot-table tbody tr.expand-row td`'s own width, and
   this table has no sticky header of its own to fall into the `.table-scroll`
   sticky-containing-block trap that batch 10 had to fix for the main table -- so
   none is added. Padding matched to the main table's own settled values
   (0.4rem/0.7rem) instead of the previous tighter 0.2rem/0.5rem. */
/* SPL/Rarity/Edition are icon-only columns in this sub-table, same as the main
   table's own Rarity/Type/Totem/Title columns -- header text and icon both
   centred (items 1/2; Edition added in the worker-column-polish pass,
   2026-08-21, matching the same treatment already applied to the main table). */
/* Splinter icon enlarged, worker sub-table only (board-polish 2026-08-21,
   Florian: "increase spl icon size") -- modifier class layered on top of the
   shared `.splinter-icon` base (still applied for the tooltip/vertical-align
   rule below), so every OTHER splinter icon on the board
   (splinterIconHtml() called without the `large` arg -- see app.js) is
   untouched at its original 0.95rem. See ceres/static/app.js's
   splinterIconHtml() comment: the icon asset itself was baked (FR-1672) with
   its contrast halo tuned for the smaller 0.95rem render size, so at this
   larger size the halo reads a little softer/thinner relative to the glyph --
   visually verified acceptable at both 1920x1080 and 1280x800 (see
   work/ceres-worker-cols.md), not re-baked separately for this one context. */
 /* Gather-all combine dialog. Shared columns and gutters keep the hero,
    outcome, copy rows, controls, and footer on one visual grid. */
 .combine-dialog-overlay { z-index: 90; align-items: center; overflow-x: hidden; }
 .slot-hazard-panel.combine-dialog-panel { width: min(60rem, calc(var(--ceres-vw) * 96)); min-width: 0; max-width: calc(var(--ceres-vw) * 96); max-height: calc(var(--ceres-vh) * 100 - 2rem); display: flex; flex-direction: column; overflow: hidden; }
 .slot-hazard-body.combine-dialog-body { padding: 1.5rem 2rem 0; font-size: 0.83rem; overflow-x: hidden; overflow-y: auto; min-height: 0; }
 .combine-editor-layout { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
 .combine-identity { width: 100%; align-items: center; gap: 1rem; }
 .combine-identity .eos-portrait-fill { width: 4rem; height: 5rem; object-fit: contain; flex: none; }
 .combine-identity > div { min-width: 0; }
 .combine-identity strong { display: inline; color: var(--heading); font-size: 1rem; overflow-wrap: anywhere; }
 .combine-identity small { margin-top: 0.25rem; font-size: 0.75rem; }
 .combine-editor-result, .combine-editor-selection { display: flex; flex-direction: column; gap: 0.75rem; min-width: 0; }
 .combine-consequence { margin: 0; color: var(--danger); font-size: 0.75rem; }
 .combine-copy-list { list-style: none; max-height: 13.5rem; overflow-y: auto; margin: 0; padding: 0; display: grid; gap: 0.35rem; }
 .slot-hazard-body.combine-dialog-body ul.combine-copy-list { margin: 0; padding: 0; }
 .combine-copy-row { display: grid; grid-template-columns: 1rem minmax(0, 1fr) auto auto; align-items: center; gap: 0.35rem; min-width: 0; padding: 0.22rem 0.35rem; border: 1px solid var(--border); border-radius: var(--radius); font-size: 0.68rem; line-height: 1.25; white-space: nowrap; }
 .combine-copy-row.is-survives { border-color: var(--ok); }
 .combine-copy-row.is-destroyed { border-color: var(--danger); }
 .combine-copy-row.is-blocked, .combine-copy-row.is-not-included { opacity: 0.62; }
 .combine-copy-mark { color: var(--meta); font-size: 0.78rem; text-align: center; }
 .combine-copy-row.is-survives .combine-copy-mark { color: var(--ok); }
 .combine-copy-row.is-destroyed .combine-copy-mark, .combine-copy-row.is-destroyed .combine-copy-state { color: var(--danger); }
 .combine-copy-id { min-width: 0; overflow: hidden; color: var(--heading); font-family: var(--font-mono); text-overflow: ellipsis; }
 .combine-copy-id.is-destroyed { text-decoration: line-through; text-decoration-thickness: 1px; }
 .combine-copy-meta, .combine-copy-state { color: var(--meta); font-variant-numeric: tabular-nums; }
 .combine-copy-state { text-align: right; }
 .combine-copy-more { padding: 0.2rem 0.35rem; color: var(--meta); font-size: 0.68rem; white-space: nowrap; }
.combine-skin-warning { margin: 0.45rem 0; padding: 0.45rem 0.6rem; border: 1px solid var(--danger); border-radius: var(--radius); font-size: 0.74rem; color: var(--heading); }
.combine-result { margin: 0.4rem 0; font-size: 0.78rem; }
.combine-preview-message { margin: 0; color: var(--heading); font-size: 0.75rem; }
.combine-preview-message.is-refusal { color: var(--heading); }
.combine-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem 0.9rem; }
.combine-editor-footer .combine-actions { justify-content: flex-end; }
.combine-editor-footer .combine-actions > .ceres-btn { min-block-size: 44px; }
.combine-sign { position: static; margin: 0; }
.combine-verification { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--border); font-size: 0.74rem; }
.combine-verification.is-success { color: var(--ok); }
.combine-verification.is-error { color: var(--heading); }

/* States 05/06 -- the legible wait between pressing Combine and a verdict
   (work/combine-modal-redesign): three named steps, waiting -> active -> done,
   replacing what used to render nothing distinguishable. */
.combine-progress { display: flex; flex-direction: column; gap: 0.35rem; margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid var(--border); }
.combine-step { display: flex; align-items: center; gap: 0.45rem; font-size: 0.76rem; color: var(--meta); }
.combine-step-dot { width: 0.55rem; height: 0.55rem; border-radius: 50%; border: 1.5px solid var(--border); flex: 0 0 auto; }
.combine-step.is-waiting .combine-step-label { color: var(--meta); }
.combine-step.is-active { color: var(--heading); }
.combine-step.is-active .combine-step-dot { border-color: var(--accent); background: var(--accent); animation: combine-step-pulse 1.1s ease-in-out infinite; }
.combine-step.is-done { color: var(--ok); }
.combine-step.is-done .combine-step-dot { border-color: var(--ok); background: var(--ok); }
.combine-step-detail { margin-left: auto; color: var(--meta); font-family: var(--font-mono); font-size: 0.68rem; overflow-wrap: anywhere; }
@keyframes combine-step-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* State 06b -- three distinct non-success surfaces (CLAUDE.md's four-verdict
   split): pending (honest, uncertain), could-not-verify (unknown), mismatch
   (the ONLY real failure). Never share a look with each other or with success. */
.combine-verdict { margin-top: 0.5rem; padding: 0.5rem 0.65rem; border-radius: var(--radius); font-size: 0.76rem; display: flex; flex-direction: column; gap: 0.3rem; align-items: flex-start; }
.combine-verdict-title { margin: 0; font-weight: 600; }
.combine-verdict-detail { margin: 0; color: var(--meta); }
.combine-verdict.is-pending { border: 1px solid var(--border); background: color-mix(in srgb, var(--border) 25%, transparent); }
.combine-verdict.is-pending-escalated { border: 1px solid var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); }
.combine-verdict.is-pending-escalated .combine-verdict-title { color: var(--warn); }
.combine-verdict.is-could-not-verify { border: 1px solid var(--border); background: color-mix(in srgb, var(--border) 25%, transparent); }
.combine-verdict.is-could-not-verify .combine-verdict-title { color: var(--heading); }
.combine-verdict.is-mismatch { border: 1px solid var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent); }
.combine-verdict.is-mismatch .combine-verdict-title { color: var(--danger); }

 .combine-editor-footer { position: sticky; bottom: 0; z-index: 1; min-width: 0; margin: 0 -2rem; padding: 0.75rem 2rem 1rem; border-top: 1px solid var(--border); background: var(--bg2); }
 /* IR-9283: phones keep the body scroll inside the panel (header stays), the footer sticks to the bottom of that
    scroll, and every control is a 44px target. IR-9306 uses the same content order at both sizes. */
 @media (max-width: 600px) {
   .slot-hazard-overlay.combine-dialog-overlay { padding: 0.5rem; }
   .slot-hazard-panel.combine-dialog-panel { width: 100%; min-width: 0; max-width: 100%; max-height: calc(var(--ceres-vh) * 100 - 1rem); }
   .slot-hazard-body.combine-dialog-body { padding: 1rem 1rem 0; flex: 1 1 auto; overscroll-behavior: contain; }
   .combine-editor-layout { gap: 1rem; }
   .combine-ledger { padding: 0.75rem; gap: 0.6rem; }
   .combine-copy-list { max-height: none; overflow-y: visible; }
   .combine-editor-footer { margin: 0 -1rem; padding: 0.75rem 1rem 1rem; }
   .combine-dialog-panel .ceres-btn, .combine-dialog-panel .combine-stepper .ceres-btn { min-block-size: max(44px, 2.75rem); min-inline-size: max(44px, 2.75rem); }
   .combine-dialog-panel input[type="checkbox"] { inline-size: max(44px, 2.75rem); block-size: max(44px, 2.75rem); }
   .combine-actions > .ceres-btn { flex: 1 1 auto; }
   .combine-verification { margin-top: 0.75rem; padding-top: 0.75rem; }
 }

 /* The ledger keeps PP visible because it is the land-specific outcome figure. */
.combine-ledger-secondary { font-size: 0.9rem; }
.combine-ledger { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 0.9rem; background: color-mix(in srgb, var(--border) 40%, transparent); border: 1px solid var(--border); border-radius: var(--radius); padding: 0.9rem 1rem; margin: 0; }
.combine-ledger-label { font-size: 0.66rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--meta); margin-bottom: 0.2rem; }
.combine-editor-layout .combine-ledger-label { font-size: 0.75rem; }
.combine-ledger-big { font-size: 1.05rem; font-weight: 600; color: var(--heading); }
.combine-ledger-sm { font-size: 0.72rem; color: var(--meta); }
.combine-editor-layout .combine-ledger-sm { font-size: 0.75rem; font-variant-numeric: tabular-nums; }
.combine-ledger-arrow { color: var(--accent); font-size: 1rem; }
.combine-ledger-col.is-after .combine-ledger-big { color: var(--ok); }
.combine-ledger-delta { font-size: 0.7rem; color: var(--ok); margin-top: 0.15rem; }
.combine-editor-layout .combine-ledger-delta { font-size: 0.75rem; font-variant-numeric: tabular-nums; }
.combine-maxed { display: inline-block; font-size: 0.58rem; letter-spacing: 0.04em; color: var(--ok); border: 1px solid var(--ok); border-radius: 3px; padding: 0 0.3rem; margin-left: 0.25rem; vertical-align: middle; }

.combine-amount { margin: 0; }
.combine-presets { display: flex; flex-wrap: wrap; gap: 0.45rem; margin-bottom: 0.65rem; }
.combine-stepper { display: flex; align-items: center; gap: 0.6rem; }

.combine-stepper-val { font-weight: 600; min-width: 2rem; text-align: center; font-variant-numeric: tabular-nums; }

/* IR-9306 desktop standards pass. Keep the owner-approved phone pilot intact. */
@media (min-width: 601px) {
  .slot-hazard-overlay.combine-dialog-overlay { background: color-mix(in srgb, var(--shadow) 58%, transparent); }
  #combine-dialog-overlay > [role="dialog"] { box-shadow: 0 18px 60px color-mix(in srgb, var(--shadow) 70%, transparent); }
  .slot-hazard-panel.combine-dialog-panel > header { gap: 0.5rem; border-bottom-color: var(--border-bright); padding: 0.75rem 1rem; }
  .slot-hazard-panel.combine-dialog-panel > header h3 { flex: 1 1 auto; min-width: 0; margin: 0; color: var(--heading); font-size: 1.25rem; }
  .slot-hazard-panel.combine-dialog-panel > header .hazard-close { flex: none; }
  .slot-hazard-body.combine-dialog-body { padding: 1rem 1rem 0; font-size: 0.8rem; }
  .combine-editor-footer { margin-inline: -1rem; padding-inline: 1rem; }
  .combine-editor-result { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-content: start; gap: 0.5rem 1rem; }
  .combine-editor-result > .combine-ledger { box-sizing: border-box; grid-column: 1; grid-row: 1 / span 3; min-height: 6rem; }
  .combine-editor-result > :not(.combine-ledger) { grid-column: 2; }
  .combine-ledger { gap: 0.75rem; padding: 0.75rem 1rem; }
  .combine-ledger-big { font-size: 1rem; }
  .combine-ledger-label { margin-bottom: 0.25rem; }
  .combine-ledger-delta { margin-top: 0.25rem; }
  .combine-maxed { font-size: 0.7rem; border-radius: 8px; padding-inline: 0.25rem; margin-left: 0.25rem; }
  .combine-presets { gap: 0.5rem; margin-bottom: 0.5rem; }
  .combine-stepper { gap: 0.5rem; }
  .combine-actions { gap: 0.75rem; }
  .combine-consequence, .combine-preview-message { font-size: 0.9rem; }
  .combine-copy-row { padding: 0.25rem 0.35rem; font-size: 0.75rem; }
  .combine-copy-mark { font-size: 0.75rem; }
  .combine-copy-more { padding: 0.25rem 0.35rem; font-size: 0.75rem; }
  .combine-copy-row.is-blocked, .combine-copy-row.is-not-included { opacity: 1; }
  .combine-copy-meta, .combine-copy-state, .combine-copy-more { color: var(--body); }
  .combine-copy-row.is-survives .combine-copy-state { color: var(--ok); }
  .combine-copy-row.is-blocked { border-color: var(--danger); }
  .combine-copy-row.is-blocked .combine-copy-state,
  .combine-copy-row.is-destroyed .combine-copy-state,
  .combine-consequence,
  .combine-preview-message.is-refusal { color: color-mix(in srgb, var(--danger) 65%, var(--body)); }
  .combine-copy-row.is-destroyed { background: color-mix(in srgb, var(--danger) 10%, var(--bg2)); }
}
 .combine-ceiling-caution { margin: 0; color: var(--warn); font-size: 0.68rem; line-height: 1.25; }

/* "Arrive" state (v0.7.81, work/combine-modal-redesign/arrive-spec.md) -- the card
   itself is the hero, same as the game's own combine dialog. Renders in the SAME
   .combine-dialog-body once a combine's broadcast succeeds (optimistically) or
   verifyCombineBroadcast() reaches domain-confirmed, replacing the old
   delete-and-close path that gave zero visible feedback. */
.combine-arrival { display: flex; flex-direction: column; gap: 0.7rem; }

/* GIGANTIC success statement (owner, 2026-08-30: "a gigantic success message")
   -- dominates the panel, never reads as body text. .is-provisional (broadcast
   sent, domain verdict not in yet) stays loud but swaps the ok color for warn
   so an unconfirmed state never LOOKS confirmed. */
.combine-arrival-headline { margin: 0; font-size: 1.5rem; font-weight: 800; line-height: 1.15; color: var(--ok); display: flex; align-items: center; gap: 0.4rem; }
.combine-arrival-headline.is-provisional { color: var(--warn); }
.combine-arrival-headline-icon { font-size: 1.3rem; }

/* Consumed -> survivor stage: "these became that" read off the layout, not a
   number (owner: "in the smartest way possible"). Wraps on narrow widths
   rather than compressing the survivor. */
.combine-arrival-stage { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; }
.combine-arrival-consumed-row { display: flex; flex-wrap: wrap; gap: 0.35rem; max-width: 100%; }
.combine-arrival-consumed-card { display: flex; flex-direction: column; align-items: center; gap: 0.15rem; width: 2.4rem; }
.combine-arrival-consumed-tile { position: relative; width: 2.4rem; height: 2.4rem; border-radius: var(--radius); overflow: hidden; background: color-mix(in srgb, var(--border) 30%, transparent); filter: grayscale(0.85) brightness(0.75); opacity: 0.7; }
/* `.is-none` lands on the OUTER `.combine-arrival-consumed-card` (both from the
   img's own onerror and from combineArrivalConsumedCardHtml() when no art URL
   is even computable) -- selector must chain from there, not style the tile
   class directly, or a failed load never gets the dashed border (it used to
   target `.combine-arrival-consumed-tile.is-none`, a class never actually set
   on the tile itself -- the CSS side of a two-part mismatch fix). */
.combine-arrival-consumed-card.is-none .combine-arrival-consumed-tile { border: 1px dashed var(--border); }
.combine-arrival-consumed-art { width: 100%; height: 100%; object-fit: contain; display: block; }
/* The strike itself -- a single diagonal line across the dimmed tile, the
   universal "this is gone" mark, so "destroyed" reads before the Card ID
   under it is even parsed. */
.combine-arrival-consumed-strike { position: absolute; inset: 0; background: linear-gradient(to top right, transparent calc(50% - 1px), var(--danger) calc(50% - 1px), var(--danger) calc(50% + 1px), transparent calc(50% + 1px)); }
/* Fallback name text -- hidden unless the tile has no usable art (`.is-none`),
   so the destroyed card stays identifiable by name even when its art tile is
   empty, not only by the Card ID below it. */
.combine-arrival-consumed-name { display: none; }
.combine-arrival-consumed-card.is-none .combine-arrival-consumed-name { display: flex; position: absolute; inset: 0; align-items: center; justify-content: center; text-align: center; overflow: hidden; padding: 0.1rem; font-size: 0.4rem; font-weight: 700; line-height: 1.05; color: var(--text); }
.combine-arrival-consumed-uid { font-size: 0.55rem; color: var(--meta); font-family: var(--font-mono); overflow-wrap: anywhere; text-align: center; opacity: 0.75; }
.combine-arrival-merge-arrow { font-size: 1.3rem; color: var(--meta); flex: 0 0 auto; }

/* The surviving card -- large and celebratory, per the owner's own combine-
   dialog reference ("the card is the hero"). Noticeably bigger than the
   consumed tiles above and given a warm glow so it reads as the winner, not
   just another card in the row. */
.combine-arrival-survivor { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.combine-arrival-art { width: 7rem; height: 7rem; object-fit: contain; flex: 0 0 auto; border-radius: var(--radius); background: color-mix(in srgb, var(--border) 30%, transparent); box-shadow: 0 0 0 2px var(--ok), 0 0 1.1rem color-mix(in srgb, var(--ok) 45%, transparent); }
.combine-arrival.is-optimistic .combine-arrival-art { box-shadow: 0 0 0 2px var(--warn), 0 0 1.1rem color-mix(in srgb, var(--warn) 45%, transparent); }
/* Fallback box for the hero survivor tile -- rendered whenever no art URL is
   computable up front, and swapped in by the img's own onerror when a
   computed URL fails to load. Never blank: it carries the card's name, the
   same ring/glow the art tile has, and a dashed border so "no picture" reads
   as a deliberate state rather than a broken one. */
.combine-arrival-art-fallback { display: none; }
.combine-arrival-survivor.is-none .combine-arrival-art-fallback { display: flex; align-items: center; justify-content: center; text-align: center; width: 7rem; height: 7rem; padding: 0.5rem; border-radius: var(--radius); border: 1px dashed var(--border); background: color-mix(in srgb, var(--border) 30%, transparent); box-shadow: 0 0 0 2px var(--ok), 0 0 1.1rem color-mix(in srgb, var(--ok) 45%, transparent); font-size: 0.8rem; font-weight: 700; color: var(--text); }
.combine-arrival.is-optimistic .combine-arrival-survivor.is-none .combine-arrival-art-fallback { box-shadow: 0 0 0 2px var(--warn), 0 0 1.1rem color-mix(in srgb, var(--warn) 45%, transparent); }
.card-art-fallback { display: none; }
.card-art-fallback-container { position: relative; container-type: inline-size; }
.card-art-fallback-container.is-none .card-art-fallback { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.08rem; box-sizing: border-box; overflow: hidden; padding: 0.08rem; border: 1px dashed var(--border); border-radius: inherit; background: var(--bg3); color: var(--heading); font-size: clamp(0.45rem, 13cqi, 0.7rem); font-weight: 700; line-height: 1.05; text-align: center; }
.card-art-fallback-name { max-width: 100%; max-height: 2.1em; overflow: hidden; }
.card-art-fallback-marker { flex: 0 0 auto; white-space: nowrap; font-size: 0.82em; }
@container (max-width: 3rem) { .card-art-fallback-name { display: none; } .card-art-fallback-marker { font-size: 0.5rem; } }
.combine-arrival-survivor-level { font-weight: 700; font-size: 0.85rem; }

.combine-arrival-details { flex: 1 1 auto; min-width: 0; }
.combine-arrival-ledger { margin: 0 0 0.5rem; }
.combine-arrival-confirm { margin: 0 0 0.35rem; color: var(--ok); font-size: 0.8rem; font-weight: 600; }
.combine-arrival-confirm.is-provisional { color: var(--warn); }
.combine-arrival-destroyed { margin: 0 0 0.5rem; color: var(--meta); font-size: 0.72rem; }

/* Worker-subtable footer notes (Florian item K, 2026-08-21 polish pass) --
   replace the old raw-JSON `.deed-stats-chip` dump and the every-row DEC
   caveat paragraph (`.dec-note`, removed -- the caveat now lives in the
   worker-subtable's own DEC `<th title>` instead, discoverable on demand
   rather than repeated on every plot). */
.deed-stats-note { font-size: 0.75rem; opacity: 0.8; margin-top: 0.3rem; }
.multiplier-breakdown { font-size: 0.75rem; opacity: 0.8; margin-top: 0.15rem; cursor: help; }
.cost-line { font-size: 0.75rem; opacity: 0.8; margin-top: 0.15rem; cursor: help; }

/* ---------------------------------------------------------------------
   Land data colors (Stage B visual pass, 2026-08-21).

   These are DATA colors -- the game's own official terrain hex values
   (ceres/static/assets/manifest.json:terrain_colors), not Iris theme
   tokens -- so they are defined here as their OWN named custom properties,
   in this one place, rather than sprinkled inline in HTML/JS. They do not
   vary by light/dark theme (a terrain swatch is the same swatch either
   way); only the per-terrain foreground pairs below vary, and each pair
   was chosen by computed WCAG contrast ratio against BOTH pure white and
   pure black (script-checked, not eyeballed -- see docs/ceres.md), never
   assumed from the theme's default text color. 12 of 14 hexes come from
   the Praetoria API's per-plot hex_code field; Badlands/Bog are
   Florian-direct (flauwy owns neither terrain) -- see the manifest's
   terrain_colors_source note. */
:root {
  /* S-14210: shared width of the Wallet edge picker's `.edge-toggle` and the Iris
     picker's own real measured width -- kept as one token so their geometry can't
     drift apart. The Iris picker itself is untouched/unowned (canonical.css), so it is
     not driven by this token, but its own recipe is replicated by the `.edge-picker`
     family (S-14250). IR-6730 (owner, verbatim: "the entire slide bar must be
     thinner"): `#badge-bar`/`#badge-flag` no longer share this token -- see
     `--badge-bar-w` below, their own dedicated (narrower) width. */
  --edge-w: 48px;
  /* IR-6730 (owner, verbatim, high: "the entire slide bar must be thinner because
     of that [sitting flush at the page edge]") gave this its own dedicated,
     narrower width, independent of `--edge-w` -- 1.5rem/24px, half the Iris/
     Wallet handles' own ~48-52px, with `.badge-bar-row`'s dots/tick scaled down
     to fit.
     S-14780 (owner, verbatim: "The badges bar still is short than the main
     sliders, why" / "Not the height, width") SUPERSEDES that: the bar must be
     exactly as wide as the Iris/Wallet/flag pills and track their live width,
     not a fixed fraction of it -- `var(--edge-w, 2rem)` (same fallback order
     `--edge-w` itself uses before app.js's first measurement pass). Every
     consumer below (the `.scroll-top` offset, `--table-end-reserve`, the bar's
     own `inline-size`, `.badge-overlay`'s `right`) reads this token rather than
     a literal, so all of them widen automatically; only `.badge-bar-row`'s
     padding and comment needed a manual revisit for the wider box (below). */
  --badge-bar-w: var(--edge-w, 2rem);
  --ceres-pp-radius: 6px;
  --ceres-pp-block-pad: 0.15rem;
  /* Terrain foreground: UNIFORM WHITE across all 14 (Florian, 2026-08-21
     board-polish batch 9, item 7 -- OVERRIDES the earlier per-terrain
     computed WCAG choice below): "make the text for Terrain with colored
     backgrounds all white. currently canyon and desert are Black." This is
     Florian's explicit call, not a re-measurement -- the earlier pass DID
     compute contrast correctly (10 of 14 already cleared 4.5:1 on white; the
     4 exceptions -- Swamp, Canyon, Desert, Tundra -- were the ones flipped to
     black specifically because white read weak there). Made legible again
     via `text-shadow` on td.terrain-cell below rather than by silently
     re-picking black for the weak cases -- Florian asked for uniform white
     AND the numbers, not a silent exception. Measured white-on-terrain
     contrast (WCAG relative-luminance ratio, script-checked): River 7.90:1,
     Forest 9.05:1, Mountain 10.27:1, Hills 7.25:1, Jungle 6.58:1, Caldera
     6.67:1, Lake 5.41:1, Plains 5.59:1, Bog 4.99:1, Badlands 4.69:1 -- all
     comfortably at or above the AA 4.5:1 text floor even before the shadow.
     Below that floor on raw contrast alone: Canyon 4.31:1 (just under, AA
     large-text/UI floor of 3:1 still clears easily), Swamp 3.01:1 (at the
     3:1 floor), Desert 2.54:1, and TUNDRA 1.59:1 -- Tundra's own near-cream
     #DACDA1 is the one genuinely marginal case: even with the text-shadow
     below, white-on-Tundra is weak, and Florian should decide whether it
     warrants an exception (this pass implements uniform white as asked
     rather than silently exempting it). */
  --terrain-river: #265B43;     --terrain-river-fg: #ffffff;
  --terrain-lake: #357658;      --terrain-lake-fg: #ffffff;
  --terrain-hills: #445F1A;     --terrain-hills-fg: #ffffff;
  --terrain-forest: #494C07;    --terrain-forest-fg: #ffffff;
  --terrain-mountain: #4E3F14;  --terrain-mountain-fg: #ffffff;
  --terrain-jungle: #576301;    --terrain-jungle-fg: #ffffff;
  --terrain-caldera: #765542;   --terrain-caldera-fg: #ffffff;
  --terrain-plains: #796703;    --terrain-plains-fg: #ffffff;
  --terrain-swamp: #9E9820;     --terrain-swamp-fg: #ffffff;
  --terrain-canyon: #A2701B;    --terrain-canyon-fg: #ffffff;
  --terrain-desert: #C19F34;    --terrain-desert-fg: #ffffff;
  --terrain-tundra: #DACDA1;    --terrain-tundra-fg: #ffffff;
  --terrain-badlands: #A1663C;  --terrain-badlands-fg: #ffffff;
  --terrain-bog: #457D24;       --terrain-bog-fg: #ffffff;

  /* Territory colours (Florian item J, 2026-08-21 polish pass -- REVISED same day
     from an edge-marker to a full BACKGROUND FILL on the existing Plot cell,
     Florian: "use territory colors as background, like terrain" -- same
     treatment as the terrain tokens above: DATA colors, not Iris theme tokens,
     identical in both themes, square-cornered full-bleed fill, no new column).

     Foreground: Florian's explicit rule (board-polish batch 9, item 6): "add
     better contrast text color to Plot column? I suggest white for those
     with color backgrounds and dark for the grey background" -- OVERRIDES
     the earlier per-territory computed-contrast pass (which had put 6 of 7
     on black; see git history), same kind of explicit-preference override as
     item 7's terrain rule above. Land of Prosperity (#999999, the one grey
     fill) now uses white text with shadow (S-13430); other colored fills also use white; Pristine
     Northwest (#FFFFFF) is a separate case, below. Measured white-on-
     territory contrast (WCAG relative-luminance ratio, script-checked):
     Shimmering Coasts 7.33:1 (comfortably AA), Great Lowlands 3.68:1 (clears
     the 3:1 UI-component floor, below the 4.5:1 text floor), Land of
     Prosperity 2.85:1 (white text with dark shadow per S-13430), Broken Lands 2.78:1, Wild Northeast 2.30:1, and Central Fire
     2.16:1 -- these last three are BELOW the 3:1 floor on raw contrast alone
     and are the marginal cases Florian should see: implemented as asked
     (white), helped by the same text-shadow treatment as the Terrain column
     (td.plot-cell below), but flagged here rather than silently exempted.
     Pristine Northwest is pure white and would otherwise vanish against a
     light-theme page background -- it carries its own `-accent` token
     (#777777, manifest.json's `accent_hex`) for a full cell border that keeps
     it a visible, delimited cell in both themes (unchanged from the earlier
     pass), plus keeps DARK text (white-on-white would be invisible) -- the
     one territory this rule doesn't touch. */
  --territory-pristine-northwest: #FFFFFF; --territory-pristine-northwest-fg: #000000; --territory-pristine-northwest-accent: #777777;
  --territory-broken-lands: #4CAF50;       --territory-broken-lands-fg: #ffffff;
  --territory-land-of-prosperity: #999999; --territory-land-of-prosperity-fg: #ffffff;
  --territory-central-fire: #FF9800;       --territory-central-fire-fg: #ffffff;
  --territory-wild-northeast: #00BCD4;     --territory-wild-northeast-fg: #ffffff;
  --territory-great-lowlands: #F44336;     --territory-great-lowlands-fg: #ffffff;
  --territory-shimmering-coasts: #673AB7;  --territory-shimmering-coasts-fg: #ffffff;

}

/* Terrain cell -- background is the official terrain color, text (the
   terrain name, unchanged) uses the paired high-contrast foreground. The
   14 classes are generated once from the tokens above; nothing here is a
   raw hex. */
table.plot-table td.terrain-cell {
  /* Square-cornered, full-bleed within the cell (Florian item 4, 2026-08-21 polish
     pass) -- was `border-radius: var(--radius)`, now explicitly none so a future
     Iris radius-token change can't silently reintroduce rounding here. */
  border-radius: 0;
  /* Text-shadow (item 7, paired with the uniform-white override above) --
     a tight dark halo so white terrain-name text holds up even on the
     lighter fills (Swamp/Canyon/Desert, and especially the near-cream
     Tundra #DACDA1 -- see the contrast numbers in the --terrain-*-fg block
     above). Two stacked shadows: a soft wide one for general separation, a
     tighter one for edge definition on the thinner glyph strokes. */
  text-shadow: 0 1px 2px color-mix(in srgb, var(--shadow) 85%, transparent), 0 0 4px color-mix(in srgb, var(--shadow) 55%, transparent);
}
td.terrain-cell.terrain-river     { background: var(--terrain-river);     color: var(--terrain-river-fg); }
td.terrain-cell.terrain-lake      { background: var(--terrain-lake);      color: var(--terrain-lake-fg); }
td.terrain-cell.terrain-hills     { background: var(--terrain-hills);     color: var(--terrain-hills-fg); }
td.terrain-cell.terrain-forest    { background: var(--terrain-forest);    color: var(--terrain-forest-fg); }
td.terrain-cell.terrain-mountain  { background: var(--terrain-mountain);  color: var(--terrain-mountain-fg); }
td.terrain-cell.terrain-jungle    { background: var(--terrain-jungle);    color: var(--terrain-jungle-fg); }
td.terrain-cell.terrain-caldera   { background: var(--terrain-caldera);   color: var(--terrain-caldera-fg); }
td.terrain-cell.terrain-plains    { background: var(--terrain-plains);    color: var(--terrain-plains-fg); }
td.terrain-cell.terrain-swamp     { background: var(--terrain-swamp);     color: var(--terrain-swamp-fg); }
td.terrain-cell.terrain-canyon    { background: var(--terrain-canyon);    color: var(--terrain-canyon-fg); }
td.terrain-cell.terrain-desert    { background: var(--terrain-desert);    color: var(--terrain-desert-fg); }
td.terrain-cell.terrain-tundra    { background: var(--terrain-tundra);    color: var(--terrain-tundra-fg); }
td.terrain-cell.terrain-badlands  { background: var(--terrain-badlands);  color: var(--terrain-badlands-fg); }
td.terrain-cell.terrain-bog       { background: var(--terrain-bog);       color: var(--terrain-bog-fg); }

/* Column alignment (Florian, 2026-08-21 sixth polish pass -- CORRECTS the
   previous "centre every icon-bearing column" rule, which overshot).
   Governing rule, stated explicitly this time and applied consistently
   rather than per-column: an ICON-ONLY cell is centred; a cell that pairs an
   icon (or nothing) WITH TEXT is left-aligned, because centering text makes
   it start at a different x-position on every row depending on that row's
   icon width -- reads as "weird" (Florian's word) at 130 rows. Header
   alignment always follows its own column's content alignment.

       Plot      CENTRE  (text only)
       Rarity    CENTRE  (icon only)
       Worksite  CENTRE  (icon only)
       Type      CENTRE  (icon only -- board-polish batch 19, item 1: text
                          moved into the tooltip, was icon + text / LEFT)
       Totem     CENTRE  (icon only -- same batch 19 item 1 change)
       Title     CENTRE  (icon only -- same batch 19 item 1 change)
       Terrain   LEFT    (icon + text -- untouched by batch 19, still the
                          identical icon+text shape the general rule covers)

   LEFT is simply the table's own base rule (`text-align: left`, above) doing
   nothing further needed -- Terrain's cell and header were never touched by
   centering below, so no explicit left-align rule is required for it. The
   six CENTRE cases need an override, using the same base-rule-matching
   specificity fix as the ROOT CAUSE note below (unchanged from the previous
   pass): `table.plot-table td.rarity-cell` etc. matches the base
   rule's own `#plot-table` prefix so it reliably wins regardless of source
   order.

   Every icon `<img>` still uses `vertical-align: middle` (vertical centering
   -- unaffected by this horizontal-alignment correction, still correct on
   every column, icon-only or icon+text alike). */
table.plot-table th.col-center,
table.plot-table td.plot-cell,
table.plot-table td.rarity-cell,
table.plot-table td.resource-cell,
table.plot-table td.type-cell,
table.plot-table td.totem-cell,
table.plot-table td.title-cell {
  text-align: center;
}
table.plot-table th[data-key="lock_state"],
table.plot-table td.lock-cell { text-align: left; }
table.plot-table :is(th[data-key="lock_state"], td.lock-cell) .plot-lock-icon { display: flex; justify-content: flex-start; }
table.plot-table td.lock-cell .plot-lock-icon > .is-locked { color: var(--danger); }
table.plot-table td.lock-cell .plot-lock-icon > .is-unlocked { color: var(--ok); }
table.plot-table :is(th[data-key="lock_state"], td.lock-cell) > .plot-lock-icon { margin-inline-start: 1rem; }
table.plot-table thead th[data-key="lock_state"] .plot-lock-icon { display: inline-flex; vertical-align: middle; line-height: 0; }
table.plot-table thead th[data-key="lock_state"] .plot-lock-icon svg { inline-size: 1.1rem; block-size: 1.1rem; display: block; stroke-width: 2.6; }   /* IR-8559: the header lock is as large as the locks of the rows, and bold */
html:not(.ceres-lw-le-1279) table.plot-table thead th[data-key="lock_state"] .plot-lock-icon svg { inline-size: 1.1rem; block-size: 1.1rem; }

table.plot-table col.col-w-totem,
table.plot-table col.col-w-title,
table.plot-table th[data-key="totem_boost"],
table.plot-table th[data-key="title_boost"],
table.plot-table td.totem-cell,
table.plot-table td.title-cell {
  min-width: 4rem;
}
.totem-cell, .title-cell { position: relative; }
.totem-cell .bonus-conflict, .title-cell .bonus-conflict {
  position: absolute;
  inset-block-start: -.15rem;
  inset-inline-start: 1rem;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  inline-size: .875rem;
  min-inline-size: .875rem;
  block-size: .875rem;
  margin: 0;
  padding: 0;
  border-radius: 50%;
  font-size: 0;
  line-height: .875rem;
  text-align: center;
  box-shadow: 0 1px 2px color-mix(in srgb, var(--shadow) 55%, transparent);
}
.totem-cell .bonus-conflict::after, .title-cell .bonus-conflict::after {
  content: "!";
  font-size: .625rem;
  line-height: 1;
}
.totem-cell .bonus-conflict, .title-cell .bonus-conflict {
  background: color-mix(in srgb, var(--danger) 22%, var(--bg3));
}
/* Icon boxes enlarged board-wide (board-polish batch 19, item 12/D -- Florian:
   "Increase the icon size and text size accordingly for better readability.
   At normal view icons are tiny."). All rendered box sizes below moved from
   ~1.0-1.1rem (16-17.6px) to 1.3rem (~20.8px), paid for by the padding cut on
   `table.plot-table th/td` above. Every icon touched here is either
   an SVG (rarity gems, terrain emblems, splinter icons) or a raster PNG/WEBP
   whose SOURCE resolution (200-470px, checked directly against the files under
   ceres/static/assets/) is far above this render size -- FR-1672's baked
   contrast halo (tools/bake_icon_shadows.py) scales UNIFORMLY with the whole
   image/vector when the CSS render box changes, so this size bump does not
   make the halo look soft or wrong and no asset re-bake was needed; verified
   visually at 1920/1280/1024 (see work/ceres-batch19-perf.md). */
.terrain-emblem { width: 1.25rem; height: 1.25rem; vertical-align: middle; margin-right: 0.3rem; flex: none; }

/* Rarity gems replace the plain rarity text (Florian: "the four gems for
   rarities instead of text"). The rarity name is kept as alt/title text so
   it stays identifiable and accessible -- not lost, just no longer spelled
   out visually. */
.rarity-gem { width: 1.3rem; height: 1.3rem; vertical-align: middle; }
/* IR-8140 (owner: "totems and rarity badge regressed into moving apart from
   each other. They must sit side by side and belong together... they have a
   margin-left and margin-right. Removing that solves the issue"): the title
   icon and rarity gem must touch, not carry breathing room between them. */
/* `resolved`/`derived` no longer render a pill (owner, 2026-08-30: "noise and
   disturbing" -- derived's note now lives in the icon's own tooltip instead,
   see bonusProvenanceNote() in app.js). `.bonus-resolved`/`.bonus-derived`
   removed as dead CSS -- neither class is emitted any more. `conflict` is
   the one exception kept visible: a genuine anomaly, not noise. */
.bonus-resolution { display: inline-block; margin-left: 0.25rem; padding: 0.08rem 0.25rem; border-radius: 999px; font-size: 0.58rem; line-height: 1.2; vertical-align: middle; white-space: nowrap; cursor: help; }
.bonus-conflict { border: 1px solid var(--danger); color: var(--danger); font-weight: 700; }

/* Plot-type mark, resource icons, totem/title marks -- small inline glyphs
   sized to not grow row height (density is a feature, per Florian). All four
   plot types (natural/magical/occupied/kingdom) get an icon now (Florian's
   2026-08-21 correction of an earlier "no natural icon exists" finding on
   this project -- see manifest.json's land/status-natural-off.svg /
   status-kingdom-off.svg entries); magical additionally gets a second,
   element-keyed icon (`.type-mark-element`) right after the type icon. */
/* Icon-only Type cell (board-polish batch 19, item 1) -- margin-right on the
   single (non-magical) icon is gone: it used to hold the text label away from
   the icon, but with the label removed and the column centered, a trailing
   margin would just push the icon off-center inside the cell. The magical
   row's second (element) icon still gets its own gap via
   `.type-mark-element`'s margin-left below, so the two-icon case is
   unaffected. */
.type-mark { width: 1.25rem; height: 1.25rem; vertical-align: middle; }
/* Gap before the trailing element/splinter icon on magical rows (Florian,
   2026-08-21 board-polish batch 9, item 3: "Add spacing between the Magical
   type icon and the splinter icon -- they currently touch"). */
.type-mark-element { margin-left: 0.3rem; }
.resource-icon { width: 1.3rem; height: 1.3rem; vertical-align: middle; margin-right: 0.3rem; object-fit: contain; }
/* S-14060: the icon that follows a resource name inside running text and button labels. */
.res-inline-icon { inline-size: 1.25em; block-size: 1.25em; object-fit: contain; vertical-align: -0.3em; margin-inline: .2em .1em; }
.resource-icon.is-tax { width: 1.7rem; height: 1.7rem; margin-block: -0.2rem; } /* IR-5360: 27 px box for the landscape Keep/Castle art; margin-box stays 1.3 rem */
/* Harvest/hr body cell (board-polish batch 19, item 2) is the ONLY other
   resourceIconHtml() call site besides Worksite -- both are icon-trailing-a-
   value cells, no visible unit text after the icon any more, so the same
   margin-right above (spacing before where "/hr" used to sit) is now just
   trailing whitespace inside the cell; harmless, left as-is rather than
   forking a second icon size for one call site. */
/* A lone Totem image must not carry a trailing inline margin: text-align centers
   the inline formatting box including its margin, which would shift the painted
   image off the column midpoint. IR-8140: the Title icon no longer keeps a
   gap to the gem -- the owner wants them touching, like Totem. */
.totem-icon { width: 1.3rem; height: 1.3rem; vertical-align: middle; margin-right: 0; }
.title-icon { width: 1.3rem; height: 1.3rem; vertical-align: middle; margin-right: 0; }
/* Title cell (board-polish batch 19, item 1 -- Florian: "Title will keep both
   icons, title plus rarity, perfectly vertical aligned"): the rarity gem is
   1.3rem elsewhere (.rarity-gem below) and title-icon is also 1.3rem above --
   equalized here, ONLY inside .title-cell, so both icons are the exact same
   box size and `vertical-align: middle` lines their tops up exactly rather
   than merely centering two different-height boxes on the same midpoint. */
.title-cell .title-icon { width: 1.3rem; height: 1.3rem; margin-right: 0; }

/* Ability icons -- worker sub-table Abilities column (board-polish, 2026-08-21,
   Florian: "Land card and VoV Abilities have specific icons which we should
   also get... use them for abilities in the worker table abilities column,
   instead of text and move the text into the tooltip"). See abilityIconHtml()
   in app.js for the code->icon mapping and the unmapped-code text fallback.
   No filter/drop-shadow/box-shadow here (FR-1672 -- that class of rule is
   what caused the 700-icon/7068ms raster regression); active/inactive
   BLOODLINE status is signalled with plain opacity instead, which does not
   trigger a per-pixel filter pass. */
.ability-icon { width: 1.1rem; height: 1.1rem; vertical-align: middle; margin-right: 0.2rem; }
.abilities-cell { white-space: nowrap; }
.ability-icon.ability-bloodline-inactive { opacity: 0.45; }
/* `.ability-icon.ability-bloodline-paired`/`-solo` (an `outline`+`outline-offset`
   box drawn around the tiny Toil and Kin icon) landed 2026-08-24 23:53
   (commit 36083ed, v0.5.20's "paired worker icon state") and REMOVED here
   2026-08-25 -- Florian, live on v0.5.20: "there is now a weird square around
   the toil and kin ability of the land cards. which looks ugly and stupid
   and offers no value." Rejected on both counts, not replaced with another
   icon-level decoration -- the state now lives where it can actually say
   something: the labelled PAIRED/SOLO card badge (`.slot-bloodline-badge`,
   further down this file) and the tile's own hover tooltip. `.ability-icon.ability-bloodline-
   inactive` immediately above is a DIFFERENT, older rule (predates v0.5.20,
   FR-1672's deliberate plain-opacity active/inactive signal) and is
   untouched. */
/* SOLO is now a warning badge (Florian, 2026-08-26, supersedes the earlier
   deliberate-state ruling): "is by default a warning badge to me... a clear
   underuse of potential." Same --warn treatment as the other warning badge,
   badge-underfilled, above -- never a new colour vocabulary. */

/* Plot cell + territory BACKGROUND FILL (Florian item J, 2026-08-21 polish pass,
   revised from an edge marker same day) -- same square-cornered, full-bleed
   treatment as td.terrain-cell above, on the EXISTING Plot cell, never a new
   column. Pristine Northwest additionally gets a full 1px border in its
   `-accent` token so the cell stays visibly delimited against a white/light
   page background, not just colour-filled with an invisible fill. */
td.plot-cell {
  border-radius: 0;
}
td.plot-cell.territory-pristine-northwest {
  background: var(--territory-pristine-northwest);
  color: var(--territory-pristine-northwest-fg);
  border: 1px solid var(--territory-pristine-northwest-accent);
}
td.plot-cell.territory-broken-lands       { background: var(--territory-broken-lands);       color: var(--territory-broken-lands-fg); }
td.plot-cell.territory-land-of-prosperity { background: var(--territory-land-of-prosperity); color: var(--territory-land-of-prosperity-fg); }
td.plot-cell.territory-central-fire       { background: var(--territory-central-fire);       color: var(--territory-central-fire-fg); }
td.plot-cell.territory-wild-northeast     { background: var(--territory-wild-northeast);     color: var(--territory-wild-northeast-fg); }
td.plot-cell.territory-great-lowlands     { background: var(--territory-great-lowlands);     color: var(--territory-great-lowlands-fg); }
td.plot-cell.territory-shimmering-coasts  { background: var(--territory-shimmering-coasts);  color: var(--territory-shimmering-coasts-fg); }
/* White-text territories only (item 6's rule above) get the same dark
   text-shadow treatment as the Terrain column, for the same reason -- three
   of these six (Broken Lands 2.78:1, Wild Northeast 2.30:1, Central Fire
   2.16:1) are below the 3:1 raw-contrast floor and need the help most.
   Pristine Northwest uses dark text on a white fill and
   is deliberately excluded -- a dark shadow behind dark text on a light
   background reads as muddy, not sharper. */
td.plot-cell.territory-broken-lands,
td.plot-cell.territory-land-of-prosperity,
td.plot-cell.territory-central-fire,
td.plot-cell.territory-wild-northeast,
td.plot-cell.territory-great-lowlands,
td.plot-cell.territory-shimmering-coasts {
  text-shadow: 0 1px 2px color-mix(in srgb, var(--shadow) 85%, transparent), 0 0 4px color-mix(in srgb, var(--shadow) 55%, transparent);
}

/* Worker-row splinter + set icons (expanded rows only -- these rows are
   already denser text, so icons stay small). */
.splinter-icon { width: 0.95rem; height: 0.95rem; vertical-align: -0.18em; margin-right: 0.25rem; }
.set-icon { width: 0.95rem; height: 0.95rem; vertical-align: -0.18em; margin-right: 0.2rem; }

/* Icon contrast shadow -- BAKED INTO THE ASSETS, not a runtime filter
   (FR-1672, 2026-08-21 scroll-perf fix; supersedes the "fourth polish pass"
   note this comment used to carry). Originally `filter: drop-shadow(...)`
   on every icon `<img>`, applied at paint time. Diagnosis
   (work/ceres-scroll-perf-diagnosis.md) measured this as the primary cause
   of catastrophic scroll jank: a filtered element gets its own compositor
   render surface, so `#plot-table`'s scroll layer could never be
   composited-scrolled and was re-rasterized on every tick (RasterTask
   7068ms -> 1934ms for a 50-tick scroll once the filter was removed;
   ScrollLayer time collapsed 300ms -> 3ms). Same halo, same two-shadow
   spec (dark 0.5px/55% + light 0.5px/55% for most icons; a tighter
   dark-only 0.25px/70% + 0.5px-offset/50% pass for the splinter family,
   whose thin internal linework smudged under the wider blur) -- now an SVG
   `<feDropShadow>` filter wrapped around each icon SVG's own content, or a
   pre-composited halo baked into the raster PNG/WEBP pixels, scaled to the
   CSS px size each icon actually renders at. See
   /tmp .../scratchpad/bake_shadows.py (run once, 2026-08-21) for the exact
   per-asset math (run once, 2026-08-21); assets themselves are the source
   of truth now, this rule is intentionally gone rather than narrowed. */
/* Set/edition short-name text (Florian item 2/3, 2026-08-21 polish pass: "show
   the set/edition name as text on all cards" / "use short set names") --
   replaces the old icon-only + promo/reward/extra-only print-class-badge
   rendering (ceres/static/app.js:setIconHtml). Every card now gets this label,
   not just the print-class ones. */
.set-name-label { font-size: 0.75rem; color: var(--text); }

/* Gold foil -- REMOVED FROM THE NAME CELL FOR GOOD (Florian, 2026-08-21
   seventh polish pass: "the third time should be impossible by construction,
   not by tuning the stops"). Full history, so nobody reintroduces this as an
   aesthetic improvement:
     1st pass: `background-clip: text` + `color: transparent`, no fallback --
       invisible whenever the clip didn't apply. Hit 39/182 live workers and
       635/7,568 live idle cards (21%/8%), read as "many card names are now
       missing".
     2nd pass (thought fixed): moved the SAME gradient behind `@supports
       (background-clip: text)` so it would only apply when confirmed
       supported. Wrong diagnosis -- every current browser supports it, so
       the gradient kept firing; its stops (#fff6d8/#e2b93b/#a9861f/#fff2c2)
       are pale enough to still be unreadable against a light theme or this
       project's own light territory backgrounds (Pristine Northwest
       #FFFFFF, Land of Prosperity #999999). Same population, same complaint,
       a second time.
   ROOT CAUSE both times: conveying foil status by styling the NAME text is
   structurally fragile regardless of which stops or fallback logic are used
   -- the name is the one thing on the row that must always stay legible.
   `ceres/static/app.js` no longer applies `.foil-gold` to the name `<td>` at
   all (worker rows or idle cards) -- gold is now communicated ONLY via the
   existing Foil column's own text ("Gold"/"Regular", `fmtFoil()`), styled
   below with a plain solid colour, never a gradient or transparency. */
.foil-gold {
  color: #d4a017;
  font-weight: 600;
}

/* NOTE: deed artwork (the 49 surveyed-deed JPGs) is deliberately NOT rendered
   in the board -- Florian, 2026-08-21: "deed artwork is for a later stage, if
   at all." The cached files and manifest.json entries stay in place and
   still show on assets-preview.html, but no board rule references them. */

/* S-15500: the reload banner is the one thing that must be readable at a glance in
   every theme. It takes its surface from the Iris background tokens, so a dark theme
   gets a dark banner and a light theme a light one with no per-theme rules, and its
   text from the themed body colour, which each Iris theme already defines to contrast
   with its own background. The accent is kept as a strong top edge instead of the
   fill, which is where the old poor contrast came from: --hi is white in every theme,
   so on the light theme white sat on a mid accent. */
.reload-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  text-align: center;
  padding: 0.75rem 1rem;
  background: var(--bg2);
  color: var(--body);
  border-block-end: 1px solid var(--border-bright);
  box-shadow: 0 10px 28px -12px var(--shadow), inset 0 3px 0 0 var(--accent);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  cursor: pointer;
}

.reload-banner:hover,
.reload-banner:focus-visible {
  background: var(--bg3, var(--bg2));
  color: var(--heading);
}

/* S-15500 follow-up: the bigger banner must not sit ON the header. When it is shown,
   the page is pushed down by its height so the wordmark and the account row stay whole;
   the banner is hidden the rest of the time, so nothing moves in normal use. */
body:has(#reload-banner:not([hidden])) {
  padding-block-start: var(--reload-banner-h, 44px);
}

body:has(#reload-banner:not([hidden])) header.board-header {
  top: var(--reload-banner-h, 44px);
}

/* ---------------------------------------------------------------------
   Custom dropdown (Florian, 2026-08-21 board-polish batch 9, item 8).

   The component itself (structure/behaviour) is vendored verbatim from
   a sibling project's customSelect.js into
   ceres/static/customSelect.js on 2026-08-21 -- see that file's header
   comment. This block is a FULL RESTYLE of Asdroid's own `csel-` rules
   (frontend/assets/styles.css) to fit Ceres: Asdroid's version leans on its
   own tokens (--primary/--tint-base/--heading/--step-0/--step--1/--z-popup)
   that don't exist in Iris's canonical.css (checked directly, 2026-08-21 --
   only --step-2 is defined estate-wide; Asdroid apparently layers its own
   local type-scale/z-index tokens on top of Iris, which Ceres doesn't).
   Every rule below uses ONLY tokens that are real in canonical.css's
   :root/theme blocks (--bg/--bg2/--border/--border-bright/--accent/--text/
   --radius/--meta) plus Ceres's own rem-based local sizing -- same
   discipline as the rest of this file. No new colors invented; chrome
   follows the existing .controls-row select styling above (same bg/border/
   radius) so the custom trigger looks like the native selects it replaces,
   just with the added option-decoration below. */
/* One-line fit at 1920px (0.3.4 board-fix batch): this used to be a flat 8rem
   floor on all five dropdowns (Sort/Rarity/Terrain/Worksite/Type) regardless of
   how short their actual content is -- "any" or a single rarity name doesn't
   need 128px. Dropped to 4.5rem so each trigger sizes close to its own real
   content (a long selected value, e.g. Sort's "Boostable PP", still grows past
   the floor on its own -- this only removes the artificial minimum for the
   common short cases). */
.controls-row .csel-wrap { position: relative; display: inline-block; min-width: 4.5rem; }
.csel-trigger { display: flex; align-items: center; width: 100%; min-width: 4.5rem; text-align: left; position: relative; }

.csel-trigger::after { content: none; }

.csel-list {
  position: absolute;
  left: 0;
  /* Popup sizes to its OWN content instead of the trigger's width (board-polish
     batch 19 item 3, Florian: "Allow them to show the full content width when
     the open dropdown is visible... ok to have a wider width for the dropdown
     than for the actual dropdown field"). The old `right: 0` forced the list to
     match `.csel-wrap`'s width exactly -- since the wrap sizes to the trigger's
     SELECTED-value text (e.g. "harvest pp"), any longer option (e.g. Sort's
     "plot dec efficiency", 19 chars) had nowhere to go but truncate/overflow.
     Ported from Asdroid's own solved case for this (frontend/assets/styles.css
     `.lm-research-controls .csel-list`: `right: auto; min-width: 100%; width:
     max-content;`) rather than inventing a new mechanism -- `min-width: 100%`
     keeps the popup at least as wide as the trigger (so it never looks
     narrower than the field it belongs to), `width: max-content` grows it to
     the longest option's natural width. `max-width` guards against the popup
     running off the right edge of the viewport for a filter sitting near the
     row's edge. The available viewport is the only upper width bound. */
  min-width: 100%;
  width: max-content;
  max-width: calc(var(--ceres-vw) * 100 - 2rem);
  top: calc(100% + 2px);
  z-index: 30; /* above sticky table header (10) and sticky column headers (default stacking) */
  background: var(--bg2);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  list-style: none;
  margin: 0;
  padding: 0.2rem 0;
  max-height: 16rem;
  overflow-y: auto;
  overflow-x: hidden;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
  font-size: 0.85rem;
}
.csel-list[hidden] { display: none; }

.csel-option {
  display: flex;
  align-items: center;
  padding: 0.3rem 0.6rem;
  cursor: pointer;
  color: var(--text);
  white-space: nowrap;
}
.csel-option:hover,
.csel-option.is-focused { background: var(--bg3); color: var(--heading); }
.csel-option.is-selected { color: var(--accent); font-weight: 600; }
.csel-group-header {
  padding: 0.25rem 0.6rem;
  pointer-events: none;
  color: var(--meta);
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.csel-divider { height: 1px; background: var(--border); margin: 0.3rem 0.4rem; pointer-events: none; }
.csel-action { padding: 0.3rem 0.6rem; cursor: pointer; color: var(--accent); }
.csel-action:hover { background: var(--bg3); }
.csel-action[aria-disabled="true"] { opacity: 0.4; cursor: default; pointer-events: none; }

/* Option icon (board-polish batch 9, item 8: "the dropdown options
   themselves carry the board's visual language"). Rarity gets its gem,
   Worksite its resource icon, Type its plot-type mark, Terrain its emblem
   PLUS a full-row background fill below -- the same icon assets and terrain
   tokens the main table already uses (ceres/static/app.js's
   `initFilterDropdowns()`), never a second icon set. */
.csel-opt-icon { width: 1rem; height: 1rem; vertical-align: middle; margin-right: 0.4rem; flex: none; }
/* S-8020 (owner: "add a space between icon and text"): every option row and trigger keeps the same gap
   between its icon and its label, whichever icon class the decorator used (gems, castle, resource marks). */
.csel-option > img, .csel-trigger > img { margin-right: 0.4rem; flex: none; }

/* Terrain option row background fill -- reuses the SAME --terrain-<name>/
   --terrain-<name>-fg tokens as the main table's td.terrain-cell (the
   "Land data colors" block above), so a filter option's colour can never
   drift out of sync with the column it filters. Text-shadow matches the
   table cell treatment for the same legibility reason (item 7, uniform
   white text). */
/* S-7940: Region option rows wear the region's territory colour (the Plot column's own --territory-* tokens,
   Pristine Northwest with its accent border); "any" stays plain. */
.csel-option-territory { font-weight: 700; }
/* S-8030 ("Inside the dropdown keep all but remove the line"): the Pristine Northwest border drew a line between every
   white row; the list rows have no border now. The closed field's value chip (.region-value) keeps it, since a white
   chip on the light field would otherwise vanish. */
.csel-option-territory.territory-pristine-northwest { background: var(--territory-pristine-northwest); color: var(--territory-pristine-northwest-fg); }
.csel-trigger.region-filled.territory-pristine-northwest { border-color: var(--territory-pristine-northwest-accent); } /* S-8320: the white field keeps its accent edge */
.csel-option-territory.territory-broken-lands       { background: var(--territory-broken-lands);       color: var(--territory-broken-lands-fg); }
.csel-option-territory.territory-land-of-prosperity { background: var(--territory-land-of-prosperity); color: var(--territory-land-of-prosperity-fg); }
.csel-option-territory.territory-central-fire       { background: var(--territory-central-fire);       color: var(--territory-central-fire-fg); }
.csel-option-territory.territory-wild-northeast     { background: var(--territory-wild-northeast);     color: var(--territory-wild-northeast-fg); }
.csel-option-territory.territory-great-lowlands     { background: var(--territory-great-lowlands);     color: var(--territory-great-lowlands-fg); }
.csel-option-territory.territory-shimmering-coasts  { background: var(--territory-shimmering-coasts);  color: var(--territory-shimmering-coasts-fg); }
.csel-option-territory:is(.territory-broken-lands, .territory-land-of-prosperity, .territory-central-fire, .territory-wild-northeast, .territory-great-lowlands, .territory-shimmering-coasts) { text-shadow: 0 1px 2px color-mix(in srgb, var(--shadow) 85%, transparent), 0 0 4px color-mix(in srgb, var(--shadow) 55%, transparent); }
/* S-7940 fix (owner on v0.9.387: "Just the selected region inside the selection field shall be styled to the
   territory, not the outer filter boundaries"): the closed Region trigger keeps exactly the chrome of Rarity/Terrain
   (same border, background, padding, font); ONLY its selected value is styled. The label is plain.
   S-8030 ("use the correct background and text color AFTER a region was selected"): the coloured text alone was
   washed out on the light field, so the value is now a chip INSIDE the field -- `.region-value` wears the same
   `.csel-option-territory territory-<slug>` classes as its row in the list (same fill, same text colour, same
   text-shadow); the field chrome around it is untouched. "any" has no chip. */
/* S-8320: no chip any more -- the closed Region field itself takes the territory classes (fill, text colour,
   text-shadow), so the whole field is coloured; its size, border radius and padding stay those of Rarity/Terrain. */
/* S-8030 ("Add a hover effect"): a territory row keeps its fill under the pointer (the generic --bg3 hover would
   hide it); the row darkens a step and takes the house accent bar at its left edge (as .candidate-row-item:hover
   takes the accent border). A darkening wash works on every fill, white Pristine Northwest included. */
.csel-list .csel-option-territory:hover,
.csel-list .csel-option-territory.is-focused { box-shadow: inset 3px 0 0 var(--accent), inset 0 0 0 999px rgba(0, 0, 0, 0.14); }
.csel-option-terrain { color: #ffffff; text-shadow: 0 1px 2px color-mix(in srgb, var(--shadow) 85%, transparent), 0 0 4px color-mix(in srgb, var(--shadow) 55%, transparent); }
.csel-option-terrain.terrain-river     { background: var(--terrain-river); }
.csel-option-terrain.terrain-lake      { background: var(--terrain-lake); }
.csel-option-terrain.terrain-hills     { background: var(--terrain-hills); }
.csel-option-terrain.terrain-forest    { background: var(--terrain-forest); }
.csel-option-terrain.terrain-mountain  { background: var(--terrain-mountain); }
.csel-option-terrain.terrain-jungle    { background: var(--terrain-jungle); }
.csel-option-terrain.terrain-caldera   { background: var(--terrain-caldera); }
.csel-option-terrain.terrain-plains    { background: var(--terrain-plains); }
.csel-option-terrain.terrain-swamp     { background: var(--terrain-swamp); }
.csel-option-terrain.terrain-canyon    { background: var(--terrain-canyon); }
.csel-option-terrain.terrain-desert    { background: var(--terrain-desert); }
.csel-option-terrain.terrain-tundra    { background: var(--terrain-tundra); }
.csel-option-terrain.terrain-badlands  { background: var(--terrain-badlands); }
.csel-option-terrain.terrain-bog       { background: var(--terrain-bog); }
/* Hover/selected state on a coloured terrain option needs its own override --
   the generic `.csel-option:hover` background (var(--bg3)) would otherwise
   sit on TOP of the terrain fill and hide it; a semi-transparent white wash
   keeps the terrain colour visible underneath while still signalling focus. */
.csel-option-terrain:hover,
.csel-option-terrain.is-focused { background-blend-mode: lighten; box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.12); }
.csel-option-terrain.is-selected { color: #ffffff; box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.08); }
/* IR-9199 (owner: "allow multi-selections in dropdowns ceres wide") -- multi-select check mark.
   Only lists the component marks aria-multiselectable carry a .csel-check slot; it sits at the row's end so
   option text and icons keep their approved position, and takes the row's own text colour (the approved
   --accent on a plain selected option, white on terrain/territory fills). Hidden (but space-holding) while
   the option is not selected, so the list does not change width as options are toggled. */
.csel-list[aria-multiselectable="true"] .csel-check {
  flex: none; inline-size: .35rem; block-size: .65rem; margin-inline-start: auto; padding-inline-start: 0;
  margin-block-start: -.15rem; translate: -.1rem 0; border: solid currentColor; border-width: 0 2px 2px 0;
  rotate: 45deg; visibility: hidden; box-sizing: content-box;
}
.csel-list[aria-multiselectable="true"] .csel-option { gap: .9rem; }
.csel-list[aria-multiselectable="true"] .csel-option.is-selected .csel-check { visibility: visible; }
/* end IR-9199 */

/* ---------------------------------------------------------------------
   Custom tooltip (board-polish batch 9 continued, item 11).

   Florian: "the tooltips are hidden behind the mouse and should use a
   beautiful tooltip style above the cursor tip." Native `title` renders
   below-right of the pointer (the pointer covers it) and cannot be styled at
   all -- ceres/static/app.js:initTooltips()/tipAttrs() replace every former
   `title="..."` with a `data-tip` attribute this component reads, driving a
   single shared `#ceres-tooltip` element (created once, positioned above the
   hovered/focused element via JS, `position:fixed` so it can never add row
   height or shift table layout -- it isn't part of the table's own flow).
   Iris tokens throughout, same as every other Ceres chrome element; works
   in all four themes since --bg3/--heading are themed already.

   Contrast pass (Florian, 2026-08-28: "it is currently not very visible").
   The text itself was never the problem -- --heading on --bg2 already
   measured 6.06-10.11:1 across all four themes, comfortably above the 4.5:1
   AA floor. The box was: --bg2 sits close in luminance to the table rows it
   floats over, and the old border (--border-bright, a translucent tint at
   .40-.45 alpha) barely reads as an edge against a dark row. Moved to
   --bg3 (the tier this file already uses for other floating/attention
   surfaces, e.g. `.slot-hazard-inline`, `.checkout-chunk-warning`) and an
   opaque 2px --heading border, which measures >=6:1 against --bg3 in all
   four themes -- unlike --warn/--ok/--accent, which fall as low as 1.25:1
   on some themes and are therefore never used for this box's own edge, only
   for the severity stripe below. */
/* FR-1865: the arrow (::before/::after below) is a pseudo-element of THIS
   element and sits at top:100%/bottom:100% -- i.e. just past its own border
   box. `overflow: hidden` used to live here too, which clipped the arrow's
   own protruding half clean off (invisible or lopsided depending on
   sub-pixel rounding, which read as "not centred"). The clip/scroll-cap and
   the padded flex layout now live on the `.tip-inner` child instead, so this
   outer box stays unclipped and the arrow always renders in full. */
#ceres-tooltip {
  position: fixed;
  z-index: 1300; /* IR-5210: above the modal overlays (1200); a tooltip drawn behind them was a barely visible pill */
  pointer-events: auto;
  background: var(--bg3);
  color: var(--heading);
  border: 2px solid var(--heading);
  border-radius: var(--radius);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
  /* S-4290 item 1: "Tooltips may never be longer than there content and have no fixed
     width". The box is as wide as its content and wraps only at the cap. */
  width: max-content;
  max-width: var(--tooltip-available-width, calc(100vw - 24px));
  box-sizing: border-box;
  overflow-wrap: anywhere;
  flex-shrink: 0;
  white-space: normal;
  /* S-6580: appears with a short fade, a 4px rise and a slight grow out of its arrow -- quick, never bouncy. */
  opacity: 0;
  transform: translateY(4px) scale(.97);
  transform-origin: var(--ceres-tooltip-arrow-left, 50%) 100%;
  transition: opacity .14s ease-out, transform .18s cubic-bezier(.2, .85, .3, 1);
}
#ceres-tooltip[hidden] { display: none; }
/* IR-8477 (finishing it): with a mouse a tooltip must never block what lies under it -- no click, wheel or hover is caught by the tooltip, so content under a
   large tooltip stays usable and scrolls on. (Touch keeps the box catchable: a tap on it is how it is dismissed without activating what is beneath.) */
#ceres-tooltip { pointer-events: none; }
#ceres-tooltip.is-touch { pointer-events: auto; }
#ceres-tooltip.is-below:not(.is-visible) { transform: translateY(-4px) scale(.97); transform-origin: var(--ceres-tooltip-arrow-left, 50%) 0; }
#ceres-tooltip.is-side-right:not(.is-visible) { transform: translateX(-4px) scale(.97); transform-origin: 100% var(--ceres-tooltip-arrow-top, 50%); }
#ceres-tooltip.is-side-left:not(.is-visible) { transform: translateX(4px) scale(.97); transform-origin: 0 var(--ceres-tooltip-arrow-top, 50%); }
#ceres-tooltip.is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { #ceres-tooltip, #ceres-tooltip.is-below:not(.is-visible), #ceres-tooltip.is-side-right:not(.is-visible), #ceres-tooltip.is-side-left:not(.is-visible) { transform: none; transition: opacity .12s linear; } }
.tip-inner {
  padding: 0.45rem 0.6rem;
  max-height: var(--tooltip-available-height, calc(100dvh - 28px));
  box-sizing: border-box;
  /* Never cut content off silently (gate document, section 5 item 5): a tooltip taller
     than the viewport scrolls instead of clipping its last rows. */
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.28rem;
}
.tip-head { display: flex; align-items: center; gap: 0.38rem; min-width: 0; }
.tip-title { font-family: var(--font-body); font-size: 0.8rem; font-weight: 600; color: var(--heading); line-height: 1.3; }
#ceres-tooltip[data-layout="worker"] { max-width: var(--tooltip-available-width, calc(100vw - 24px)); }
#ceres-tooltip[data-layout="worker"] .tip-inner { padding: .85rem 1rem; gap: .75rem; }
#ceres-tooltip[data-layout="worker"] .tip-head { display: grid; gap: .4rem; padding-block-end: .65rem; border-block-end: 1px solid var(--border); }
#ceres-tooltip[data-layout="worker"] .tip-head > .tip-dot { display: none; }
#ceres-tooltip[data-layout="worker"] .tip-title { font-size: 1rem; line-height: 1.35; }
#ceres-tooltip[data-layout="worker"] :is(.tip-identity, .tip-level) { font-size: .8rem; line-height: 1.5; color: var(--text); }
#ceres-tooltip[data-layout="worker"] .tip-level { font-variant-numeric: tabular-nums; }
#ceres-tooltip[data-layout="worker"] .tip-items { grid-template-columns: minmax(min-content, 1fr) minmax(0, 1.3fr); gap: .45rem 1.25rem; }
#ceres-tooltip[data-layout="worker"] .tip-item-icon-cell { display: none; }
#ceres-tooltip[data-layout="worker"] .tip-item-name { padding: 0; }
#ceres-tooltip[data-layout="worker"] .tip-item-effect.is-line { grid-column: 1 / -1; }
#ceres-tooltip[data-layout="worker"] .tip-group { margin-block-start: .65rem; padding-block-start: .65rem; border-block-start: 1px solid var(--border); }
#ceres-tooltip[data-layout="worker"] .tip-group:first-child { margin: 0; padding: 0; border: 0; }
.tip-item-name.has-label-icon { display: inline-flex; align-items: baseline; gap: .35em; }
.tip-item-name.has-label-icon > span { min-width: 0; }
.tip-label-icon { inline-size: 1.1em; block-size: 1.1em; object-fit: contain; flex: 0 0 auto; align-self: center; }
.tip-body { font-size: 0.76rem; font-weight: 400; color: var(--text); line-height: 1.45; white-space: pre-line; }
.tip-foot { font-size: 0.72rem; color: var(--text); line-height: 1.4; border-top: 1px solid var(--border); padding-top: 0.3rem; margin-top: 0.05rem; }
.tip-key {
  display: block;
  font-family: var(--font-body);
  font-size: 0.92rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--heading);
  font-variant-numeric: tabular-nums;
  margin-bottom: 0.12rem;
}
.tip-icon { width: 14px; height: 14px; flex: 0 0 auto; object-fit: contain; }
.tip-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
#ceres-tooltip .tip-body:empty,
#ceres-tooltip .tip-foot:empty { display: none; }
#ceres-tooltip[data-depth="brief"] { width: max-content; }
#ceres-tooltip[data-depth="brief"] .tip-inner { padding: 0.3rem 0.5rem; }
#ceres-tooltip[data-depth="brief"] .tip-body { white-space: normal; }
/* S-4290 item 2: "When tabs split view is used, make sure all align perfectly
   vertically." Every row shares ONE grid -- icon | label | value -- so labels and values
   line up across the whole tooltip; a plain line spans label and value. */
.tip-items { display: grid; grid-template-columns: max-content fit-content(45%) minmax(0, 1fr); align-items: baseline; row-gap: 0.22rem; white-space: normal; }
.tip-item-icon-cell { display: inline-flex; align-self: start; margin-top: .12em; } /* IR-4630: icon aligns with the title line */
.tip-item-icon { width: 14px; height: 14px; flex: 0 0 auto; object-fit: contain; margin-right: 0.35rem; }
.tip-item-name { color: var(--text); white-space: normal; min-width: 0; padding-right: 0.7rem; line-height: 1.35; }
/* S-16430 (0.9.803): a short label like "Plot type" must never split mid-word -- the tooltip box's
   own overflow-wrap: anywhere (needed for long unbreakable values elsewhere) will otherwise break a
   short label at any character once the fit-content(45%) name column runs out of room, dropping the
   trailing letter onto its own line. white-space: nowrap overrides that for labels marked nowrap. */
.tip-item-name--nowrap { white-space: nowrap; }
.tip-item-effect { color: var(--heading); font-weight: 600; line-height: 1.35; font-variant-numeric: tabular-nums; white-space: pre-line; }
.tip-item-effect.is-line { grid-column: 2 / -1; color: var(--text); font-weight: 400; }
.tip-group { grid-column: 1 / -1; margin-top: 0.4rem; font-size: 0.65rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text); }
.tip-inline-icon { width: 13px; height: 13px; object-fit: contain; vertical-align: -2px; margin: 0 0.18em 0 0.1em; }
/* S-4360: the Dark Energy icon after each DEC stake figure in the plot table. */
.dec-stake-icon { width: 14px; height: 14px; object-fit: contain; vertical-align: -2px; margin-left: 0.25em; }
/* S-14420 review fix: the Wood/hr, Stone/hr, Iron/hr cell icons are NOT `.dec-stake-icon`
   (that class is a fixed, deliberately small 14px glyph for the DEC token) -- they join
   the S-14330 shared table icon clamp instead (see that selector list further down), so
   they read at the same size as every other icon in the row (20-22px), not a small badge.
   This base rule is only the pre-clamp fallback (matches .resource-icon/.totem-icon's own
   1.3rem default) in case the class is ever used outside the table's own clamp scope. */
.material-cost-icon { width: 1.3rem; height: 1.3rem; object-fit: contain; vertical-align: middle; margin-left: 0.25em; } /* review fix: -2px pushed cost rows past the frozen 36px */
/* Severity accent (FR: "tooltip should use warning or danger colors").
   `showTooltip()` mirrors the hovered element's `data-tip-severity` (set by
   `tipAttrs(text, severity)`) onto this element's own `data-severity`, so a
   tooltip's colour carries the same meaning as its `ceres/notices.py`
   severity. Left-border stripe only -- not the box's own outer border --
   because the severity tokens (--warn especially) don't clear the same
   contrast floor as --heading in every theme; a thin accent stripe next to
   an already-legible box is the same pattern `.ceres-notice-*`/
   `.worksite-banner.tone-*` already use elsewhere in this file. The
   background tint is a 10% color-mix, chosen because a full-strength severity
   fill would drag text contrast down in the lighter themes -- at 10% the
   heading-on-fill ratio barely moves (still >=6:1 measured across all four
   themes) while the box still visibly shifts hue. DANGER is reserved for
   genuinely destructive/irreversible actions (Clear plot, combine); CAUTION
   is for a tooltip that corrects a fear rather than describing a real risk
   (Sort by contribution) -- the two must never look the same. */
/* S-4290 item 4, his words: "Remove all weird border-left styles from some of the
   tooltips like PWR - unpowered." No stripe, no tinted box: severity is carried by the
   coloured dot in the header only (rules below). */
#ceres-tooltip[data-severity="danger"] .tip-dot { background: var(--danger); }
#ceres-tooltip[data-severity="caution"] .tip-dot { background: var(--warn); }
#ceres-tooltip[data-severity="success"] .tip-dot { background: var(--ok); }
#ceres-tooltip:not([data-severity]) .tip-dot { display: none; }
/* Two-triangle arrow (an outer border-color triangle plus a slightly smaller
   inner one matching the tooltip's own background) -- the same technique
   ceres.css already uses nowhere else, but a standard CSS-only tooltip-arrow
   trick: gives the arrow a visible 1px border edge instead of a flat cutout.
   Points DOWN at the cursor by default (tooltip sits above); `.is-below`
   flips both triangles to point UP when positionTooltip() had to place the
   tooltip below the target instead. Worker-card side placement relocates the
   same triangles horizontally and uses `--ceres-tooltip-arrow-top` to track
   the target's vertical centre. */
#ceres-tooltip::after,
#ceres-tooltip::before {
  content: "";
  position: absolute;
  left: var(--ceres-tooltip-arrow-left, 50%);
  width: 0;
  height: 0;
  border: 6px solid transparent;
  pointer-events: none;
}
#ceres-tooltip::after { top: 100%; transform: translateX(-50%); border-top-color: var(--heading); }
#ceres-tooltip::before { top: calc(100% - 2px); transform: translateX(-50%); border-top-color: var(--bg3); }
#ceres-tooltip.is-below::after { top: auto; bottom: 100%; border-top-color: transparent; border-bottom-color: var(--heading); }
#ceres-tooltip.is-below::before { top: auto; bottom: calc(100% - 2px); border-top-color: transparent; border-bottom-color: var(--bg3); }
#ceres-tooltip.is-side-right::after { left: 100%; top: var(--ceres-tooltip-arrow-top, 50%); transform: translateY(-50%); border-top-color: transparent; border-bottom-color: transparent; border-left-color: var(--heading); }
#ceres-tooltip.is-side-right::before { left: calc(100% - 2px); top: var(--ceres-tooltip-arrow-top, 50%); transform: translateY(-50%); border-top-color: transparent; border-bottom-color: transparent; border-left-color: var(--bg3); }
#ceres-tooltip.is-side-left::after { left: auto; right: 100%; top: var(--ceres-tooltip-arrow-top, 50%); transform: translateY(-50%); border-top-color: transparent; border-bottom-color: transparent; border-right-color: var(--heading); }
#ceres-tooltip.is-side-left::before { left: auto; right: calc(100% - 2px); top: var(--ceres-tooltip-arrow-top, 50%); transform: translateY(-50%); border-top-color: transparent; border-bottom-color: transparent; border-right-color: var(--bg3); }
@media (prefers-reduced-motion: reduce) {
  #ceres-tooltip { transition: none; }
}

/* Every element carrying `data-tip` gets a hint cursor -- the same signal a
   native `title` attribute gives for free, kept here since the native
   attribute itself is gone. Badges/icons already use `cursor: help` in a few
   places (`.dec-efficiency-flag`, `.totals-note`, the original `.badge`
   rule) -- this generalizes it to every tooltip-bearing element uniformly. */
[data-tip] { cursor: help; }

/* In-page slot editor. It inherits the expand row's width and page scrolling;
   no nested overflow or viewport-sized editor is allowed here.
   No padding/border/radius here any more (item 14, Florian: "it must sit at
   the upmost top left corner, hitting exactly the pixels from the row of the
   plots... zero padding or free space or any rounding"). This element used
   to carry `padding: 0.65rem 0.8rem 0.8rem` + `border-top: 1px` + a rounded
   `border-radius` -- combined with the parent `<td>`'s own inherited padding
   (fixed above) that was THE gap: measured live before this fix, the terrain
   photo's real top-left corner sat 20px right and ~15px down from the parent
   plot row's own edge, entirely explained by these two elements' padding/
   border stacked together (td: 4px/7.2px, this element: 10.4px top incl.
   border + 12.8px left). Readable-content padding moves to `.editor-main`
   below instead -- that keeps the TEXT away from the raw edges without
   affecting `.editor-scene`'s own sizing (`.editor-scene` is `.editor-main`'s
   sibling inside `.editor-band`, not its child, so `.editor-main`'s padding
   never insets it). */
.slot-editor { box-sizing: border-box; position: relative; width: 100%; min-width: 0; }
/* Scene background (terrain/worksite photo) lives on `.editor-band`, not the
   outer `.slot-editor` section (item 7 fix, board-polish, Florian: "opening
   the worker cards list breaks the background image, which must be fixed
   and never bounce around"). `.editor-band` wraps ONLY the fixed-size facts
   line + tile grid + footer -- the inline candidate picker and the op-review
   panel are `.slot-editor`'s own children, siblings of `.editor-band`, not
   inside it. Previously these `::before`/`::after` scene layers (inset:0,
   absolutely positioned) lived on `.slot-editor` itself, whose box height
   grows substantially the moment the picker opens (a whole candidate list
   stacking below); `inset:0` re-stretches the SAME background image to fill
   that new, taller box on every open/close, reading as the scene visibly
   resizing/"bouncing". Scoping the scene to `.editor-band` means its own box
   height never changes when the picker opens or closes, so the background
   never moves. */
/* Worksite geometry, shared by three otherwise-unrelated rules: the scene layer
   that paints the building, `.slot-grid`'s left reserve, and the Change Worksite
   button. Declaring it once here is what keeps them in register.
   `--worksite-w` is deliberately the SAME 9.4rem as `.slot-grid`'s default
   `--slot-h`, so the building stands exactly as tall as a worker/asset tile; it
   is written out rather than referencing `--slot-h`, because `.slot-grid.is-compact`
   redefines that variable and the building must not shrink with it.
   `--worksite-lift` raises the square's bottom edge clear of the footer so its
   base lines up with the tiles' base. */
.editor-band { display: block; position: relative; isolation: isolate; min-width: 0; --worksite-h: 9.4rem; --worksite-w: 22.9125rem; --worksite-x: 0.8rem; --worksite-lift: 3.175rem; --worksite-btn-w: 11rem; }
/* The scene's own `overflow: hidden` clip is scoped to `.editor-scene`
   (an absolutely-positioned, pure-decoration `<div>`, first child of
   `.editor-band`, `pointer-events: none`) rather than to `.editor-band`
   itself. That matters for item 5 (sticky buttons, see `.slot-review` /
   `.slot-group-controls` below): `position: sticky` on a DESCENDANT only
   sticks relative to `.table-scroll` (this board's real, established
   horizontal-scroll container) if nothing in between has `overflow` set to
   anything but `visible` -- an `overflow: hidden` on `.editor-band` itself
   (the earlier shape of this rule, inherited from `.slot-editor` pre-item-7)
   would have silently turned `.editor-band` into the sticky containing block
   instead, breaking the fix below without any visible error. `.editor-main`
   (the real, unclipped content -- facts/tiles/footer/buttons) is a plain
   sibling of `.editor-scene`, so nothing between it and `.table-scroll`
   constrains overflow. */
/* border-radius: 0 (item 14) -- this is the element that actually clips the
   visible photo (`overflow: hidden` + the radius), so the rounding Florian
   saw came from here, not from `.slot-editor`. Square corners now, flush to
   `.editor-band`'s own box (which, after the padding move above, is itself
   flush to the parent plot row's edges). */
.editor-scene { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.editor-scene::before, .editor-scene::after { content: ''; position: absolute; inset: 0; pointer-events: none; }
/* PHASE 1 PROBE (ceres-worksite-art-placement-investigation) -- the worksite overlay and
   the terrain photo are the SAME canvas geometry (2829x1080, byte-identical), painted to
   register 1:1. Both layers now share `background-size: 100% auto` and
   `background-position: 50% var(--scene-y)`, so the building sits exactly where the artist
   grounded it on the terrain, at full registered scale, instead of being scaled to the
   band's height and slid right. Tiles remain independent controls below the scene. */
/* The worksite art is a 1:1 SQUARE canvas (productionBuildingSquare/, 640x640,
   with a smaller building region bottom-centre anchored inside the canvas), while
   the terrain photo is still the wide 2829x1080 plate. The two layers therefore
   need DIFFERENT transforms -- `background-size: 100% auto` on a square renders it
   band-WIDTH tall (~1700px inside a ~270px band), which is what made the buildings
   vanish. The worksite gets a FIXED square edge (`--worksite-w`), so every plot
   renders its building at exactly the same size and the same x, whatever the art
   or the terrain. `--worksite-x` is the band's own left content padding, so the
   square starts flush with the footer's first control; `.worksite-change-btn`
   below claims the identical width and x, which is what centres it under the
   building without any magic number. The square's bottom sits `--worksite-lift`
   above the band's bottom edge so the building clears the footer row instead of
   sitting behind the button. */
.editor-scene::before { background-image: var(--scene-terrain, none); background-size: 100% auto; background-position: center var(--scene-y, 87%); background-repeat: no-repeat; }
/* S-5740 (FR2): the art layer spans the whole band (never clipped at the sides or bottom). Each image's
   BUILDING is placed, not the image: its centre (--art-cx) on the harvest box's centre (= worksite column
   centre) and its lower end (--art-base) exactly on the harvest box's top edge (0.6325rem above the footer
   line). Per-image values come from art.js WORKSITE_ART_GEOMETRY. Only the top may run behind the plot row.
   S-5960 (owner: "None of the buildings is allowed to touch the harvest box"): --art-base is now the artwork's
   lowest visible pixel, measured per image, and --art-gap keeps it .5rem above the box -- no building, ground or
   log pile is ever hidden behind the harvest box. */
.editor-worksite-art { --art-cx: .5; --art-base: .9; --art-h: 12rem; --art-l: 0; --art-t: 0; --art-r: 1; --art-ratio: 2.4375; --ws-scale: 1;
  /* IR-4710 zero-cutoff fit: the building centre (--ac) is fixed on the harvest box; the image height is the largest
     that keeps the artwork's outer box inside the allowed box on all four sides, preserving the image proportions. */
  --ac: calc(var(--worksite-x) + var(--worksite-w) / 2);
  --abox-top: calc(var(--eos-facts, .45rem) + var(--eos-controls, 1.6rem) + var(--eos-cards, 12.45rem) - 0.6325rem - var(--art-gap, .5rem));
  --art-fit: min(
    calc((var(--ac) - var(--eos-affinity-size, 1.5rem) - 1.05rem) / ((var(--art-cx) - var(--art-l)) * var(--art-ratio))),
    calc(var(--ws-scale) * (var(--worksite-x) + var(--worksite-w) + .8rem - var(--ac)) / ((var(--art-r) - var(--art-cx)) * var(--art-ratio))),
    calc((var(--abox-top) - var(--eos-facts, .45rem) - var(--eos-controls, 1.6rem) - .25rem) / (var(--art-base) - var(--art-t))));
  position: absolute; inset: 0; background-image: var(--scene-worksite, none); background-repeat: no-repeat; background-size: auto var(--art-fit); background-position: left calc(var(--ac) - var(--art-cx) * var(--art-fit) * var(--art-ratio)) top calc(var(--abox-top) - var(--art-base) * var(--art-fit)); pointer-events: none; }
/* Scale the terrain with the full plot band; a fixed Full HD width left 4K scenes partly unpainted. */
/* Top-to-bottom black ramp only -- this is what holds the header text's contrast
   (measured ~0.83 alpha where the facts line sits). The earlier LEFT-to-right ramp that
   used to sit alongside it (`linear-gradient(90deg, ...)`, darkening toward the right)
   is gone: no text sits in the 44%-76% band it darkened, so it wasn't protecting
   anything -- and with the building now registered large and left, it was darkest over
   plain asset tiles that need no contrast help at all, and lightest right where the
   building itself sits. */
/* S-5310: a small black-to-transparent fade along the bottom edge too, same black as the top scrim. */
/* S-6270 (owner: "the same black to transparent gradient we use on the bottom ... at the left, right and top. Remove the
   gradient on the top we currently use, it is too long"): one fade, the bottom's own depth (12% of the 296px band =
   2.22rem, its mid stop 5% = .925rem), on all four edges -- in rem so the sides are as deep as top and bottom. */
.editor-scene::after { inset: -2px; background-repeat: no-repeat; /* IR-4850: reach past every edge -- at a fractional band width a light 1px sliver showed at the right */ --edge-fade: rgba(0, 0, 0, 0.75) 0, rgba(0, 0, 0, 0.35) .925rem, rgba(0, 0, 0, 0) 2.22rem;
  background: linear-gradient(0deg, var(--edge-fade)), linear-gradient(180deg, var(--edge-fade)), linear-gradient(90deg, var(--edge-fade)), linear-gradient(270deg, var(--edge-fade)); }
.editor-band.no-scene .editor-scene::after { background: none; }
.editor-band > .editor-main { position: relative; z-index: 1; }
/* S-5840/S-5860 (owner: "I like the shadows. Implement them"): poster-card shadow, replacing S-5700's hard
   offset. It lives on the tile's WRAPPER (card cell, item slot, Runi/power frame): dimmed tiles carry a filter
   that would dim a shadow on the tile itself. Two layers -- a tight contact shade and a wide diffuse one pulled in
   by negative spread so it pools BELOW the card. The selected tile lifts straight up 6px. The tile row stops
   clipping below its tiles so the shadow can show. */
.editor-band .slot-grid { overflow: visible; }
.editor-band .slot-grid :is(.slot-tile-cell, .asset-slot, .eos-permanent-frame[data-reveal-group="power"]) {
  border-radius: var(--radius);
  box-shadow: 0 calc(2 * var(--eos-effect-unit)) calc(5 * var(--eos-effect-unit)) 0 color-mix(in srgb, var(--shadow) 38%, transparent),
              0 calc(14 * var(--eos-effect-unit)) calc(30 * var(--eos-effect-unit)) calc(-4 * var(--eos-effect-unit)) color-mix(in srgb, var(--shadow) 78%, transparent);
  transition: transform .2s ease, box-shadow .2s ease; }
.editor-band .slot-grid :is(.slot-tile-cell, .asset-slot, .eos-permanent-frame[data-reveal-group="power"]):has(.slot-tile.is-picker-focus) {
  transform: translateY(calc(-6 * var(--eos-effect-unit)));
  box-shadow: 0 calc(6 * var(--eos-effect-unit)) calc(10 * var(--eos-effect-unit)) 0 color-mix(in srgb, var(--shadow) 32%, transparent),
              0 calc(26 * var(--eos-effect-unit)) calc(44 * var(--eos-effect-unit)) calc(-6 * var(--eos-effect-unit)) color-mix(in srgb, var(--shadow) 88%, transparent); }
.editor-band .slot-grid :is(.slot-tile-cell, .asset-slot, .eos-permanent-frame[data-reveal-group="power"]):has(.slot-tile.is-empty) { box-shadow: none; }
/* Readable-content padding lives here now, not on `.slot-editor` (see that
   selector's own comment) -- same values it used to carry
   (0.65rem/0.8rem/0.8rem), so the text layout is visually unchanged even
   though the background photo behind it now reaches all four edges. */
.editor-main { display: flex; flex-direction: column; min-width: 0; padding: 0.65rem 0.8rem 0.8rem; box-sizing: border-box; }
/* R-167: Total Base PP now lives in the staged change-badge row.
   The standalone plaque and anchor are intentionally gone. */
.terrain-affinity, .terrain-affinity-scroll {
  --affinity-zero: var(--heading);
  --affinity-bonus: #74c98b;
  --affinity-penalty: #cf5555;
}
[data-theme="dark"] .terrain-affinity,
[data-theme="dark"] .terrain-affinity-scroll {
  --affinity-bonus: #7bd59a;
  --affinity-penalty: #d45b62;
}
[data-theme="light"] .terrain-affinity,
[data-theme="light"] .terrain-affinity-scroll {
  --affinity-bonus: #2a804a;
  --affinity-penalty: #b52a38;
}
[data-theme="wood"] .terrain-affinity,
[data-theme="wood"] .terrain-affinity-scroll {
  --affinity-bonus: #227a47;
  --affinity-penalty: #b32633;
}
.terrain-affinity-badge[data-affinity-state="favoured"] { color: var(--affinity-bonus); }
.terrain-affinity-badge[data-affinity-state="penalised"] { color: var(--affinity-penalty); }
.terrain-affinity { position: relative; display: flex; flex-wrap: nowrap; justify-content: center; gap: 0.25rem; inline-size: 12rem; min-inline-size: 12rem; block-size: 1.5rem; line-height: 1; }
/* S-5370 (RETIRED by IR-6870, owner: "plots show now two splinter rows. remove the old
   vertical one."): this vertical column at the band's left edge is no longer rendered on
   desktop (editor.js only emits `data-terrain-affinity="editor"` when `presentation` is
   absent, i.e. the mobile plot sheet's own render pass -- see the IR-6870 comment above
   `renderEditorBandContents`'s return). The rule below is kept, unused on desktop, only
   because the mobile sheet still briefly holds this exact markup before rows.js moves it
   into `.mobile-plot-affinity`; do not re-enable it for `.editor-main` without checking
   that move still happens first. */
/* S-175, his request on approving the feature: "it works really well on the dark
   themes and ok on the ligh designs. I think giving them a shadow can improve
   contrast further." A THIRD separation channel, on the axis the other two do not
   touch: colour carries meaning for normal vision, ring geometry carries it under
   greyscale and colour-blindness, and this separates the BADGE FROM ITS SURFACE --
   which is exactly what is weak on the pale grounds he graded as only "ok".
   `box-shadow` paints outside the border box without participating in layout, so
   the R-138 fixed band (`.editor-band` block-size) cannot be disturbed by it: zero
   added height, zero added width, by construction rather than by measurement.
   One soft dark shadow serves every theme -- it is near-invisible against the
   near-black grounds of alchemical/dark (--bg #0a0906 / #0E0B1A) and does the real
   work against light #F3F1FA and wood #EDE4CE, which is where he asked for it. */
.terrain-affinity-badge { position: relative; display: block; flex: 0 0 1.5rem; box-sizing: border-box; inline-size: 1.5rem; block-size: 1.5rem; border-radius: 50%; background: var(--bg); color: var(--heading); opacity: 1; forced-color-adjust: none; pointer-events: auto; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.34), 0 0 0 1px rgba(0, 0, 0, 0.10); }
/* Forced-colours mode paints its own system colours and a shadow there is noise. */
@media (forced-colors: active) { .terrain-affinity-badge, .slot-tile { box-shadow: none; } }
.terrain-affinity-badge:focus-visible { outline: 2px solid currentColor; outline-offset: 0; }
.terrain-affinity-badge::before, .terrain-affinity-badge::after { content: ''; position: absolute; inset: 0; border-radius: 50%; pointer-events: none; }
.terrain-affinity-badge::after { inset: 0.1875rem; background: var(--bg); }
.terrain-affinity-badge[data-affinity-state="favoured"]::before { border: 0.1875rem solid currentColor; }
.terrain-affinity-badge[data-affinity-state="penalised"]::before { padding: 0.1875rem; background: repeating-conic-gradient(currentColor 0 40deg, transparent 40deg 60deg); -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; }
.terrain-affinity-badge[data-affinity-state="unchanged"]::before { border: 0.0625rem solid currentColor; }
.terrain-affinity-badge[data-affinity-state="unknown"]::before { border: 0.0625rem dotted currentColor; }
.terrain-affinity-badge > .splinter-icon { position: absolute; inset: 0; z-index: 1; display: block; inline-size: 1rem; block-size: 1rem; margin: auto; object-fit: contain; opacity: 1; filter: none; mask: none; -webkit-mask: none; mix-blend-mode: normal; }
.candidate-worker-affinity { display: block; min-inline-size: 0; max-inline-size: 100%; block-size: 1.5rem; }
.terrain-affinity-scroll { position: relative; display: block; min-inline-size: 0; max-inline-size: 100%; block-size: 1.5rem; }
.terrain-affinity-viewport { position: relative; display: block; min-inline-size: 0; max-inline-size: 100%; block-size: 1.5rem; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; overscroll-behavior-x: contain; }
.terrain-affinity-viewport::-webkit-scrollbar { display: none; }
/* A keyboard user must SEE where focus is. The outline sits OUTSIDE the run
   (positive offset) so it cannot paint over a settled ring -- G7 permits an
   outline on exactly that condition, and removing the ring entirely was a
   narrowing nobody asked for. The edge highlight below remains, as the
   scroll affordance it was added to be. */
.terrain-affinity-viewport:focus-visible, .terrain-affinity-viewport:has(.terrain-affinity-badge:focus-visible) { outline: 2px solid var(--heading); outline-offset: 2px; }
.terrain-affinity-edge { position: absolute; top: 0.4375rem; inline-size: 0.25rem; block-size: 0.625rem; pointer-events: none; }
.terrain-affinity-edge.is-start { background: linear-gradient(90deg, color-mix(in srgb, var(--affinity-zero) 72%, transparent), transparent); }
.terrain-affinity-edge.is-end { background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--affinity-zero) 72%, transparent)); }
.terrain-affinity-viewport:focus-visible ~ .terrain-affinity-edge:not([hidden]) { background: var(--affinity-zero); }
.terrain-affinity-edge[hidden] { display: none; }
@media (forced-colors: active) {
  .terrain-affinity.terrain-affinity, .terrain-affinity-scroll.terrain-affinity-scroll { --affinity-zero: var(--heading); }
  .terrain-affinity-badge, .terrain-affinity-edge { forced-color-adjust: none; }
  .terrain-affinity-edge, .terrain-affinity-edge.is-start, .terrain-affinity-edge.is-end,
  .terrain-affinity-viewport:focus-visible ~ .terrain-affinity-edge:not([hidden]) { background: CanvasText; }
  .terrain-affinity-badge > .splinter-icon { forced-color-adjust: none; }
}
/* Per-card Base PP ghost grid. Its 6.7rem cell is pinned to `.slot-grid`'s
   `--slot-w` declaration above; the alignment term mirrors that grid's padding-left and
   subtracts the button's own right edge plus the footer gap. The themed surface stays on
   filled cells while allowing the terrain photo to read through. */
.editor-base-pp-row {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: baseline;
  gap: 1.05rem;
  margin-left: calc(var(--worksite-x) + var(--worksite-w) + 1.05rem
                   - ((var(--worksite-w) - var(--worksite-btn-w)) / 2 + var(--worksite-btn-w) + 1rem));
  white-space: nowrap;
}

.editor-base-pp-cell {
  box-sizing: border-box;
  flex: 0 0 6.7rem; /* Pinned to `.slot-grid`'s --slot-w: 6.7rem. */
  min-width: 0;
  border-radius: var(--ceres-pp-radius); /* S-4990/S-5020: the cards' corner shape. Not var(--radius): 11px in dark/light/wood turns this ~26px-tall cell into a pill. */
  text-align: center;
  color: var(--heading);
}
.editor-base-pp-cell.is-filled {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--ceres-pp-block-pad) 0.3rem;
  border: 1px solid var(--primary); /* S-4990: neutral terrain = primary */
  border-radius: var(--ceres-pp-radius);
  background: color-mix(in srgb, var(--primary) 15%, var(--bg3));
}
/* S-4990: terrain bonus = green (the penalty/cap-clipped danger plate follows below). */
.editor-base-pp-cell.is-filled.is-terrain-bonus {
  border-color: var(--ok);
  background: color-mix(in srgb, var(--ok) 15%, var(--bg3));
}
/* S-5000: an unpowered card's cell is red, whatever its terrain (placed after the bonus rule). */
.editor-base-pp-cell.is-filled.is-unpowered {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 15%, var(--bg3));
}
.slot-editor-footer .editor-base-pp-cell b {
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--heading);
  font-size: 1.08rem;
  font-weight: 750;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
/* IR-2620/IR-2700/IR-2900, his ruling: a terrain-penalised worker's figure is shown REDUCED and RED --
   "it must be colored red because it is reduced forcefully and potentially a misplacment
   on that plot (in that case it is delberate and the penalty accepted but nevertheless it
   must be marked loudly)". The mark is deliberately the SAME treatment as `is-cap-clipped`:
   both mean "this figure is smaller than the card's own number, and here is why", and one
   visual language for one meaning is why S-177 exists. It cannot be dismissed -- the flag is
   derived per render from `occupant.terrain_boost`, and there is no acknowledgement state on
   this path to clear it, which is exactly what "nevertheless" asks for.

   IR-2700: the closing brace above was MISSING and sat after these two rules instead, so
   Chromium parsed them as nested descendants of `.is-cap-clipped` and DROPPED them. The
   figure computed correctly and painted neutral for the whole life of v0.9.14. In v0.9.15,
   raw `--danger` over a translucent wash remained photo-dependent and could fall far below
   4.5:1. A red that cannot be read is hidden, so this opaque token-derived plate makes the
   measured contrast stable over every terrain photo while the adjusted figure stays red. */
.editor-base-pp-cell.is-filled.is-cap-clipped,
.editor-base-pp-cell.is-filled.is-terrain-penalised {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 15%, var(--bg3));
}
.slot-editor-footer .editor-base-pp-cell.is-cap-clipped b,
.slot-editor-footer .editor-base-pp-cell.is-terrain-penalised b {
  color: color-mix(in srgb, var(--danger) 65%, var(--hi));
  font-weight: 800;
}
[data-theme="light"] .slot-editor-footer .editor-base-pp-cell.is-cap-clipped b,
[data-theme="light"] .slot-editor-footer .editor-base-pp-cell.is-terrain-penalised b,
[data-theme="wood"] .slot-editor-footer .editor-base-pp-cell.is-cap-clipped b,
[data-theme="wood"] .slot-editor-footer .editor-base-pp-cell.is-terrain-penalised b {
  color: color-mix(in srgb, var(--danger) 55%, var(--shadow));
}
.slot-editor-footer .editor-base-pp-cell.is-cap-clipped b::before,
.slot-editor-footer .editor-base-pp-cell.is-terrain-penalised b::before {
  content: "\25BC\00A0";
  font-size: 0.72em;
  color: inherit;
}
.editor-base-pp-divider {
  box-sizing: border-box;
  flex: 0 0 1px;
  margin: 0 0.9rem;
}

/* IR-6500 (2026-09-20): shared desktop+mobile, formerly unscoped in mobile-plot.css --
   the mobile worker-tile boostable-PP badge's own terrain-penalised colour, same
   treatment as .slot-editor-footer's above; moved here so mobile-plot.css can require
   every one of its own selectors to be viewport-scoped without changing this. */
.mobile-plot-sheet .mobile-boostable-pp .editor-base-pp-cell.is-terrain-penalised b {
  color: color-mix(in srgb, var(--danger) 65%, var(--hi));
}
[data-theme="light"] .mobile-plot-sheet .mobile-boostable-pp .editor-base-pp-cell.is-terrain-penalised b,
[data-theme="wood"] .mobile-plot-sheet .mobile-boostable-pp .editor-base-pp-cell.is-terrain-penalised b {
  color: color-mix(in srgb, var(--danger) 55%, var(--shadow));
}

/* Instant per-plot change badges in `.editor-facts` (owner, 2026-09-01: "Any change done
   to the tiles must be reflected instantly in editor-facts with colorful badges for the
   exact changes"). This row sits on the same terrain photo, so an accent carried by text
   colour alone can't guarantee contrast. Positive/negative use `--ok`/`--danger`; an
   unresolvable change uses `--info` rather than a guessed number. Heading-text-vs-chip-
   background measured >=4.85:1 for all three accents in all four Iris themes.
   `flex-shrink: 0` on the badges, `flex-wrap: wrap` already on the parent `.editor-facts`
   -- badges reflow onto the row's own wrap behaviour rather than forcing horizontal
   overflow at 1920px. */
.editor-change-badges { display: inline-flex; align-items: flex-start; gap: 0.45rem; flex-wrap: nowrap; }
.editor-change-badge { /* S-7380: larger -- they were 0.66rem */
  display: inline-block;
  flex-shrink: 0;
  font-size: 0.9rem;
  font-weight: 800;
  border-radius: 0.3rem;
  padding: 0.12rem 0.6rem;
  color: var(--heading);
  text-shadow: none;
  cursor: help;
  font-variant-numeric: tabular-nums;
}
.editor-change-badge.is-positive { background: color-mix(in srgb, var(--ok) 15%, var(--bg3)); border: 1px solid var(--ok); }
.editor-change-badge.is-negative { background: color-mix(in srgb, var(--danger) 15%, var(--bg3)); border: 1px solid var(--danger); }
.editor-change-badge.is-unknown { background: color-mix(in srgb, var(--info) 15%, var(--bg3)); border: 1px solid var(--info); }
/* FR-1941: the production badge carries the plot's resource icon, sized to the badge text. */
.editor-change-production .resource-icon { block-size: 1.05em; inline-size: auto; vertical-align: -0.18em; margin-inline: 0.1em; filter: none; }

/* IR-6500 (2026-09-20): shared desktop+mobile, formerly unscoped in mobile-plot.css --
   the mobile plot sheet's own wrapper around .editor-change-badges (above); moved here
   so mobile-plot.css can require every one of its own selectors to be viewport-scoped
   without changing this. */
.mobile-plot-sheet .mobile-staged-changes {
  box-sizing: border-box;
  min-inline-size: 0;
  margin-block: .65rem;
  padding: .7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg2);
}
.mobile-plot-sheet .mobile-staged-changes h3 {
  margin: 0 0 .5rem;
  color: var(--heading);
  font-size: .9rem;
}
.mobile-plot-sheet .mobile-staged-changes .editor-change-badges {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  max-inline-size: 100%;
}
.mobile-plot-sheet .mobile-staged-changes .editor-change-badge { max-inline-size: 100%; white-space: normal; }
/* S-2420: one line, never a second -- the row's height is reserved for a later tile-badge redesign. */
.change-badge-cap { display: inline; white-space: nowrap; }
.slot-editor-deed { font-family: var(--font-mono); font-size: 0.66rem; overflow-wrap: anywhere; }
/* Worker-table status text renders only when the corresponding warning condition is
   actually true. */
.multiplier-breakdown, .cost-line { font-size: 0.69rem; }
.multiplier-breakdown { color: var(--text); }
.cost-line strong { color: var(--text); font-variant-numeric: tabular-nums; }
.slot-editor-body { display: flex; flex-direction: column; gap: 0.55rem; min-width: 0; }
/* LOCAL INVARIANT: `.slot-power-satellite` is one Power-slot affordance. R-89 adds
   `.slot-energized-satellite` as its display-only visual reuse on an Energized worker;
   both wrappers remain 36.8px and left 35.2px/top -18.4px relative to the tile. */
.slot-power { --slot-w: 6.7rem; --slot-h: 9.4rem; --power-satellite-size: 2.3rem; position: relative; flex: 0 0 var(--slot-w); width: var(--slot-w); height: var(--slot-h); min-width: 0; pointer-events: none; }
.slot-power-stage { position: relative; width: 100%; height: 100%; }
.slot-power-large { position: relative; z-index: 1; pointer-events: none; }
.slot-power-large > .slot-tile { pointer-events: auto; }
.slot-power-satellite, .slot-energized-satellite { position: absolute; top: calc(var(--power-satellite-size) / -2); left: calc((var(--slot-w) - 2.3rem) / 2); z-index: 2; pointer-events: none; }
.slot-energized-satellite { --power-satellite-size: 2.3rem; }
.power-satellite { position: relative; display: flex; align-items: center; justify-content: center; width: var(--power-satellite-size); height: var(--power-satellite-size); box-sizing: border-box; overflow: hidden; border: 1px solid var(--border-bright); border-radius: 999px; background: color-mix(in srgb, var(--bg3) 66%, transparent); color: var(--heading); pointer-events: auto; }
button.power-satellite { padding: 0; cursor: pointer; }
.power-satellite:hover, .power-satellite:focus-visible { border-color: var(--accent); }
.power-satellite:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.power-satellite.is-empty { border-style: dashed; }
.power-satellite-plus { font-size: 1.25rem; line-height: 1; }
.power-satellite-label { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
.power-satellite .slot-art { border-radius: 999px; }
/* LOCAL INVARIANT: Satellite art fills its box so its rendered center matches the Power-slot circle. */
.power-satellite .slot-art img { box-sizing: border-box; width: 100%; height: 100%; object-fit: contain; object-position: 50% 50%; padding: 0.18rem; }
.slot-energized-satellite .slot-art img { padding: 0; transform: scale(1.35); transform-origin: center; }
/* `pointer-events: none` -- same reasoning as `.slot-grid` above: `.slot-group`
   is a block-level `<section>` that always spans its full available width, so
   without this its own empty right-hand space (behind the revealed worksite art)
   would swallow clicks meant for it, exactly as `.slot-grid`'s did. */
.slot-group { position: relative; border: 0; border-radius: var(--radius); min-width: 0; pointer-events: none; }
/* Item 12 -- the old `.slot-group-head` (Workers label + live "4/5" count +
   "✓ powered by ..."/"⚠ not powered" status line) is GONE. Florian: "Table
   header for workers is redundant... I dont want to see that, remove the
   entire row completely. Build a new row instead only for filters, which are
   left aligned and have a strong contrast." `.slot-group-controls` below is
   that replacement row -- bulk-action buttons only, nothing else. The
   `filled`/`group.capacity` VALUES this header used to display are still
   computed in app.js's renderEditorGroup() (they drive each button's
   disabled state); only the "N/M" text rendering is gone. The unsatisfied-
   constraint message ("plot produces 0 PP...") is not lost either -- see
   `.slot-zero-state` further down, which already rendered it independently
   and is untouched.
   LEFT ALIGNED + `position: sticky; left:` (item 5's own established
   technique, same as `.slot-review` below) rather than plain `margin-left:
   auto`/static flow -- Florian's own words predicted this ("left-aligning
   them largely SOLVES [being hidden on scroll]"), but static left-alignment
   only solves it at the table's default (unscrolled) position; this
   `<section class="slot-editor">` still sits inside the wide, horizontally-
   scrolling `<td colspan="16">`, so a plain `left: 0` block would scroll
   OFF-SCREEN to the left the moment the table is scrolled right. Sticky-left
   keeps it glued to the visible left edge of `.table-scroll`'s viewport at
   any scroll position, both directions -- verified live (see CHANGELOG). */
.slot-group-controls { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.35rem; position: sticky; left: 0; z-index: 5; background: transparent; border-radius: 0; pointer-events: none; }
/* Visually hidden (sr-only clip, same technique as `.slot-stage-summary`) --
   the worker-capacity value app.js still renders here, kept for anything
   that reads it (including this project's own Playwright coverage) even
   though the header it used to sit in is gone (item 12). */
/* S-7690 (his choice: delayed spinner, every account): centred over the empty worker tiles while
   the plot's detail is pending; invisible for the first 400 ms so a fast open never flashes it. */
/* S-7690 fix (his screenshot: one spinner floating between tiles): a small spinner INSIDE every
   ghost tile (`.slot-tile` is position: relative), centred by its own box, so it can never land on
   a tile edge. No CSS delay: opacity 0 until rows.js sets `is-slow-open` on the expand row 400 ms
   into a still-pending load, then a short fade-in; a fast open never shows it at all. */
.slot-editor-body { position: relative; }
.slot-loading-spinner { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; opacity: 0; transition: opacity 180ms ease; z-index: 3; }
tr.expand-row.is-slow-open .slot-loading-spinner, .editor-band.is-slow-open .slot-loading-spinner { opacity: 1; }
.slot-loading-spinner[hidden] { display: none !important; } /* IR-5390: reconnecting/error text wins over the spinner (eos.js setStatus) */
.slot-loading-spinner .spinner { width: 1.6em; height: 1.6em; vertical-align: 0; }
@media (prefers-reduced-motion: reduce) { .slot-loading-spinner { transition: none; } }
.slot-group-count { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.slot-bulk { pointer-events: auto; }

/* S-8040 (superseded S-14970): empty worker tiles stay addable, drawn dimmed and hatched. */
.slot-tile.is-empty.is-no-power { opacity: 0.72; background-image: repeating-linear-gradient(135deg, transparent 0 9px, color-mix(in srgb, var(--danger) 12%, transparent) 9px 11px); }
.slot-tile.is-empty.is-no-power:hover { opacity: 1; }
/* `pointer-events: none` on the grid itself, `auto` back on `.slot-tile` below --
   `.slot-grid` is a block-level flex container, so its own box always spans the FULL
   width available to it (the row's tile count only changes how much of that box its
   CONTENT fills, via flex-wrap). Left at the default `auto`, the grid's own empty
   trailing space -- exactly the region the worksite overlay is shifted into, above --
   silently ate clicks meant for that revealed art with nothing ever visibly there to
   explain why. */
.slot-grid { --slot-w: 6.7rem; --slot-h: 9.4rem; display: flex; align-items: flex-start; flex-wrap: nowrap; gap: 1.05rem; padding: 1.25rem 0.6rem 0.35rem calc(var(--worksite-x) + var(--worksite-w) + 1.05rem); min-width: 0; overflow-x: auto; overflow-y: hidden; pointer-events: none; }
.slot-grid.is-compact { --slot-w: 3.25rem; --slot-h: 4.5rem; gap: 0.3rem; }
.slot-grid.is-textonly { --slot-w: 4.5rem; --slot-h: 1.25rem; gap: 0.3rem; }
.slot-grid-divider { flex: 0 0 1px; align-self: stretch; margin: 0 0.9rem; background: var(--border); }
.slot-tile { box-sizing: border-box; flex: 0 0 var(--slot-w); width: var(--slot-w); height: var(--slot-h); min-height: var(--slot-h); max-height: var(--slot-h); position: relative; display: flex; flex-direction: column; justify-content: flex-end; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--text); padding: 0; text-align: center; cursor: pointer; overflow: hidden; overflow-wrap: anywhere; transition: border-color 0.12s ease; pointer-events: auto;
  /* S-176, his request: "Give a similar box-shadow to .slot-tile, to make the cards
     pop out a little more against the background". Same idiom as the terrain badges
     (S-175), scaled for a much larger tile: these sit directly on the plot's terrain
     scene inside the R-138 reveal, carrying only a 1px border against artwork.
     Safe against the tile's own state marks: is-staged / is-cooling /
     is-pending-removal paint with `outline` at a NEGATIVE offset, i.e. inside the
     border box, so nothing outside it can collide with or dim them. `box-shadow`
     does not participate in layout, so --slot-w/--slot-h and the reveal's fixed
     band are untouched by construction. */
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.38), 0 0 0 1px rgba(0, 0, 0, 0.10); }
.slot-tile:hover { border-color: var(--accent); }
.slot-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.slot-tile.is-empty { align-items: center; justify-content: center; border-style: dashed; color: var(--meta); background: color-mix(in srgb, var(--bg3) 66%, transparent); }
.slot-tile.is-empty .plus { font-size: 1.25rem; line-height: 1; }
.slot-tile.is-empty .empty-label-top { margin-bottom: .3rem; } /* S-7160 */
.slot-tile.is-empty .empty-label { font-size: 0.64rem; white-space: nowrap; }
/* Item 17 -- zero Power Cores available. A `<div>`, not a `<button>` (no
   click semantics at all, so nothing can look interactive and silently do
   nothing) -- `cursor: not-allowed` and a dimmer `+` reinforce that
   visually; `opacity` on the whole tile so it clearly reads as a different
   state from a normal empty (clickable) slot. */
.slot-tile.is-empty.is-unavailable { cursor: not-allowed; opacity: 0.55; }
.slot-tile.is-empty.is-unavailable .plus { opacity: 0.35; }
.slot-tile.is-view-only { cursor: default; }
/* GREEN means "staged here, not yet signed" (Florian, 2026-09-03: "add a green coloring for
   freshly but yet not signed workers in a tile"), mirroring the red that marks an over-cap
   deduction. The amber `--warn` stays on the two states that are NOT this: `.is-pending-add`
   (already SIGNED, waiting for the game to confirm) and `.is-pending-removal` (a staged
   unstake). Outline only -- decorative, so the `--warn`/`--ok` text-contrast floor noted
   below does not apply. */
/* S-6480: a staged tile pulses like the "X Changes" badge, in the colour of its stage state -- green: can be signed
   now; red: not possible yet (died in Survival / land unstake cooldown); orange: staked on another plot (unstake
   first, then its cooldown). Replaces the dashed outlines. */
.slot-tile.is-stage-ready { --stage-tone: var(--ok); }
.slot-tile.is-stage-blocked, .power-satellite.is-power-required { --stage-tone: var(--danger); }
.slot-tile.is-stage-elsewhere { --stage-tone: var(--warn); }
/* S-6510: a slim 2px border (as before) whose colour pulses, and a glow layer drawn ABOVE the card art (a shadow
   under the art or outside the tile is covered or clipped -- measured: invisible on terrain). */
.slot-tile.is-stage-ready, .slot-tile.is-stage-blocked, .slot-tile.is-stage-elsewhere, .power-satellite.is-power-required { outline: 2px solid var(--stage-tone); outline-offset: -2px; animation: stage-tile-outline 1.6s ease-in-out infinite; }
.slot-tile.is-stage-ready::after, .slot-tile.is-stage-blocked::after, .slot-tile.is-stage-elsewhere::after, .power-satellite.is-power-required::after {
  content: ""; position: absolute; inset: 0; z-index: 2; border-radius: inherit; pointer-events: none;
  box-shadow: inset 0 0 calc(18 * var(--eos-effect-unit)) calc(5 * var(--eos-effect-unit)) var(--stage-tone), inset 0 0 0 calc(3 * var(--eos-effect-unit)) color-mix(in srgb, var(--stage-tone) 70%, transparent);
  opacity: .15; animation: stage-tile-glow 1.6s ease-in-out infinite;
}
@keyframes stage-tile-glow { 0%, 100% { opacity: .15; } 50% { opacity: .95; } }
@keyframes stage-tile-outline { 0%, 100% { outline-color: var(--stage-tone); } 50% { outline-color: color-mix(in srgb, var(--stage-tone) 55%, white); } }
@media (prefers-reduced-motion: reduce) {
  .slot-tile.is-stage-ready, .slot-tile.is-stage-blocked, .slot-tile.is-stage-elsewhere, .power-satellite.is-power-required { animation: none; }
  .slot-tile.is-stage-ready::after, .slot-tile.is-stage-blocked::after, .slot-tile.is-stage-elsewhere::after, .power-satellite.is-power-required::after { animation: none; opacity: .5; }
}
/* A staged candidate the server already marked not-ready (cooldown.ready === false --
   see checkoutBlockedCooldownEntries() in app.js) must read as visibly different from a
   normal staged move, not just carry `.is-staged`'s green outline -- `--danger` wins the
   cascade over `.is-staged` by sitting after it in source order, and stacks fine with
   `.is-pending-removal`/`.is-view-only` since none of those set `outline`. */
/* .is-cooling keeps its ⏳ flag; its outline now comes from the S-6480 stage state above. */
/* Was `color: var(--danger)` -- measured FAILING WCAG AA against `--bg3` in multiple
   themes (`--danger`/`--bg3` computes 3.6-4.9:1 depending on theme, below the 4.5 floor
   in half of them). `--heading` keeps the glyph itself legible everywhere (matches the
   base `.slot-flag` pairing above); `--danger` still marks this specific flag as the
   more severe (cooling, not just solo) variant via its own border colour. */
.slot-flag.slot-flag-cooldown { top: 3px; left: 3px; color: #fff; border-color: rgba(255,255,255,.9); } /* S-6140: top-left corner, free since the sole-source flag went */
.slot-tile.is-unpowered .slot-flag.slot-flag-cooldown { top: 25px; } /* the ⚡ unpowered flag keeps the corner; ⏳ sits below it */
/* Restore-tile affordance (2026-08-25): a slot emptied by a staged removal.
   Must read as visibly different from both a live occupant tile and a plain
   empty "add" tile BEFORE the click -- dashed `--warn` outline borrows the
   same staged-removal colour `.is-staged` already uses elsewhere. The art
   (when present) is dimmed and desaturated, and the name gets a strikethrough,
   while the tile tooltip explains that clicking cancels the staged removal. */
.slot-tile.is-pending-removal:not(.is-stage-ready) { outline: 2px solid var(--warn); outline-offset: -2px; } /* S-6500: a staged unstake is green (the S-6480 rule); only an unstake already signed and awaiting the game stays amber */
.slot-tile.is-pending-add { border-style: dashed; outline: 2px solid var(--warn); outline-offset: -2px; }
.slot-tile.is-pending-removal .slot-art img { opacity: 0.45; filter: grayscale(60%); }
.slot-tile.is-pending-removal:not(.has-slot-art) { background: var(--bg3); color: var(--heading); }
.slot-tile.is-pending-removal .slot-name { text-decoration: line-through; }
.slot-art { position: absolute; inset: 0; display: block; border-radius: inherit; overflow: hidden; pointer-events: none; }
.slot-art img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 12%; }
.eos-portrait-fill { transform-origin: 50% 50%; opacity: 1; transform: scale(1); }
/* S-4810: during the reveal a tile picture may have no src yet (managed art) or still be
   loading -- the browser then paints its alt text ("… card art", "… icon") and a
   broken-image glyph inside the dark placeholder. Keep a src-less picture invisible
   until its src is set, and never draw the alt text; the pop-in runs once it is ready. */
.eos-portrait-fill:not([src]) { visibility: hidden; }
/* IR-4160: never a black tile. While its picture loads, a tile looks exactly like an
   empty slot (the `.slot-tile.is-empty` look, without the name); the caption and tile
   fill fade in together with the picture (420ms, matching the picture fade). */
.slot-tile.is-art-pending { background: color-mix(in srgb, var(--bg3) 66%, transparent); border-style: dashed; }
.slot-tile .slot-caption { transition: opacity 420ms cubic-bezier(0.25, 0.1, 0.25, 1); }
/* S-4880b: a freshly picked card loads directly (no reveal queue) and fades in once the
   picture has loaded -- editor.js handleCardImageLoad adds `is-loaded`. */
/* editor.js drives the fade frame by frame (inline opacity) so a busy page still paints it
   whole and a redraw mid-fade continues it; `is-pick-done` marks it finished. */
.eos-portrait-fill.pick-fade { opacity: 0; }
.eos-portrait-fill.pick-fade.is-pick-done { opacity: 1; }
.slot-tile.is-caption-pending .slot-caption { opacity: 0; } /* IR-4160a: removed at fade start; the tile look (is-art-pending) at fade end */
.eos-portrait-fill { color: transparent; }
/* S-4830: a soft fade-in, not a pop -- the owner found the scale bounce wrong for images
   that load fast. Opacity only, gentle ease-out; the class name is kept so eos.js and the
   reduced-motion override stay unchanged. */
.eos-portrait-fill.eos-portrait-pop-in {
  animation-name: eos-portrait-pop-opacity;
  animation-duration: 420ms;
  animation-delay: 0ms;
  animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
  animation-iteration-count: 1;
  animation-fill-mode: both;
}
@keyframes eos-portrait-pop-opacity {
  0% { opacity: 0; }
  100% { opacity: 1; }
}
.slot-tile-item .slot-art img { box-sizing: border-box; object-fit: contain; object-position: 50% 50%; padding: 0.35rem; }
/* S-5130: totem and title art sits near the top border (with a little padding) and fully above
   the 52px name area, instead of centred across the whole tile and half hidden behind the name. */
/* Scoped under .slot-tile-item: the general item rule above (.slot-tile-item .slot-art img) is more
   specific than a bare .slot-art-totem img, so v0.9.118/0.9.119 never took effect. */
/* S-5320: same size, nudged down so the art sits centred between the top border and the name's
   letters (measured: totem 10.6px above / 11.0px below; title art 18.5 / 17.0 with its own margin). */
.slot-tile-item .slot-art-totem img, .slot-tile-item .slot-art-title img, .slot-tile-item .slot-art-power_core img { object-position: 50% 0; padding: 0.66rem 0.35rem calc(52px + 0.44rem); } /* S-7410: the large Power Core like totem and title */
.slot-caption { position: absolute; z-index: 1; left: 0; right: 0; bottom: 0; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; width: 100%; height: 52px; box-sizing: border-box; padding: 2.6px 0.35rem 0; overflow: hidden; background: none; backdrop-filter: none; text-align: center; }
.slot-tile.has-slot-art .slot-name, .slot-tile.has-slot-art .slot-meta, .slot-tile.has-slot-art .slot-badges, .changes-panel .ch-lock-plan .slot-name { margin-left: 0; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.9); }
.slot-tile.no-slot-art { background: radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--accent) 32%, transparent) 0%, transparent 62%), color-mix(in srgb, var(--bg3) 66%, transparent); }
.slot-tile-item.has-slot-art { background: color-mix(in srgb, var(--bg3) 66%, transparent); }
.slot-crest { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 2.6rem; line-height: 1; opacity: 1; pointer-events: none; }
.power-satellite.is-empty { background: color-mix(in srgb, var(--bg3) 66%, transparent); }
/* top-LEFT (item 24, Florian: "The X button for plot assets is now top left
   but should be top right" -- a direct regression from item 8, which moved
   `.slot-remove` here to dodge this same flag; swapped instead of undoing
   item 8's overflow:hidden clipping fix, see `.slot-remove` below). */
/* Corner marks are fixed-size, high-contrast controls so they stay legible over any art. */
.slot-flag { position: absolute; top: 3px; left: 3px; z-index: 2; display: inline-flex; align-items: center; justify-content: center; width: 19px; height: 19px; box-sizing: border-box; border-radius: 50%; background: rgba(0,0,0,.62); color: #fff; border: 1px solid rgba(255,255,255,.9); font-size: 13px; font-weight: 800; line-height: 1; pointer-events: auto; }
.slot-remove { position: absolute; top: 3px; right: 3px; z-index: 5; width: 19px; box-sizing: border-box; }
.slot-zero-state { padding: 0.9rem 0.7rem; background: var(--bg3); color: var(--heading); font-size: 0.8rem; }
/* Totem/Title/Power Core tiles now match the worker tile's own top edge and
   height exactly (item 1, Florian: "make sure all images are perfectly
   aligned in the horizontal, which is needed for totem, title and power
   core"). Previously `.asset-slot` was a column flexbox (label, then tile,
   stacked) with its OWN shorter --slot-h -- the label consumed real flow
   height above the tile, so an item tile's top edge (and therefore its
   image) sat lower than a worker tile's, which has no label at all and
   starts flush with the row. Fixed by taking the label out of flow
   (absolutely positioned above the tile) and matching --slot-w/--slot-h to
   the worker tile's own (inherited from `.slot-grid`, no longer
   overridden) -- both tile kinds are now the same box, at the same Y, in
   the same flex row. */
.asset-slot { position: relative; display: block; flex: 0 1 auto; min-width: 0; }
.slot-grid.is-textonly .slot-name, .slot-grid.is-textonly .slot-badges { display: none; }
.slot-grid.is-textonly .slot-caption { width: 100%; align-items: center; padding: 0.1rem 0.3rem; background: none; }
.slot-grid.is-textonly .slot-meta { color: var(--text); font-size: 0.6rem; line-height: 1; white-space: nowrap; }
.slot-name { display: -webkit-box; width: 100%; max-height: 29.4px; overflow: hidden; overflow-wrap: normal; word-break: keep-all; hyphens: none; color: #fff; font-size: 12.8px; font-weight: 700; line-height: 14.7px; text-overflow: ellipsis; text-shadow: 0 1px 2px rgba(0,0,0,.9); -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.slot-caption--long .slot-name { font-size: 11.5px; line-height: 13.2px; }
.slot-meta, .slot-badges { color: var(--text); font-size: 0.65rem; font-variant-numeric: tabular-nums; }
/* Worker Base PP colour, keyed to `terrain_boost`'s own sign -- see app.js's
   renderEditorTile() comment for why this signal and not an invented one. */
.slot-pp.is-terrain-gain { color: var(--ok); font-weight: 700; }
.slot-pp.is-terrain-loss { color: var(--danger); font-weight: 700; }
/* Item 4 -- Land/Verico ability icons on the holder's own tile. Reuses the
   worker sub-table's existing `.ability-icon` asset/tooltip, sized down for
   this small a caption. */
.slot-badges { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slot-abilities { display: none; }
/* IR-9159 (owner: the Toil and Kin and Companion badges belong on the tile, "on the bottom of the tile", in Echo's style --
   EchoReapers worksite-tiles.css .tc-badges/.tc-badge, worksite-abilities.css ER-1938): the ability icons and ONE labelled
   pill per card (holder "Toil & Kin +30%" / "Toil & Kin: Solo", companion "Companion") sit INSIDE the tile in a
   bottom-anchored `.slot-foot`, 3px above the 52px name strip. editor.js renders no foot on a tile wearing the centred
   waiting / planned badge (.slot-wait-badge), so the two never meet. The Energized / six-ability arcs keep their place. */
.slot-foot { position: absolute; left: 0; right: 0; bottom: calc(52px + 3px); z-index: 3; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 3px; padding: 0 3px; box-sizing: border-box; pointer-events: none; }
.slot-ability-row { position: static; display: flex; align-items: flex-end; justify-content: center; block-size: 24px; max-width: 100%; pointer-events: none; }
.slot-kin-badges { display: flex; flex-direction: column; align-items: center; gap: 2px; max-width: 100%; min-width: 0; pointer-events: none; }
.slot-kin-badge { display: inline-flex; align-items: center; gap: 3px; max-width: 100%; min-height: 18px; box-sizing: border-box; padding: 1px 4px; border: 1px solid var(--ok); border-radius: 4px; background: color-mix(in srgb, var(--ok) 14%, var(--bg2)); color: var(--ok); font-size: 10px; font-weight: 600; line-height: 1.2; letter-spacing: -0.015em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: none; pointer-events: auto; cursor: help; }
.slot-kin-badge.is-solo { border-color: var(--warn); color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, var(--bg2)); }
.slot-kin-badge .slot-kin-icon { inline-size: 12px; block-size: 12px; flex: none; object-fit: contain; }
.slot-kin-badge .slot-kin-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.slot-tile.is-pending-removal .slot-foot { opacity: .55; }
.slot-ability-row.is-split { justify-content: space-between; padding: 0 2px; }
.slot-ability-lane { display: inline-flex; align-items: flex-end; justify-content: center; gap: 2px; min-width: 0; pointer-events: none; }
.slot-ability-row.is-split .slot-ability-lane { flex: 0 0 22px; }
.slot-ability-item { display: inline-flex; align-items: flex-end; justify-content: center; width: 24px; height: 24px; pointer-events: auto; }
.slot-ability-row.is-split .slot-ability-item { width: 22px; }
.slot-ability-item .ability-icon { display: block; width: 22px; height: 22px; margin: 0; object-fit: contain; }
.slot-ability-item > span { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; overflow: hidden; font-size: .48rem; line-height: 1; pointer-events: auto; }
.slot-ability-row.is-centred { overflow: visible; }
.slot-ability-row.is-centred .slot-ability-lane { flex: none; gap: 0; } /* S-6050: his value */
.slot-ability-row.is-centred .slot-ability-item { width: 22px; } /* S-6050: icon boxes = icon size, so icons touch */
/* S-6020: Energized worker -- ability icons on an arc around the satellite, centred on the tile's top edge. */
.slot-ability-arc { position: absolute; left: 50%; top: 0; z-index: 4; inline-size: 0; block-size: 0; pointer-events: none; }
.slot-ability-arc.is-vov { top: 6px; } /* S-6120: the arch's ends rest on the tile's top edge */
/* S-6560 (owner ruling "More room, six-ability plots"): the arch covered the tile's top 17px with only 4px left under the
   plot controls. A plot holding a six-ability card gets 1.5rem (24px) more room above its cards; the arch keeps its
   shape and lifts 19px so its ends clear the tile. Every other plot is unchanged. */
.editor-band:has(.slot-ability-arc.is-vov) { --eos-cards: 13.95rem; block-size: calc(var(--eos-band-height, 18.5125rem) + 1.5rem); min-block-size: calc(var(--eos-band-height, 18.5125rem) + 1.5rem); }
.editor-band .slot-grid:has(.slot-ability-arc.is-vov) { padding-top: 4rem; }
.editor-band .slot-grid .slot-ability-arc.is-vov { top: -13px; }
.slot-ability-arc .slot-ability-item { position: absolute; left: calc(var(--ax) * var(--arc-scale, 1)); top: var(--ay); width: 22px; height: 22px; transform: translate(-50%, -50%); align-items: center; }
.slot-ability-overflow { display: inline-flex; align-items: center; justify-content: center; width: 22px; box-sizing: border-box; pointer-events: auto; }

/* Card-level BLOODLINE marking (2026-08-25, v0.5.20 follow-up -- see
   bloodlineCardBadgeHtml()'s own comment in app.js for why this exists
   alongside the plot-level Badges-column chip and the tiny per-icon outline).
   Solid background rather than a bare `color:` on the two badge classes
   below -- a worker tile's own background is a photographic card image, so
   a text-colour-only badge would
   have unpredictable contrast per-card; a solid pill is legible regardless of
   the art underneath it. Uses the SAME `--bg3`/`--heading` pairing CLAUDE.md
   documents as the one combo proven >=4.5:1 across all four Iris themes
   (`.slot-hazard-inline` and `.plot-warning-row` rely on it) -- only
   the border colour differs between paired/solo, which is decorative and
   doesn't carry the text-contrast requirement.
   SOLO is a warning (Florian, 2026-08-26, supersedes the 2026-08-24 neutral
   ruling above): "is by default a warning badge to me... a clear underuse of
   potential." Border goes `--warn`, the same token every other warning badge
   on this board uses (badge-underfilled, badge-unpowered) -- not a new
   colour vocabulary, just the existing warning one applied here too. */
.slot-bloodline-badge { display: inline-flex; align-items: center; margin-bottom: 0.15rem; padding: 0.02rem 0.4rem; border-radius: 999px; background: var(--bg3); color: var(--heading); border: 1px solid var(--ok); font-size: 0.58rem; font-weight: 700; letter-spacing: 0.03em; line-height: 1.3; white-space: nowrap; cursor: help; }
/* Solid, not dashed (2026-08-26 consistency fix): the plot-level SOLO chip is
   `badge-problem` (solid), so the card-level pill must read the same state the
   same way -- dashed is reserved for a neutral property, and SOLO is no longer
   one. */
.slot-bloodline-badge.is-solo { border-color: var(--warn); }
/* The SOLO badge is a real <button> on the Worker group (the "find a companion"
   affordance) -- reset UA button chrome so it still reads as the same pill,
   and use a pointer cursor since it's now actionable, not just a tooltip. */
button.slot-bloodline-badge { appearance: none; font-family: inherit; cursor: pointer; }
button.slot-bloodline-badge:hover, button.slot-bloodline-badge:focus-visible { border-color: var(--accent); outline: none; }
/* COMPANION badge (2026-08-25 follow-up) -- the non-holder card that merely
   shares the holder's sub_type on this plot. Same proven bg3/heading pill so
   contrast holds in every theme; a dotted border (distinct from PAIRED's and
   SOLO's solid borders) marks it as a third, different role -- this card
   SATISFIES a bloodline, it does not carry one. */
.slot-bloodline-badge.is-companion { border-style: dotted; border-color: var(--accent); }

/* The reserved row height keeps `.editor-band` stable when notices and staged operations
   appear or disappear, so the scene never re-crops. */
.slot-editor-footer { position: relative; display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.35rem 1rem; margin-top: 0.1rem; padding-top: 0.1rem; font-size: 0.8rem; min-height: 1.7625rem; } /* S-5750: no border-top (owner) */
.slot-editor-footer b { padding: 0 0.2rem; border-radius: var(--radius); background: var(--bg3); color: var(--heading); font-variant-numeric: tabular-nums; }
/* Item 10 -- Florian: "'N moves staged' and '+XYZ PP (modelled)' seem broken
   and should be removed entirely." Hidden rather than deleted from the
   markup: the underlying CERES_STAGE state/delta math is untouched (the
   portfolio checkout panel still depends on it). Standard "visually hidden"
   (sr-only) clip, NOT `display: none`/`visibility: hidden` -- both of those
   remove the text from the browser's own `innerText` (confirmed live: doing
   that broke tests/ceres/test_live_checkout.py, which asserts against a row's
   rendered inner_text() to know a staged move cleared). Clipping to a 1px box
   keeps the node genuinely rendered (in scope for innerText / copy-paste /
   this project's own Playwright coverage) while making it truly invisible on
   screen -- nothing left for Florian to see. */
.slot-stage-summary { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* Item 19 -- the server's own `/op-preview` hazards (op_preview.py), plain
   visible text, no gate/modal (Florian: "A warning is ok but never a blocker
   like this"). Same solid `--bg3`/`--heading` pairing as item 12's control
   row (proven 6.06:1-9.16:1 across all four Iris themes, computed against
   canonical.css's real values) -- `color: var(--warn)` alone was tried first
   and REJECTED: `--warn` text against this board's own `--bg` computes to
   only 1.84:1 on the light theme and 3.6:1 on wood, both under the 4.5:1
   target. `--warn` is kept as a
   decorative border only, which does not need text-contrast compliance. */
.slot-hazard-inline { display: inline-block; background: var(--bg3); color: var(--heading); border: 1px solid var(--warn); border-radius: var(--radius); padding: 0.25rem 0.55rem; font-size: 0.72rem; font-weight: 600; }
.slot-review { position: sticky; left: 1.4rem; z-index: 5; }

.slot-keychain-warning { background: var(--bg3); color: var(--heading); font-size: 0.78rem; }
.op-preview-review { margin-top: 0.45rem; padding: 0.45rem; border: 1px solid var(--accent); border-radius: var(--radius); background: var(--bg3); }
.op-preview-review.is-error { background: var(--bg3); color: var(--heading); }
.op-preview-review h4 { margin: 0 0 0.15rem; font-size: 0.82rem; }
.op-preview-review > p { margin: 0 0 0.3rem; font-size: 0.72rem; }
.op-preview-review pre { max-height: 18rem; overflow: auto; white-space: pre-wrap; font-size: 0.72rem; }
.op-preview-review .op-states { display: grid; gap: 0.2rem; margin-top: 0.35rem; font-size: 0.72rem; }
.op-preview-review .checkout-chunk-warning { margin: 0.3rem 0; }
.op-review-chunks { display: grid; gap: 0.3rem; }
.op-review-chunk { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }
.op-review-chunk > header { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; padding: 0.25rem 0.4rem; border-bottom: 1px solid var(--border); color: var(--text-dim); font-size: 0.68rem; }
.op-review-chunk > header strong { color: var(--heading); font-size: 0.72rem; }
.op-review-chunk > .op-review-ops { display: grid; gap: 0.1rem; margin: 0; padding: 0.2rem 0.4rem 0.2rem 1.9rem; }
.op-review-op { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: baseline; gap: 0.35rem; min-width: 0; line-height: 1.2; font-size: 0.72rem; }
.op-review-op-number { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.op-review-op-meaning { color: var(--heading); min-width: 0; }
.op-review-auth { color: var(--ok); font-size: 0.66rem; white-space: nowrap; }
.op-preview-review > .op-review-ops { margin-top: 0.3rem; }
.op-preview-review > .op-review-ops summary { color: var(--text-dim); cursor: pointer; font-size: 0.68rem; }
.op-preview-review > .op-review-ops pre { margin: 0.25rem 0 0; }
.op-preview-review > .slot-review { margin-top: 0.35rem; }
/* S-6000: one-step checkout -- the folded preview and its single sign button. */
.op-preview-review.is-compact { margin: 0 .55rem .6rem; padding: .65rem .75rem; border: 1px solid var(--border-bright); border-radius: var(--radius); background: color-mix(in srgb, var(--bg2) 70%, var(--bg3)); }
.op-preview-review.is-compact.is-error { border-color: color-mix(in srgb, var(--danger) 55%, var(--border)); }
.op-sign-row { display: flex; align-items: center; justify-content: space-between; gap: .8rem; }
.op-sign-summary { display: flex; flex-direction: column; gap: .15rem; min-width: 0; font-size: .72rem; color: var(--text-dim); }
.op-sign-summary strong { color: var(--heading); font-size: .82rem; }
.op-sign-summary:has(.spinner) { flex-direction: row; align-items: center; gap: .5rem; color: var(--body); }
.op-sign-btn { flex: none; }

.op-c-note { margin: .45rem 0 0; font-size: .7rem; color: var(--body); }
.op-c-note.is-warn { color: var(--warn); }
.op-c-note.is-bad { color: var(--danger); }
.op-c-details { margin-top: .5rem; }
.op-c-details summary { color: var(--text-dim); cursor: pointer; font-size: .68rem; }
.op-c-ops { margin: .35rem 0 0; padding: 0; list-style: none; display: grid; gap: .2rem; }
.op-c-ops li { display: grid; grid-template-columns: auto 1fr auto; gap: .5rem; padding: .3rem .45rem; border-radius: 6px; background: var(--bg3); font-size: .72rem; }
.op-c-tx { color: var(--text-dim); }
.op-c-meaning { color: var(--heading); }
.op-c-auth { color: var(--ok); font-size: .66rem; }
.op-c-raw { margin-top: .35rem; }
.op-c-raw summary { color: var(--text-dim); cursor: pointer; font-size: .64rem; }
.op-c-details pre { margin: .35rem 0 0; max-height: 14rem; overflow: auto; white-space: pre-wrap; font-size: .66rem; }
.op-preview-review.is-compact .op-state:empty { display: none; }

.slot-tile-cell { position: relative; display: inline-block; flex: 0 0 var(--slot-w); vertical-align: top; }

.tile-combine-chip { position: absolute; left: 50%; top: -11px; z-index: 3; display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; pointer-events: auto; }

.combine-plot-chooser { display: grid; gap: 0.3rem; margin: 0.4rem 0 0; padding: 0; list-style: none; }
.candidate-upgrade { color: var(--accent); font-size: 0.64rem; }
/* Worker picker actions (2026-08-30 layout fix): a real trailing grid column
   inside the row (`.candidate-actions`, emitted by renderCandidateRow() in
   app.js), not a sibling flex wrapper -- the old `.candidate-action-row` this
   replaced existed only because the row used to be a <button> and buttons
   cannot nest buttons. */
.candidate-actions { display: flex; flex-direction: column; justify-content: center; gap: 0.22rem; min-width: 0; }
.picker-combine { width: 100%; box-sizing: border-box; }

.picker-combine-dash { align-self: center; padding: 0.22rem 0.3rem; color: var(--meta); font-size: 0.64rem; }

.candidate-visible-slice { position: sticky; left: 0; z-index: 4; box-sizing: border-box; container-type: inline-size; inline-size: 100%; max-inline-size: 100%; min-inline-size: 0; }
/* S-5380 worker picker: one header bar, tall card rows, status badges. Iris tokens only. */
.inline-picker { box-sizing: border-box; inline-size: 100%; max-inline-size: 100%; min-inline-size: 0; margin-top: 0; /* S-13970: no top margin */ padding: 0; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); }
.picker-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem 0.9rem; padding: 0.55rem 0.8rem; background: color-mix(in srgb, var(--primary) 8%, var(--bg3)); border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; }
.picker-bar-title { order: 1; display: flex; align-items: baseline; gap: 0.5rem; flex: 1 1 0; min-width: 0; max-width: 100%; }
.picker-group-icon { flex: 0 0 auto; font-size: 1.15rem; line-height: 1; align-self: center; }
.picker-group-title { flex: 0 0 auto; color: var(--heading); font-size: 1rem; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap; }
.picker-context { flex: 0 1 auto; min-width: 0; overflow: hidden; color: var(--text); font-size: 0.8rem; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
.picker-bar-end { order: 2; display: flex; flex: 0 0 auto; align-items: center; gap: 0.6rem; margin-left: auto; }
/* S-13980: a thin vertical pipe between the sort chips and Close (same divider token as the sort pipe). */
.picker-bar-end .picker-bar-sep { flex: 0 0 1px; inline-size: 1px; align-self: stretch; min-block-size: 1.4rem; background: var(--border-bright); }
/* IR-6290: the mobile plot sheet lays .picker-bar-end out as a grid and strips Close, so the pipe has nothing to separate there. */
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-bar-sep { display: none; }
.picker-bar-end .picker-keep-open { position: relative; display: flex; align-items: center; flex: 0 0 auto; white-space: nowrap; }

.picker-bar-end .picker-keep-open input { position: absolute; inline-size: 1px; margin: 0; pointer-events: none; }

/* S-8210: jumping to the next slot -- the list keeps its height and dims, then the new cards fade up gently. */
.candidate-list { transition: opacity 0.2s ease; }
.candidate-list.is-advancing { opacity: 0.35; }
@keyframes picker-list-arrive { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.candidate-list.is-arriving > * { animation: picker-list-arrive 0.28s ease-out both; }
@media (prefers-reduced-motion: reduce) { .candidate-list { transition: none; } .candidate-list.is-arriving > * { animation: none; } }
/* S-8240: the used card folds shut like a harmonica; the cards below slide up, nothing else redraws. */
.candidate-card.is-collapsing { overflow: hidden; min-height: 0; padding-block: 0; margin-block-end: -0.45rem; border-block-width: 0; opacity: 0; pointer-events: none; transition: height 0.46s cubic-bezier(0.22, 1, 0.36, 1), padding 0.46s cubic-bezier(0.22, 1, 0.36, 1), margin 0.46s cubic-bezier(0.22, 1, 0.36, 1), border-width 0.46s ease, opacity 0.24s ease; } /* S-8250: a softer, longer ease-out */
/* S-8250: the card that moves into the used card's place settles in gently -- a small lift and a brief accent glow. */
@keyframes candidate-settle { 0% { transform: translateY(0.6rem); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent); } 55% { transform: translateY(-1px); } 100% { transform: none; box-shadow: 0 0 0 0 transparent; } }
.candidate-card.is-settling { animation: candidate-settle 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.12s both; }
@media (prefers-reduced-motion: reduce) { .candidate-card.is-collapsing { transition: none; } .candidate-card.is-settling { animation: none; } }
.picker-sort-chips { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.3rem; min-width: 0; }
.picker-chip { white-space: nowrap; }

.picker-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.55rem; min-width: 0; }
.picker-bar .picker-filters { order: 3; flex: 1 1 100%; margin: 0; padding: 0; border: 0; background: none; }
.picker-filters label:not(.ceres-btn) { display: flex; flex-direction: row; align-items: center; gap: 0.3rem; flex: 0 0 auto; color: var(--text-dim); font-size: 0.62rem; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; white-space: nowrap; }
/* S-8520 (board filter row technique applied here): the outer caption text is gone
   (kept only as `.sr-only` inside the label, see candidates.js's own comment) -- this
   lets the label shrink to its one visible control's own width, matching board.js's
   `.board-filter-field` rule exactly. */
.picker-filters .picker-filter-field { flex: 0 0 auto; min-width: 0; gap: 0; }
.picker-filters .board-filter-field { flex: 0 0 auto; min-width: 0; }
.picker-filters input:not(.ceres-wide-input), .picker-filters select { box-sizing: border-box; inline-size: 7rem; max-width: 100%; min-width: 0; padding: 0.22rem 0.4rem; font: inherit; font-size: 0.75rem; }
.picker-filters input[data-picker-filter="q"] { inline-size: 10rem; max-width: 100%; }
/* S-16830 / S-16825c: the shared input's larger font needs room for the whole "Search name…" placeholder at every width. */
.picker-filters input.ceres-wide-input[data-picker-filter="q"] { inline-size: 8.25rem; } /* S-16855b: "Search name…" fits at the dropdown text size */
/* S-5330: Min and Max Base PP are short number fields side by side. */
.picker-filters input[data-picker-filter="min_pp"], .picker-filters input[data-picker-filter="max_pp"] { inline-size: 5rem; max-width: 100%; }
.picker-filters .controls-sep { flex: 0 0 1px; align-self: stretch; min-block-size: 1.4rem; background: var(--border-bright); }
html.ceres-lw-le-1279 .picker-filters .controls-sep { display: none; }
.picker-filters select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Selects route through customSelect.js; the visible trigger is sized here. */
.picker-filters .csel-wrap { position: relative; display: inline-block; inline-size: max-content; min-inline-size: max-content; flex: 0 0 auto; }
.picker-filters .csel-trigger { min-inline-size: max-content; }
.picker-filters .picker-toggle { position: relative; gap: 0; text-transform: none; }

.picker-filters .picker-toggle input { position: absolute; inline-size: 1px; margin: 0; pointer-events: none; }

.picker-filters .picker-clear-filters { flex: none; text-decoration-color: transparent; }

.picker-filters .picker-clear-filters:hover,
.picker-filters .picker-clear-filters:focus-visible { text-decoration-color: currentColor; }
.picker-bar .terrain-affinity-badge:focus:not(:focus-visible) { outline: none; }
/* The plot's splinter icons, as the worker picker's Splinter filter. */
.picker-bar .candidate-worker-affinity { block-size: auto; max-inline-size: 100%; margin-right: 0.3rem; }
/* S-5580: sort sits at the right end of the filter row, a pipe between it and the filters.
   syncSortDivider() (picker-actions.js) drops the pipe when the group wraps to the start of a line. */
/* S-7950: the sort chips live in the title row's end group, right-aligned before Close. */
.picker-bar .picker-sort-chips { padding-left: 0.75rem; border-left: 1px solid var(--border-bright); }
.picker-bar .picker-sort-chips.is-line-start { padding-left: 0; border-left: 0; }
/* S-5570: the badges ARE the plot view's terrain-affinity badges (shared .terrain-affinity-badge
   rules: 1.5rem disc, ring, 1rem icon). Only the <button> chrome is reset here -- no size,
   padding, border or background of our own. Hover and selection draw outside the disc. */
.picker-bar .terrain-affinity-badge { padding: 0; border: 0; font: inherit; cursor: pointer; appearance: none; }
.picker-bar .terrain-affinity-scroll, .picker-bar .terrain-affinity-viewport { block-size: auto; }
.picker-bar .terrain-affinity-viewport { padding: 0.3rem; }
.picker-bar .terrain-affinity-badge:hover { box-shadow: 0 0 0 2px var(--accent); }
/* S-5460 item 1: with a splinter chosen, it stays full colour in a gold ring; the rest grey out. */
.picker-bar .terrain-affinity-badge { transition: box-shadow 0.12s ease, opacity 0.12s ease, filter 0.12s ease; }
.picker-bar .terrain-affinity:has(.is-selected) .terrain-affinity-badge:not(.is-selected) { filter: grayscale(1); opacity: 0.35; }
.picker-bar .terrain-affinity:has(.is-selected) .terrain-affinity-badge:not(.is-selected):hover { opacity: 0.7; }
.picker-bar .terrain-affinity-badge:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.candidate-list { display: flex; flex-direction: column; gap: 0.45rem; padding: 0.6rem 0.8rem; min-width: 0; }
/* IR-5790 / S-9140: the grid's column count is a pure function of its own inline size -- the one authoritative
   content width minus the picker's margins -- never of a tier breakpoint: as many columns as fit a card's content
   minimum (15.5rem: 7 across Full HD even with a classic scrollbar), at most 7 (the S-9050 Full HD look).
   The -0.1px keeps exact-fit 7 from rounding down to 6. */
.candidate-list.is-card-grid { --candidate-grid-card-min: 13.5rem; --candidate-grid-gap: 0.75rem; box-sizing: border-box; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--candidate-grid-card-min)), 1fr)); align-items: start; gap: var(--candidate-grid-gap); inline-size: 100%; max-inline-size: 100%; margin-inline: 0; padding: 0.75rem 0.8rem; }
.candidate-list.is-card-grid > .candidate-grid-card { min-inline-size: 0; }
.candidate-grid-card { position: relative; display: grid; grid-template-rows: auto auto; align-content: start; align-items: start; gap: 0.5rem; min-inline-size: 0; overflow: visible; box-sizing: border-box; padding: 0.55rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--text); }
.candidate-grid-card[data-picker-action="choose"] { cursor: pointer; }
.candidate-grid-card[data-picker-action="choose"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.candidate-grid-card[aria-disabled="true"] { cursor: not-allowed; opacity: 0.72; }
.candidate-grid-card:not(:disabled):not([aria-disabled="true"]):hover, .candidate-grid-card:not(:disabled):not([aria-disabled="true"]):focus-within { border-color: var(--border-bright); background: color-mix(in srgb, var(--accent) 7%, var(--bg)); box-shadow: 0 6px 18px color-mix(in srgb, var(--shadow) 35%, transparent); }
.candidate-grid-card.is-selected { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent); }
.candidate-grid-select { border-radius: var(--radius); display: flex; flex-direction: column; align-items: stretch; min-inline-size: 0; border: 0; padding: 0; background: transparent; color: inherit; font: inherit; text-align: start; cursor: pointer; }
.candidate-grid-stage { inline-size: 100%; justify-content: flex-start; }
.candidate-grid-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius); }
.candidate-grid-select[disabled] { cursor: not-allowed; opacity: 0.72; }
.candidate-grid-face, .candidate-grid-item-face { display: flex; flex-direction: column; align-items: stretch; gap: 0.45rem; min-inline-size: 0; }
.candidate-grid-face { position: relative; align-items: center; text-align: center; }
.candidate-grid-face .candidate-art { align-self: center; inline-size: min(100%, 7rem); block-size: 9.8rem; }
.candidate-grid-face .candidate-art { position: relative; }
.candidate-grid-face .candidate-grid-identity { inline-size: 100%; align-items: center; }
.candidate-grid-identity { display: flex; flex-direction: column; gap: 0.25rem; min-inline-size: 0; text-align: center; }
.candidate-grid-icon-level { display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem; min-inline-size: 0; color: var(--text); font-size: 0.74rem; }
.candidate-grid-icon-level .candidate-icon-row { flex-wrap: nowrap; }
.candidate-grid-identity .candidate-abilities { align-self: center; }
/* S-9360: the card minimum now scales down to 13.5rem; a centred (not stretched) name must still stop at the card edge and ellipsis, never spill. */
.candidate-grid-identity > .candidate-name { max-inline-size: 100%; }
.candidate-grid-face .candidate-name, .candidate-grid-item-face .candidate-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.candidate-grid-metrics { display: flex; justify-content: center; align-self: stretch; max-inline-size: 100%; gap: var(--ceres-control-gap); }
.candidate-grid-metric { box-sizing: border-box; display: flex; flex: 1 1 0; flex-direction: column; align-items: center; justify-content: center; min-inline-size: 0; min-block-size: var(--ceres-control-min-block); padding-block: var(--ceres-pp-block-pad); padding-inline: var(--ceres-control-inline-pad); border: 1px solid color-mix(in srgb, var(--border) 65%, transparent); border-radius: var(--ceres-pp-radius); background: color-mix(in srgb, var(--bg3) 55%, transparent); }
.candidate-grid-metric strong { max-inline-size: 100%; white-space: nowrap; color: var(--heading); font-size: 1rem; }
.candidate-grid-metric small { color: var(--text-dim); font-size: 0.62rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; }
.candidate-grid-boostable.is-positive { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 14%, var(--bg3)); }
.candidate-grid-boostable.is-negative { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, var(--bg3)); }
.candidate-grid-boostable.is-unknown { border-color: var(--warn); }
.candidate-grid-boostable.is-positive strong { color: var(--ok); }
.candidate-grid-boostable.is-negative strong { color: var(--danger); }
.candidate-readiness-overlay { position: absolute; inset: 0; z-index: 2; display: inline-flex; flex-direction: column; box-sizing: border-box; align-items: center; justify-content: center; gap: 0.35rem; padding: 0.35rem; border: 1px solid var(--danger); border-radius: inherit; background: color-mix(in srgb, var(--danger) 46%, transparent); color: var(--hi); font-size: 0.67rem; font-weight: 700; line-height: 1.2; text-align: center; text-shadow: 0 1px 2px var(--shadow); }
.candidate-readiness-overlay.is-danger { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 46%, transparent); }
.candidate-readiness-badge { display: inline-flex; align-items: center; justify-content: center; padding: 0.1rem 0.28rem; border: 1px solid currentColor; border-radius: 999px; color: var(--hi); font-size: 0.61rem; font-variant-numeric: tabular-nums; }
.candidate-grid-art-foil { position: absolute; inset: auto auto 0.4rem 50%; z-index: 3; max-inline-size: calc(100% - 0.8rem); margin: 0; transform: translateX(-50%); box-shadow: 0 1px 4px var(--shadow); }
.candidate-grid-face .candidate-metric small, .candidate-grid-item-face .candidate-boost small { font-size: 0.62rem; }
.candidate-grid-item-face { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 0.55rem; }
.candidate-grid-item-face .candidate-rank { grid-row: span 2; min-inline-size: 2.1rem; }
.candidate-grid-item-face .candidate-boost { grid-column: 1 / -1; justify-self: end; text-align: end; }
.candidate-grid-rarity { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.25rem; padding-block: 0.45rem; font: inherit; text-align: center; }
.candidate-grid-rarity.is-unavailable:is(:disabled, [aria-disabled="true"]) { opacity: 0.55; cursor: not-allowed; }
.candidate-grid-rarity-icon { display: grid; place-items: center; min-block-size: 2.25rem; }
.candidate-grid-rarity-icon .totem-icon { inline-size: 2.25rem; block-size: 2.25rem; }
.candidate-grid-rarity-icon .rarity-gem { inline-size: 1.5rem; block-size: 1.5rem; }
.candidate-grid-rarity-count { color: var(--text-dim); font-size: 0.75rem; }
.candidate-grid-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; min-inline-size: 0; }
.candidate-grid-tools .candidate-primary { flex: 1 1 8rem; }
.candidate-detail-toggle,
.candidate-detail-pin { flex: 0 1 auto; }

.candidate-grid-detail { position: absolute; inset-block-start: calc(100% + 0.45rem); inset-inline-start: 0; z-index: 20; box-sizing: border-box; inline-size: 100%; max-inline-size: 100%; padding: 0.65rem; border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg2); color: var(--text); box-shadow: 0 10px 28px color-mix(in srgb, var(--shadow) 50%, transparent); overflow-wrap: anywhere; opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(-0.2rem); transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s ease; }
.candidate-grid-card:hover:not(:has(.candidate-detail-toggle:focus, .candidate-detail-pin:focus)) .candidate-grid-detail, .candidate-grid-card:has(.candidate-grid-select:focus-visible) .candidate-grid-detail, .candidate-grid-card.is-detail-open .candidate-grid-detail { opacity: 1; visibility: visible; pointer-events: auto; transform: none; }
.candidate-grid-detail p { margin: 0 0 0.55rem; line-height: 1.35; }
@keyframes candidate-grid-details-in { from { opacity: 0; transform: translateY(0.25rem); } to { opacity: 1; transform: none; } }
.candidate-grid-card.is-grid-arriving :is(.candidate-grid-identity, .candidate-grid-metrics, .candidate-grid-tools) { animation: candidate-grid-details-in 320ms ease-in both; animation-delay: calc(var(--ci, 0) * 35ms); }
.picker-view-switch { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; min-inline-size: 2.25rem; margin-inline-start: auto; }

.picker-view-switch .hdr-ic { display: block; inline-size: 1.35rem; overflow: visible; }

/* IR-5790: the former raw width @media column overrides are gone -- the auto-fill rule above derives the count. */
html.ceres-lw-le-544 .candidate-list.is-card-grid { --candidate-grid-gap: 0.55rem; padding-inline: 0.45rem; }
html.ceres-lw-le-544 .candidate-grid-face .candidate-art { block-size: 8rem; }
@media (prefers-reduced-motion: reduce) { .candidate-grid-detail { transition: none; } .candidate-grid-card.is-grid-arriving :is(.candidate-grid-identity, .candidate-grid-metrics, .candidate-grid-tools) { animation: none; } }
.candidate-row { display: grid; align-items: center; gap: 0.45rem; width: 100%; min-width: 0; box-sizing: border-box; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--text); padding: 0.38rem 0.5rem; font: inherit; text-align: left; cursor: pointer; }
.candidate-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Totem/Title item rows keep their compact 3-column shape. */
.candidate-row-item { grid-template-columns: 1.5rem minmax(0, 1fr) 7rem; min-height: 2.5rem; border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); transition: border-color 0.12s ease, background-color 0.12s ease; }
.candidate-row-item:hover { border-color: var(--accent); background: var(--bg3); }
.candidate-columns.is-item-columns { display: grid; grid-template-columns: 1.5rem minmax(0, 1fr) 7rem; gap: 0.45rem; margin: 0.6rem 0.8rem 0; padding: 0 0.5rem; color: var(--heading); font-size: 0.62rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
.candidate-item-tier { margin-left: 0.35rem; color: var(--text-dim); font-size: 0.68rem; font-weight: 600; }
.candidate-boost { justify-self: end; color: var(--heading); text-align: right; font-size: 0.93rem; font-weight: 750; font-variant-numeric: tabular-nums; white-space: nowrap; line-height: 1.15; }
.candidate-rank { display: flex; flex-direction: column; align-items: center; color: var(--text-dim); font-size: 0.69rem; font-weight: 600; font-variant-numeric: tabular-nums; text-align: center; }
.candidate-copy { display: flex; flex-direction: column; min-width: 0; }
.candidate-name-line { display: flex; align-items: center; gap: 0.35rem; min-width: 0; white-space: nowrap; }
.candidate-name, .candidate-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.candidate-name { color: var(--text); font-size: 0.8rem; font-weight: 600; }
.candidate-meta { color: var(--text-dim); font-size: 0.69rem; }

/* The card row: art | identity | status | Base PP | Boostable | Terrain | Plot change | actions. */
/* S-6110: the picture column is .8rem wider than the 6rem picture -- more room before name and stats. */
.candidate-card { grid-template-columns: 6.8rem minmax(14rem, 2.4fr) minmax(13.5rem, 15rem) repeat(5, minmax(0, 1fr)) minmax(7rem, 9rem); /* S-8100: spare width goes to the identity (abilities and Bloodline sit side by side, so the row stays at the art height); status and actions are capped so no empty gap opens beside them */ /* S-6150: + Harvest /h */ /* S-8000: status track >= 13.5rem so the one-line cooling pill never overflows; identity min 9.5rem + 0.65rem gaps keep the metric tracks as wide as before at 1366 */ /* IR-5370 (escalated): fluid tracks -- the metric columns share the remaining width (minmax(0, 1fr)), so the row can never be wider than its container at any root font size; overflow hidden is the hard stop at the border */ column-gap: 0.65rem; min-width: 0; overflow: hidden; padding: 0.5rem 0.8rem; transition: border-color 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease; }
.candidate-card:not(.candidate-grid-card) { min-height: 9.4rem; }
.candidate-card:not([aria-disabled]):hover { border-color: var(--border-bright); background: color-mix(in srgb, var(--accent) 7%, var(--bg)); box-shadow: 0 6px 18px color-mix(in srgb, var(--shadow) 35%, transparent); }
.candidate-card[aria-disabled="true"] { cursor: not-allowed; }
.candidate-art { display: block; overflow: hidden; border: 1px solid var(--border); border-radius: 0.3rem; background: var(--bg3); }
.candidate-card .candidate-art { grid-area: auto; inline-size: 6rem; block-size: 8.4rem; }
.candidate-art img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; transition: transform 0.18s ease; }
/* S-6070: picker cards open like a plot -- the card first (staggered soft fade), its art slot in the empty-slot
   look until the picture is decoded, then the picture fades in over 420ms (eos-portrait-pop-opacity). */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } /* IR-4740 */
.candidate-card.is-ghost { pointer-events: none; }
/* IR-4760: the spinner shows only if loading takes longer than 300ms -- a fast load never flashes it. */
.candidate-card.is-ghost .spinner { opacity: 0; animation: ghost-spinner-in 1ms linear 300ms forwards; }
@keyframes ghost-spinner-in { to { opacity: 1; } }
.candidate-card.is-ghost .candidate-art { display: grid; place-items: center; border-style: dashed; background: color-mix(in srgb, var(--bg3) 66%, transparent); }
.candidate-list:not(.is-card-grid) .candidate-card.is-ghost .cand-ghost-body { min-block-size: 8.4rem; }
.candidate-card.is-entering { animation: eos-portrait-pop-opacity 320ms cubic-bezier(0.25, 0.1, 0.25, 1) both; animation-delay: calc(var(--ci, 0) * 40ms); }
.candidate-art:has(> img:not(.is-art-in)) { border-style: dashed; background: color-mix(in srgb, var(--bg3) 66%, transparent); }
.candidate-art > img:not(.is-art-in) { opacity: 0; }
.candidate-art > img.is-art-in:not(.is-art-instant) { animation: eos-portrait-pop-opacity 420ms cubic-bezier(0.25, 0.1, 0.25, 1) both; }
@media (prefers-reduced-motion: reduce) { .candidate-card.is-entering, .candidate-art > img.is-art-in { animation: none; } }
.candidate-card:not([aria-disabled]):hover .candidate-art img { transform: scale(1.05); }
.candidate-card.is-not-ready .candidate-art img { filter: saturate(0.4) brightness(0.85); }
.candidate-list.is-card-grid > .candidate-grid-card.is-not-ready .candidate-art img { filter: none; }
.candidate-card-details { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; overflow-wrap: anywhere; }
/* S-8130: the Bloodline plate always takes its own line under the abilities (the card's fourth row), whatever its length. */
.candidate-traits { display: flex; flex-direction: column; align-items: flex-start; gap: 0.3rem; min-width: 0; }
.candidate-card .candidate-bloodline.is-paired, .candidate-card .candidate-bloodline.is-solo { padding: 0.18rem 0.55rem 0.2rem 0.4rem; }
.candidate-card .candidate-bloodline-sub { font-size: 0.72rem; }
.candidate-card .candidate-name { color: var(--heading); font-size: 1.25rem; font-weight: 700; line-height: 1.2; }
.candidate-identity { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 0.25rem; white-space: nowrap; }
.candidate-card .candidate-identity { display: flex; flex-wrap: wrap; gap: 0.3rem 0.4rem; color: var(--text); font-size: 0.85rem; white-space: normal; }
.candidate-identity .splinter-icon, .candidate-identity .rarity-gem, .candidate-identity .set-icon { inline-size: 1rem; block-size: 1rem; object-fit: contain; }
/* S-5620: one box size and one gap for every icon on the card line -- splinter, rarity, set,
   and each land ability in the row below -- so the icons line up in columns. */
.candidate-card { --icon-box: 1.9rem; --icon-gap: 0.4rem; }
.candidate-icon-row { display: inline-flex; flex-wrap: nowrap; align-items: center; gap: var(--icon-gap); }
.candidate-icon-box { display: inline-flex; flex: 0 0 var(--icon-box); align-items: center; justify-content: center; box-sizing: border-box; inline-size: var(--icon-box); block-size: var(--icon-box); overflow: hidden; }
.candidate-card .candidate-icon-box > img { display: block; inline-size: 100%; block-size: 100%; margin: 0; object-fit: contain; }
.candidate-identity .set-icon-generic { opacity: 0.82; }
.candidate-abilities { display: flex; flex-wrap: nowrap; align-self: flex-start; gap: var(--icon-gap); }
/* S-5460 item 8: a squarer plate like the plot's Base PP cells; title + Bloodline, then the pairing. */
.candidate-bloodline { display: inline-flex; flex-direction: column; align-self: flex-start; gap: 0.12rem; max-inline-size: 100%; box-sizing: border-box; color: var(--text); font-size: 0.8rem; }
.candidate-bloodline-head { display: inline-flex; align-items: center; gap: 0.4rem; min-width: 0; font-weight: 600; }
.candidate-bloodline-head .ability-icon { flex: 0 0 auto; inline-size: 1.3rem; block-size: 1.3rem; }
.candidate-bloodline-sub { padding-inline-start: 1.7rem; color: var(--text); font-size: 0.75rem; }
.candidate-bloodline.is-paired, .candidate-bloodline.is-solo { padding: 0.3rem 0.7rem 0.32rem 0.45rem; border: 1px solid; border-radius: 6px; }
.candidate-bloodline.is-paired .candidate-bloodline-head, .candidate-bloodline.is-solo .candidate-bloodline-head { color: var(--heading); font-weight: 700; }
.candidate-bloodline.is-paired { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 15%, var(--bg3)); }
.candidate-bloodline.is-solo { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 15%, var(--bg3)); }

/* Status: one badge (tone on border/tint/icon, --heading text for contrast) and a detail line. */
.candidate-status { display: flex; flex-direction: column; align-items: flex-start; gap: 0.35rem; min-width: 0; overflow-wrap: anywhere; }
.candidate-status-badge { --badge-tone: var(--border-bright); display: inline-flex; align-items: center; gap: 0.4rem; max-inline-size: 100%; box-sizing: border-box; padding: 0.28rem 0.65rem; border: 1px solid var(--badge-tone); border-radius: 999px; background: color-mix(in srgb, var(--badge-tone) 18%, var(--bg)); color: var(--heading); font-size: 0.95rem; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.candidate-status-badge.is-info { --badge-tone: var(--info); }
.candidate-status-badge.is-warn { --badge-tone: var(--warn); }
.candidate-status-badge.is-danger { --badge-tone: var(--danger); }
.candidate-status-badge.is-quiet { padding-inline: 0; border-color: transparent; background: none; color: var(--text); font-weight: 600; }
.candidate-status-icon { color: var(--badge-tone); font-size: 1.1rem; line-height: 1; }
.candidate-countdown { font-weight: 800; }
.candidate-status-badge { flex-wrap: wrap; column-gap: 0.35rem; row-gap: 0.05rem; border-radius: 1rem; line-height: 1.25; }
.candidate-status-part { white-space: nowrap; }
.candidate-status-onland { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; color: var(--heading); font-size: 0.95rem; font-weight: 700; line-height: 1.25; } /* S-7980: "On land" + the shared plot badge (.ch-plot-badge), no pill */
.candidate-status-badge.is-oneline { flex-wrap: nowrap; column-gap: 0.28rem; padding-inline: 0.45rem; font-size: 0.78rem; } /* S-8000: countdown pills ("Cooling · ready in 2d 18h") stay on one line */
.candidate-status-badge.is-oneline .candidate-status-icon { font-size: 0.95rem; }
.candidate-status-unstaked { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; } /* S-8000: "Unstaked from" + the shared plot badge, like the On-land cell */
.candidate-countdown { white-space: nowrap; }
.candidate-card .picker-combine-dash { display: none; }
.picker-upgrade-loading { margin: 0; color: var(--text); font-style: italic; }
.candidate-status-detail { color: var(--text); font-size: 0.8rem; line-height: 1.3; }

/* Metrics: display:contents so each cell is its own grid track; captions ride under the value. */
.candidate-metrics { display: contents; }
.candidate-metric { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.2rem; min-width: 0; overflow-wrap: anywhere; color: var(--heading); text-align: center; font-variant-numeric: tabular-nums; } /* S-8100: centred in their columns, so the five figures sit evenly spaced across the row */
.candidate-metric strong { max-inline-size: 100%; overflow: hidden; color: var(--heading); font-size: 1.35rem; font-weight: 750; line-height: 1.1; text-overflow: ellipsis; white-space: nowrap; }
.candidate-base strong { font-size: 1.5rem; }
.candidate-metric small { color: var(--text-dim); font-size: 0.7rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; }
.candidate-harvest-change strong { display: inline-flex; align-items: center; justify-content: center; gap: .3rem; }
.candidate-harvest-change.is-two strong { font-size: 1.05rem; }
.cand-res-ic { inline-size: 1.15rem; block-size: 1.15rem; object-fit: contain; flex: none; }
.candidate-metric em { overflow-wrap: anywhere; color: var(--text-dim); font-size: 0.7rem; font-style: normal; line-height: 1.2; }
.candidate-terrain > strong.is-r117-positive, .candidate-plot-change > strong.is-positive { color: var(--ok); }
.candidate-terrain > strong.is-r117-negative, .candidate-plot-change > strong.is-negative { color: var(--danger); }
.candidate-card .candidate-actions { display: flex; flex-direction: column; align-items: stretch; justify-content: center; gap: 0.35rem; min-width: 0; /* IR-5370 (his fix): the span may shrink to its 8rem track and the button fills it, centring its label -- nothing can spill past the row border */ }
.candidate-primary { display: inline-flex; justify-content: center; align-items: center; width: 100%; min-width: 0; box-sizing: border-box; overflow: hidden; white-space: nowrap; }

.candidate-card .candidate-upgrade { text-align: center; font-size: 0.72rem; }

/* Small pills still used by foil badges and item rows. */
.candidate-tag { display: inline-block; margin-left: 0.3rem; padding: 0 0.28rem; border: 1px solid var(--border); border-radius: 999px; color: var(--text-dim); font-size: 0.6rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; vertical-align: 1px; white-space: nowrap; }
.candidate-tag.is-warn { border-color: var(--warn); background: var(--bg3); color: var(--heading); }
.candidate-tag.is-foil { border-color: var(--gold); background: var(--bg3); color: var(--heading); }
.candidate-tag.is-info { border-color: var(--info); background: var(--bg3); color: var(--heading); }
.candidate-tag.is-danger { border-color: var(--danger); background: var(--bg3); color: var(--danger); }
.foil-badge { display: inline-flex; box-sizing: border-box; align-items: center; justify-content: center; max-inline-size: 100%; overflow: hidden; padding: 0.04rem 0.32rem; border: 1px solid; border-radius: 999px; color: var(--hi); font-size: 0.58rem; font-weight: 800; letter-spacing: 0.03em; line-height: 1.25; text-overflow: ellipsis; vertical-align: middle; white-space: nowrap; }
.foil-badge.foil-1 { background: var(--gold-dim); border-color: var(--gold); }
.foil-badge.foil-2 { background: color-mix(in srgb, var(--gold-dim) 80%, var(--orchid) 20%); border: 2px double var(--orchid); }
.foil-badge.foil-3 { background: color-mix(in srgb, var(--orchid) 45%, var(--shadow) 55%); border-color: var(--orchid); }
.foil-badge.foil-4 { background: color-mix(in srgb, var(--orchid) 65%, var(--shadow) 35%); border: 2px double var(--gold); }
.candidate-tag.foil-badge { margin-left: 0.3rem; color: var(--hi); }
.candidate-card .candidate-tag.foil-badge { margin-left: 0; font-size: 0.66rem; }
.candidate-card .candidate-grid-art-foil { margin-left: 0; }
.slot-foil-badge { margin-bottom: 0.15rem; font-size: 0.5rem; }
.slot-grid.is-textonly .slot-foil-badge { margin-bottom: 0; font-size: 0.48rem; }
.checkout-foil-badge { margin-left: 0.35rem; font-size: 0.52rem; }
.combine-chooser-foil { margin-inline: 0.25rem; font-size: 0.52rem; }
.combine-hero-foil { font-size: 0.56rem; }
.combine-arrival-foil { font-size: 0.42rem; }
.candidate-delta { justify-self: end; color: var(--text-dim); text-align: right; font-size: 0.82rem; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; line-height: 1.15; }
.candidate-delta small { display: block; color: var(--text-dim); font-size: 0.6rem; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; }

.picker-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0.55rem 0.8rem 0.65rem; border-top: 1px solid var(--border); color: var(--text); font-size: 0.75rem; }
.slot-editor .candidate-meta,
.slot-editor .candidate-delta,
.slot-editor .candidate-boost,
.slot-editor .picker-footer { color: var(--text); }

.slot-editor .candidate-tag.foil-badge { color: var(--hi); }
.candidate-list.is-card-grid > .candidate-grid-card { --candidate-card-inline-pad: 0.55rem; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto; overflow: visible; padding: 0.55rem var(--candidate-card-inline-pad); }
.candidate-list.is-card-grid > .candidate-grid-card .candidate-art { inline-size: min(100%, 7rem); block-size: 9.8rem; }
.candidate-list.is-card-grid > .candidate-grid-card .candidate-metrics { display: grid; }
.picker-count { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.picker-more { margin-left: auto; }
.picker-loading-more { color: var(--text); font-style: italic; }
.picker-empty { margin: 0; padding: 0.6rem 0.2rem; color: var(--text); }
.candidate-list > .picker-availability { grid-column: 1 / -1; box-sizing: border-box; min-block-size: 2.75rem; }
.picker-error { background: var(--bg3); color: var(--heading); }

html.ceres-lw-ge-1400 .candidate-visible-slice { inline-size: 100%; max-inline-size: 100%; }

/* Below ~1600px the filter row tightens so it stays one line under the title line. */
@container (max-width: 100rem) {
  .picker-bar .picker-filters { gap: 0.3rem 0.4rem; }
  .picker-filters input[data-picker-filter="q"] { inline-size: 7.5rem; }
  .picker-filters input[data-picker-filter="min_pp"], .picker-filters input[data-picker-filter="max_pp"] { inline-size: 4.5rem; }
  /* S-5460: the one filter row (splinters + fields + pills + sort) stays two lines at 1366. */
  .picker-filters input[data-picker-filter="q"] { inline-size: 6rem; }
  /* S-16855b: 5rem so "Max PP" fits with the dropdown's inline padding (S-16769 "Make minPP and maxPP longer"). */
  .picker-filters input[data-picker-filter="min_pp"], .picker-filters input[data-picker-filter="max_pp"] { inline-size: 5rem; }
  .picker-filters label:not(.ceres-btn) { gap: 0.2rem; letter-spacing: 0; }
  .candidate-card .candidate-identity { gap: 0.3rem 0.3rem; font-size: 0.8rem; }
  .candidate-card { --icon-box: 1.75rem; --icon-gap: 0.35rem; grid-template-columns: 6.8rem minmax(14rem, 3fr) minmax(13.5rem, 14rem) repeat(5, minmax(0, 1fr)) minmax(7rem, 8rem); } /* S-8100: narrower desktops give the identity a larger share */
  .candidate-metric strong { font-size: 1.15rem; }
  .candidate-base strong { font-size: 1.25rem; }
  .candidate-metric small { font-size: 0.62rem; letter-spacing: 0.03em; }
}

/* Mid width (below desktop, S-8100): art spans two lines; identity/status/actions on top, the metrics below. */
@container (max-width: 64rem) {
  .candidate-card { grid-template-columns: 6.8rem minmax(0, 1.3fr) minmax(13.5rem, 1fr) 8rem; /* IR-5370 (his fix) */ /* S-8000: 13.5rem status track */ grid-template-areas: "art identity status actions" "art metrics metrics metrics"; row-gap: 0.4rem; }
  .candidate-card .candidate-art { grid-area: art; }
  .candidate-card .candidate-card-details { grid-area: identity; }
  .candidate-card .candidate-status { grid-area: status; }
  .candidate-card .candidate-actions { grid-area: actions; }
  .candidate-card .candidate-metrics { display: grid; grid-area: metrics; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 0.8rem; }
}

/* Narrow: art beside identity and status, then metrics 2-up, then actions in a row. */
@container (max-width: 54rem) {
  .picker-bar-end { margin-left: 0; }
  .candidate-card { grid-template-columns: 5.8rem minmax(0, 1fr); grid-template-areas: "art identity" "art status" "metrics metrics" "actions actions"; }
  .candidate-card:not(.candidate-grid-card) { min-height: 0; }
  .candidate-card .candidate-art { inline-size: 5rem; block-size: 7rem; }
  .candidate-card .candidate-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .candidate-card .candidate-actions { flex-direction: row; }
  .candidate-card .candidate-status-badge { white-space: normal; }
}

@container (max-width: 28rem) {
  .picker-filters label:not(.picker-toggle) { min-width: 0; max-width: 100%; overflow: hidden; flex: 1 1 7.5rem; }
  .picker-filters input, .picker-filters select { max-width: 100%; width: 100%; }
  .picker-filters .csel-wrap, .picker-filters .csel-trigger { max-width: 100%; width: 100%; }
  .candidate-list { padding-inline: 0.35rem; }
  .candidate-card .candidate-name { font-size: 1rem; }
}

.slot-hazard-overlay { position: fixed; inset: 0; z-index: 100; display: flex; align-items: flex-start; justify-content: center; padding: calc(var(--ceres-vh) * 5) 1rem; background: rgba(0, 0, 0, 0.55); overflow-y: auto; }
#slot-hazard-overlay { z-index: 1350; }
.slot-hazard-overlay[hidden] { display: none; }
.slot-hazard-panel { box-sizing: border-box; width: fit-content; min-width: min(42rem, 100%); max-width: 100%; border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg2); color: var(--text); }
.slot-hazard-panel > header { display: flex; align-items: center; gap: 0.6rem; padding: 0.8rem 1rem; border-bottom: 1px solid var(--danger); }
.slot-hazard-panel h3 { color: var(--danger); font-family: var(--font-display); font-size: 1.05rem; }
.hazard-close { margin-left: auto; }

.slot-hazard-body { padding: 0.9rem 1rem; font-size: 0.83rem; }
.slot-hazard-body ul { margin: 0.55rem 0; padding-left: 1.2rem; }
.hazard-unobserved { margin: 0.65rem 0; padding: 0.65rem 0.75rem; border: 1px dashed var(--warn); border-radius: var(--radius); background: var(--bg3); }
.slot-hazard-panel > footer { display: flex; gap: 0.4rem; padding: 0.7rem 1rem; border-top: 1px solid var(--border); }
.hazard-confirm { margin-left: auto; background: var(--danger); border-color: var(--danger); }
/* Legacy placement note: the current header pill sits above dialog overlays.
   Previously z-index110 was above the worksite-modal and hazard overlays (100 each) --
   at 90 this toast rendered fully behind an open overlay, i.e. invisible, the
   exact "confirmed, but I can't see it" complaint this fixes. */
.stage-toast { position: fixed; top: calc(env(safe-area-inset-top) + .65rem); bottom: auto; left: 50%; z-index: 1400; pointer-events: none; max-width: calc(var(--ceres-vw) * 100 - 2rem); transform: translateX(-50%); border: 1px solid var(--ok); border-radius: var(--radius); background: var(--bg3); color: var(--text); padding: 0.55rem 0.9rem; font-size: 0.8rem; text-align: center; }
.stage-toast.is-error { border-color: var(--danger); background: var(--bg3); color: var(--heading); }
.stage-toast[hidden] { display: none; }

/* The confirmation pill is the durable-looking presentation surface for a transaction outcome. */
.ceres-pill, .worksite-banner { position: fixed; top: 1rem; left: 50%; z-index: 1400; box-sizing: border-box; width: calc(var(--ceres-vw) * 48); min-width: min(38rem, calc(var(--ceres-vw) * 100 - 2rem)); max-width: calc(var(--ceres-vw) * 100 - 2rem); min-height: 9.5rem; transform: translateX(-50%); display: flex; flex-direction: column; gap: 0.65rem; overflow: hidden; border: 1px solid var(--border-bright); border-left: 4px solid var(--border); border-radius: var(--radius); background: var(--bg2); color: var(--body); padding: 0.9rem 1.1rem 1rem; box-shadow: 0 0.6rem 1.6rem color-mix(in srgb, var(--shadow) 55%, transparent); }
.ceres-pill[hidden], .worksite-banner[hidden] { display: none; }
.ceres-pill.tone-info, .worksite-banner.tone-info { border-left-color: var(--accent); }
.ceres-pill.tone-success, .worksite-banner.tone-success { border-left-color: var(--ok); }
.ceres-pill.tone-danger, .worksite-banner.tone-danger { border-left-color: var(--danger); }
.ceres-pill.tone-caution, .worksite-banner.tone-caution { border-left-color: var(--warn); }
.ceres-pill-title-row { display: flex; align-items: center; gap: 0.65rem; min-height: 1.75rem; }
.ceres-pill-title, .worksite-banner-title { display: flex; align-items: center; gap: 0.55rem; min-width: 0; flex: 1 1 auto; color: var(--heading); font-family: var(--font-display); font-size: 1.05rem; font-weight: 700; }
.ceres-pill-detail, .worksite-banner-detail { color: var(--body); font-family: var(--font-body); font-size: 0.84rem; line-height: 1.45; }
.ceres-pill-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr)); gap: 0.45rem; }
.ceres-pill-fact { min-width: 0; box-sizing: border-box; padding: 0.4rem 0.55rem; border-radius: max(0px, calc(var(--radius) - 2px)); background: color-mix(in srgb, var(--bg3) 72%, transparent); }
.ceres-pill-label { display: block; color: var(--body); font-size: 0.6rem; font-weight: 700; letter-spacing: 0.07em; line-height: 1.2; text-transform: uppercase; }
.ceres-pill-value { display: block; min-width: 0; overflow-wrap: anywhere; color: var(--heading); font-size: 0.86rem; font-variant-numeric: tabular-nums; font-weight: 600; }
.ceres-pill-transaction { margin-top: auto; }
/* IR-5030: one line per transaction of a batch */
.ceres-pill-batch { margin: 0; padding: 0; list-style: none; display: grid; gap: .3rem; }
.ceres-pill-batch li { display: flex; align-items: baseline; gap: .6rem; font-size: .84rem; color: var(--body); }
.ceres-pill-batch li span:first-child { color: var(--heading); font-weight: 700; }
.ceres-pill-batch li.is-confirmed span:first-child::before { content: '✓ '; color: var(--ok); }
.ceres-pill-batch li.is-failed span:first-child::before { content: '✕ '; color: var(--danger); }
.ceres-pill-batch li a { margin-left: auto; color: var(--body); font-family: var(--font-mono); font-size: .7rem; }
.ceres-pill-transaction a { color: var(--heading); font-size: 0.8rem; text-underline-offset: 2px; }
.ceres-pill-txid { margin-left: 0.45rem; color: var(--body); font-family: var(--font-mono); font-size: 0.7rem; }
.ceres-pill-action { align-self: flex-start; }
.ceres-pill.phase-refused,
.ceres-pill.phase-mismatch { border-color: var(--danger); border-left-color: var(--danger); background: color-mix(in srgb, var(--danger) 24%, var(--bg2)); }
.ceres-pill.phase-pending,
.ceres-pill.phase-pending-escalated,
.ceres-pill.phase-could-not-verify,
.ceres-pill.phase-confirmed-partial,
.ceres-pill.is-pending,
.ceres-pill.is-unresolved,
.ceres-pill.is-confirmed-partial,
.ceres-pill.is-confirmed-stale { border-color: var(--warn); border-left-color: var(--warn); background: color-mix(in srgb, var(--warn) 24%, var(--bg2)); }
.ceres-pill.phase-confirmed { border-color: var(--ok); border-left-color: var(--ok); background: color-mix(in srgb, var(--ok) 15%, var(--bg2)); }
.ceres-pill.phase-refused .ceres-pill-title,
.ceres-pill.phase-mismatch .ceres-pill-title { color: var(--danger); }
.ceres-pill.phase-pending .ceres-pill-title,
.ceres-pill.phase-pending-escalated .ceres-pill-title,
.ceres-pill.phase-could-not-verify .ceres-pill-title,
.ceres-pill.phase-confirmed-partial .ceres-pill-title,
.ceres-pill.is-pending .ceres-pill-title,
.ceres-pill.is-unresolved .ceres-pill-title,
.ceres-pill.is-confirmed-partial .ceres-pill-title,
.ceres-pill.is-confirmed-stale .ceres-pill-title { color: var(--warn); }
.ceres-pill.phase-confirmed .ceres-pill-title { color: var(--ok); }

.worksite-banner-dismiss { flex: 0 0 28px; width: 28px; height: 28px; margin-left: auto; border: 0; border-radius: 999px; background: transparent; color: var(--heading); font-size: 0.95rem; line-height: 1; cursor: pointer; padding: 0; }

.ceres-pill-dismiss { flex: 0 0 28px; width: 28px; margin-left: auto; }

.worksite-banner-dismiss:hover { background: color-mix(in srgb, var(--heading) 14%, transparent); }

.worksite-banner-dismiss:focus-visible { outline: 2px solid var(--heading); outline-offset: 2px; }

.ceres-pill-progress { position: absolute; right: 0; bottom: 0; left: 0; height: 3px; background: color-mix(in srgb, var(--heading) 16%, transparent); animation: ceres-pill-countdown 8s linear forwards; }
.ceres-pill-progress::before { display: block; width: 100%; height: 100%; transform-origin: left; background: var(--heading); content: ''; animation: ceres-pill-fill 8s linear forwards; }
.ceres-pill:hover .ceres-pill-progress, .ceres-pill:focus-within .ceres-pill-progress, .ceres-pill.is-paused .ceres-pill-progress, .ceres-pill:hover .ceres-pill-progress::before, .ceres-pill:focus-within .ceres-pill-progress::before, .ceres-pill.is-paused .ceres-pill-progress::before { animation-play-state: paused; }
@keyframes ceres-pill-countdown { from { opacity: 1; } to { opacity: 1; } }
@keyframes ceres-pill-fill { to { transform: scaleX(0); } }
html.ceres-lw-le-1099 .ceres-pill, html.ceres-lw-le-1099 .worksite-banner { min-width: min(38rem, calc(var(--ceres-vw) * 100 - 2rem - 4rem)); max-width: calc(var(--ceres-vw) * 100 - 2rem - 4rem); }
@media (prefers-reduced-motion: reduce) { .ceres-pill-progress { display: none; } }
.worksite-change-btn { flex: 0 0 var(--worksite-btn-w); display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; margin: 0 0 0 calc((var(--worksite-w) - var(--worksite-btn-w)) / 2); white-space: nowrap; }

/* S-7600 (v0.9.352): the worksite modal is a house modal (`.harvest-overlay` > `.harvest-panel
   .worksite-panel`, the Changes/Fill pattern): width like the Fill modal (60rem keeps four tiles
   on one row at Full HD), the body scrolls, the footer is fixed. The former `.worksite-modal-*`
   overlay/panel/scene/verdict/footer rules went with the old panel. */
.harvest-panel.worksite-panel { box-sizing: border-box; width: fit-content; min-width: min(60rem, calc(100vw - 2rem)); max-width: calc(var(--ceres-vw, 1vw) * 100 - 2rem); }
.worksite-panel .hm-headr h2 { display: flex; align-items: center; flex-wrap: wrap; gap: .6rem; }
.worksite-panel .hm-headr h2 .ch-plot-badge { font-size: 1.05rem; font-weight: 700; }
.worksite-panel .hm-body > .hm-card { box-sizing: border-box; min-width: 100%; }
.worksite-panel .ws-tiles .worksite-modal-tc { margin-top: .8rem; font-weight: 600; cursor: help; }
.worksite-panel .ws-tiles .worksite-modal-tc input { accent-color: var(--primary); inline-size: 1.05rem; block-size: 1.05rem; margin: 0; }
.worksite-panel .ws-tiles .worksite-modal-tc.is-off { opacity: .55; cursor: not-allowed; }
.worksite-panel .ws-signing { contain: inline-size; margin-block-start: 1rem; } /* the tiles set the width; a long notice wraps instead of widening the panel */
.worksite-panel .ws-signing .op-preview-review.is-compact { margin: 0; border: 0; padding: 0; background: none; }
.worksite-panel .ws-notices { display: grid; gap: .4rem; margin-top: .6rem; }
.worksite-panel .ws-notice { margin: 0; font-size: .85rem; }
.worksite-panel .op-c-details pre, .worksite-panel .op-preview-review pre { overflow-x: hidden; overflow-y: auto; overflow-wrap: anywhere; white-space: pre-wrap; }
.worksite-panel .hm-note.is-danger { color: var(--danger); }
.worksite-panel .hm-note.is-caution { color: var(--warn); }
.worksite-panel .hm-note.is-success { color: var(--ok); }
/* One media row, not a stacked column (R-23 §3.4): the 7.5rem square art beside the sentence. */
.worksite-completion { display: flex; align-items: center; gap: .9rem; margin: 0; padding: 0; }
.worksite-completion-lead { margin: 0; color: var(--body); font-size: .95rem; }
.worksite-completion-art { flex: 0 0 7.5rem; aspect-ratio: 1; width: auto; margin: 0; }
.worksite-completion-art .worksite-tile-art { background-position: 49% 91%; background-size: 158% auto; }
.worksite-modal-refusal { margin: 0; }

/* One tile per legal worksite, the current one pinned first and visually
   distinct (`.is-current`): no hover affordance, no Time/TC line (§3.2 --
   keeping the worksite costs nothing). Grid, not flex, so tiles wrap
   identically at narrow widths instead of the current tile alone reflowing
   differently -- drops from a wide row to 2 or 1 per row at narrower widths
   via `auto-fit`, never a fixed column count that would overflow at 375px.
   Flavour text and the Time/TC rows moved off the tile face into the hover
   tooltip and the confirm step respectively (worksite-modal rework), so this
   minmax is narrower than it once needed to be -- kept wide enough for the
   building art and rate block to stay legible. */
.worksite-tile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr)); gap: 0.9rem; }
.worksite-tile { position: relative; display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg3); padding: 0.75rem 0.7rem 0.7rem; text-align: center; }
.worksite-tile.is-current { border-color: var(--accent); background: color-mix(in srgb, var(--bg3) 82%, var(--accent) 8%); }
/* Every OTHER tile is a real <button> (FR-1721 follow-up: "zero clickability" --
   the tiles rendered but nothing could be picked). Reset button chrome back to
   plain-tile styling, then layer the interactive states on top: a hand cursor and
   a lifted border on hover/focus so the tile reads as clickable before it's ever
   touched, and a filled accent border plus a check glyph (same slot the CURRENT
   badge occupies on tile 1, so nothing shifts) once selected. `font: inherit`
   because a bare <button> otherwise inherits the UA's form-control font, not the
   page's -- the one thing button-reset CSS most commonly misses. */
.worksite-tile-option { font: inherit; color: inherit; cursor: pointer; width: 100%; }
.worksite-tile-option:hover, .worksite-tile-option:focus-visible { border-color: var(--accent); }
.worksite-tile-option:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.worksite-tile-option.is-selected { border-color: var(--ok); background: color-mix(in srgb, var(--bg3) 78%, var(--ok) 12%); box-shadow: inset 0 0 0 1px var(--ok); } /* S-7600: the chosen worksite is green */
.worksite-tile-option.is-selected .worksite-tile-check { background: var(--ok); }
.worksite-tile-badge, .worksite-tile-check { position: static; align-self: flex-end; margin-bottom: 0.35rem; line-height: 1; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--bg); background: var(--accent); border-radius: 999px; padding: 0.12rem 0.5rem; }
/* Reserve the status row on every tile so narrow worksite names never collide
   with Current or the selection marker. */
.worksite-tile-check { opacity: 0; transform: scale(0.6); transition: opacity 0.12s, transform 0.12s; line-height: 1; padding: 0.12rem 0.4rem; }
.worksite-tile-option.is-selected .worksite-tile-check { opacity: 1; transform: scale(1); }
/* S-5270: +100% land-type bonus pill, bottom-left on the worksite art. */
.worksite-tile-art { position: relative; }
.worksite-tile-pills { position: absolute; left: 0.4rem; bottom: 0.2rem; right: 0.4rem; display: flex; flex-direction: column; align-items: center; gap: 0.2rem; }
.worksite-tile-land-bonus { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.62rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--primary); background: var(--bg); border: 1px solid var(--primary); border-radius: 999px; padding: 0.14rem 0.5rem; box-shadow: 0 1px 3px color-mix(in srgb, var(--bg) 60%, transparent); }
.worksite-tile-land-bonus { display: inline-flex; align-items: center; gap: 0.25rem; }
.worksite-tile-pill-icon { width: 0.85rem; height: 0.85rem; flex: none; }
/* S-5290/S-5300: both pills share one look -- primary text and border on a solid ground. */
.worksite-tile-name { font-family: var(--font-display); font-weight: 700; font-size: 0.98rem; color: var(--heading); margin-bottom: 0.5rem; }
/* Official worksite art uses the source canvas's grounded registration and a
   zoom onto its smaller content rect; compact board glyphs remain separate. */
.worksite-tile-art { width: 100%; aspect-ratio: 1.286; border-radius: var(--radius); background-color: var(--bg); background-repeat: no-repeat; background-position: 49% 91%; background-size: 158% auto; overflow: hidden; }
.worksite-tile-art.is-missing { background-color: var(--bg); }
/* Rate block -- the single most decision-relevant number on the tile (the
   entire reason Ceres builds its own modal instead of linking to a reference
   that shows flavour text and no numbers): a tinted callout, not a plain
   line among others, so it reads as a distinct answer to "what would I earn"
   rather than one more data row. */
.worksite-tile-rate-block { margin: 0.55rem 0 0.45rem; padding: 0.4rem 0.3rem; border-radius: var(--radius); background: color-mix(in srgb, var(--accent) 12%, transparent); cursor: help; }
.worksite-tile-rate { font-size: 1.2rem; font-weight: 700; color: var(--heading); font-variant-numeric: tabular-nums; }
/* --body at reduced opacity, never --text-dim/--meta (FR-1721: --meta fails
   WCAG contrast on the alchemical and wood themes at any alpha). */
.worksite-tile-rate-unit { font-size: 0.68rem; font-weight: 500; color: var(--body); opacity: 0.78; }
/* Sub-line under the hero rate -- the tile's second-stage figure
   (projected/measured harvest PP, or "measured now" on the current tile).
   What makes the modal read as arithmetic instead of an oracle; see
   worksiteRateHtml()'s own comment in app.js. */
.worksite-tile-rate-sub { font-size: 0.64rem; color: var(--body); opacity: 0.62; font-variant-numeric: tabular-nums; }
/* Label/value data block -- now just the "Needs" row (Time and TC cost moved
   to the confirm step; flavour text moved into the tile's own hover tooltip,
   points 5/6/7 of the worksite-modal rework). `margin-top: auto` still pins
   it to the tile's bottom edge so every tile's data block lines up on the
   same baseline across a row. */
.worksite-tile-data { margin-top: auto; padding-top: 0.5rem; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 0.2rem; text-align: left; }
/* color-mix, not `opacity`, on this row: `opacity` would also fade the
   `<b>` value inside it since a parent's opacity composites its whole
   subtree -- a child `opacity: 1` cannot undo that. */
.worksite-tile-data-row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.6rem; font-size: 0.72rem; color: color-mix(in srgb, var(--body) 78%, transparent); }
.worksite-tile-data-row b { color: var(--heading); font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; }
/* The "Needs" row's resource icons -- `.resource-icon`'s own 1.3rem size (the
   board's compact Worksite column) is too large sitting inline in a one-line data
   row, so it's overridden smaller here; still large enough to read at a glance,
   and each icon keeps its own title tooltip from resourceIconHtml(). */
.worksite-tile-needs-icons { display: flex; align-items: center; gap: 0.15rem; }
.worksite-tile-needs-icons .resource-icon { width: 0.95rem; height: 0.95rem; margin-right: 0; }

.board-notices { display: grid; gap: 0.35rem; margin: 0.5rem 0; }
.ceres-notice { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.7rem; background: var(--bg3); color: var(--heading); border-left: 3px solid var(--border); padding: 0.35rem 0.55rem; font-size: 0.76rem; }
.ceres-notice-warning, .ceres-notice-advisory { border-left-color: var(--warn); }
.ceres-notice-hazard, .ceres-notice-refusal { border-left-color: var(--danger); }
.ceres-notice-verification { border-left-color: var(--accent); }
.ceres-notice-success { border-left-color: var(--ok); }
.notice-message { font-weight: 600; }
.notice-reason { color: var(--heading); font-size: 0.7rem; }
.op-state-notice { margin: 0; }
.op-state-notice .notice-message { font-weight: 600; }

html.ceres-lw-le-544 {
  /* `.slot-editor` itself carries no padding any more (item 14) -- this used
     to re-add inline padding here for narrow viewports, which reintroduced
     exactly the left/right gap item 14 removed (measured live: 7.1875px each
     side at 375px before this fix). The narrow-viewport inset now lives on
     `.editor-main` below, same place the base padding moved to. */
  .editor-main { padding-inline: 0.45rem; }
  /* Sticky positioning is for the wide-table horizontal-scroll case only --
     at this narrow a viewport the table itself reflows to a single column
     (no horizontal scroll to defend against), so both buttons go back to
     plain full-width flow here, same as before item 5/11. */
  .slot-group-controls { width: 100%; position: static; }
  .picker-source { width: 100%; margin-left: 0; }
  .candidate-delta { font-size: 0.7rem; }
  .slot-review { width: 100%; position: static; left: auto; }
}

html.ceres-lw-le-960 {
  .picker-context { max-width: 100%; }
}

.slot-editor { inline-size: 100%; max-inline-size: 100%; min-inline-size: 0; }
.editor-band {
  box-sizing: border-box; inline-size: 100%; max-inline-size: 100%;
  block-size: var(--eos-band-height, 18.5125rem); min-block-size: var(--eos-band-height, 18.5125rem);
  --eos-facts: .45rem; --eos-controls: 1.6rem; --eos-cards: 12.45rem; --eos-footer: 2.7rem; /* S-5730 flipped: the +1.25rem sits at the top */
  background: radial-gradient(ellipse at 16% 75%, color-mix(in srgb, var(--accent) 13%, #13111d), #13111d 65%);
}
.editor-band[data-eos-render-error] { block-size: var(--eos-band-height, 18.5125rem); min-block-size: var(--eos-band-height, 18.5125rem); }
.eos-narrow .editor-band { --eos-facts: .45rem; --eos-controls: 3.6rem; --eos-footer: 4.7rem; }
.editor-band > .editor-main { position: absolute; inset: 0; padding: 0 .8rem; display: block; }
.editor-band .slot-editor-body { position: absolute; inset: var(--eos-facts) .8rem auto; block-size: calc(var(--eos-controls) + var(--eos-cards)); }
.editor-band .slot-group { block-size: 100%; }
.editor-band .slot-group-controls { --plot-control-pipe-gap: .7rem; block-size: var(--eos-controls); margin: 0; align-content: start; align-items: start; gap: .25rem; }
.editor-band .slot-grid {
  box-sizing: border-box; block-size: var(--eos-cards); margin: 0; padding-top: 2.5rem; padding-bottom: .35rem;
  padding-left: calc(var(--worksite-x) + var(--worksite-w) + 1.05rem); /* IR-4710: was a hard 24.7625rem */ overflow: visible; flex-wrap: nowrap; gap: 1.05rem; min-inline-size: 0; max-inline-size: 100%;
}
.editor-band .slot-grid > * { flex-shrink: 0; }
.editor-band .slot-grid.is-textonly { flex-wrap: nowrap; }
.editor-band .slot-grid.is-textonly .slot-tile-cell { inline-size: var(--eos-slot-w); }
.editor-band .slot-editor-footer {
  box-sizing: border-box; position: absolute; inset: calc(var(--eos-facts) + var(--eos-controls) + var(--eos-cards)) .8rem auto; block-size: var(--eos-footer);
  min-block-size: 0; margin: 0; padding-top: .1rem; flex-wrap: nowrap; align-items: start; gap: .4rem;
}
.editor-band .editor-base-pp-row { position: absolute; top: .1rem; left: calc(var(--worksite-x) + var(--worksite-w) + 1.05rem); flex: none; margin: 0; gap: 1.05rem;
  /* S-5250: same height as .worksite-change-btn, badges centred in it, so both share one centre line */
  block-size: 1.7625rem; align-items: center; }
.editor-band .editor-base-pp-cell { flex: 0 0 6.7rem; inline-size: 6.7rem; }

.eos-status button {
  font: inherit; font-size: .68rem; border: 1px solid var(--border-bright); border-radius: var(--radius);
  padding: .18rem .35rem; background: var(--bg3); color: var(--heading); cursor: pointer;
}
.eos-scenic-veil, .eos-worker-cover { position: absolute; inset: 0; pointer-events: none; }
.eos-scenic-veil {
  z-index: 2; overflow: hidden;
  background: radial-gradient(ellipse at 16% 75%, color-mix(in srgb, var(--accent) 13%, #13111d), #13111d 65%);
}
.eos-worker-cover {
  z-index: 3; left: calc(var(--worksite-x) + var(--worksite-w) + 1.05rem);
  background: linear-gradient(90deg, transparent, #13111d 1.2rem, #13111d);
}
.eos-worker-cover::before { content: ''; position: absolute; inset: 0 0 0 1.2rem; background: #13111d; }
.eos-plot-identity { position: absolute; left: 1.2rem; top: 1rem; color: #e6deeb; font-size: .78rem; letter-spacing: .04em; }
.eos-horizon { position: absolute; left: 1rem; width: 23rem; bottom: var(--worksite-lift); height: 1px; background: linear-gradient(90deg, transparent, #b8a37a, transparent); opacity: .55; }
.editor-band > .eos-close { position: absolute; inset: var(--eos-facts, .45rem) 0.8rem auto auto; z-index: 5; }

/* S-14350 (owner ruling 2026-09-19): the expanded plot's and slot picker's
   Close buttons -- icon-only, red chevron-up on a semi-transparent ground.
   FR-5064 exception: not a ceres-btn treatment. */
.chevron-close { inline-size: 2rem; block-size: 2rem; padding: 0; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 5px; color: var(--danger); background: color-mix(in srgb, var(--bg) 55%, transparent); backdrop-filter: saturate(120%) blur(2px); cursor: pointer; transition: background .2s ease, color .2s ease; }
.chevron-close:hover, .chevron-close:focus-visible { background: color-mix(in srgb, var(--danger) 18%, color-mix(in srgb, var(--bg) 55%, transparent)); }
.chevron-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chevron-close svg { inline-size: 1rem; block-size: 1rem; }
.close-all-chevron-stack { display: grid; justify-items: center; align-content: center; block-size: 1rem; }
.close-all-chevron-stack svg { display: block; inline-size: .85rem; block-size: .85rem; }
.close-all-chevron-stack svg + svg { margin-block-start: -.42rem; }
/* IR-8318: keep the bulk control hidden; .chevron-close sets display explicitly. */
.close-all-btn[hidden] { display: none; }

.eos-status { position: absolute; z-index: 4; inset: auto .8rem .2rem auto; display: flex; align-items: center; gap: .4rem; max-inline-size: calc(100% - 1.6rem); color: var(--heading); font-size: .72rem; }
.eos-status-text:not(:empty) { background: var(--bg3); padding: .15rem .3rem; }
.eos-status [hidden], .eos-scenic-veil[hidden], .eos-worker-cover[hidden] { display: none; }
.editor-band[data-plot-state="preparing"] [data-reveal-group]:not([data-eos-permanent-frame]) { visibility: hidden; }
.editor-band[data-plot-state="preparing"] [data-terrain-affinity="editor"] { visibility: hidden; }
.eos-art-fallback { display: flex; flex-direction: column; justify-content: center; padding: .4rem; background: linear-gradient(145deg, var(--bg3), var(--bg)); color: var(--heading); }
@media (prefers-reduced-motion: reduce) {
  .editor-band *, .editor-band *::before, .editor-band *::after { animation: none !important; transition: none !important; }
}
@media (forced-colors: active) {
  .eos-portrait-fill.eos-portrait-pop-in { animation: none !important; }
}

.editor-band .slot-zero-state { position: absolute; left: 0; top: calc(var(--eos-controls) + 1rem); max-inline-size: 100%; }

.eos-status-text { min-inline-size: 0; max-inline-size: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ==========================================================================
   R-141 — the Praetoria bar.  Ceres v0.8.98.
   ==========================================================================
   His brief, verbatim across five messages: "top notch, ultra modern, ultra
   beautiful ... blank slate ... Functional and beautiful", "filters are 100%
   irrelevant for the header", "input fields and header exclusive buttons must
   aligne and be precisely the same size to the pixel", "Remove totals-strip
   entirely from the header. it always forces us to make compromise decisions.
   I want a top not ultra design for the header and not a text desert", and
   "I asked for a super beautiful design with fitting background image that
   ultra fits the splinterlands land theme".

   THE STRUCTURE COMES FROM HIS OWN TWO REFERENCES (S-173), read live at 1920px:
   splinterlands.com runs a ~68px chrome bar, peakmonsters.com a ~48px one, and
   in BOTH the artwork is a band and the bar is a thin strip of chrome. Every
   previous Ceres attempt built one thick band that was simultaneously chrome and
   hero -- which is why they read as admin consoles. He granted "you can feel
   free to be less compact", so this is 80px: half the outgoing 121px proposal
   and a third under the current header.

   THE ARTWORK IS REAL. `assets/land/terrain-backgrounds/` already ships 140
   Praetoria plates that the board renders per plot; this uses one of them as the
   bar's own ground rather than inventing a texture or committing an image.
   Legibility over photography is solved by scrimming ONLY the two ends where
   controls sit, so the middle of the bar stays clean landscape -- the thing he
   asked for and the thing the rejected build replaced with a flat dark fill.

   PIXEL PARITY IS A STATED ACCEPTANCE CRITERION, not a preference: --hdr-ctl is
   the single height every header control resolves to, and they share one
   baseline via `align-items: center` on one flex row. Nothing here sets its own
   height. */
:root { --hdr-h: 80px; --hdr-ctl: 34px; }

header.board-header {
  position: sticky; top: 0; z-index: 50; /* IR-6930, matches the base rule above */
  display: flex; align-items: center; gap: 1rem;
  block-size: var(--hdr-h); min-block-size: var(--hdr-h);
  /* Right padding clears the Iris theme picker, which is a FIXED widget that docks
     itself against this header (app.js measures its real box) and carries 48px of
     its own chrome (canonical.css:20-39). It is not ours to restyle -- 'header
     exclusive buttons' in his S-166 ruling excludes it -- so the header yields the
     space instead of letting the two collide. */
  /* Right padding is SET FROM MEASUREMENT by centerThemePicker() in app.js (S-194):
     --hdr-pad-right is the picker's real occupied width plus a gap. The 4.25rem here
     is only the pre-script fallback for the first paint. */
  padding: 0 var(--hdr-pad-right, 4.25rem) 0 1.15rem;
  /* Fixed height, not padding-derived: the plot table below is virtualised and
     measures its own rows, and R-138's reveal centres an opened plot against the
     viewport -- a header that changes height with its content disturbs both. */
  background-image:
    /* IR-4000: a light backing behind the logo (left) and the buttons (right) only --
       the scene stays visible across the header. */
    linear-gradient(90deg, color-mix(in srgb, var(--bg) 70%, transparent) 0%, color-mix(in srgb, var(--bg) 35%, transparent) 12%, transparent 22%),
    linear-gradient(270deg, color-mix(in srgb, var(--bg) 70%, transparent) 0%, color-mix(in srgb, var(--bg) 35%, transparent) 14%, transparent 26%),
    var(--hdr-scene, url('assets/web/land/terrain-backgrounds/plains_natural.webp'));
  background-size: auto, auto, cover;
  background-position: center, center, center 62%;
  background-repeat: no-repeat;
}
/* The bar never exceeds 1920px of content, his hard break (S-1). The image is
   `cover`, so it fills whatever width the viewport gives it without stretching. */
.board-header > * { min-inline-size: 0; }

/* Logo: 56px, down from 75px. In both references the mark is small and hard
   left; the wordmark is not the header's subject. The four theme-keyed
   `content: url()` swaps below are untouched -- this only changes the render
   height, so his commissioned per-theme artwork still switches with the theme.
   THIS IS THE DEFECT S-172 NAMED: every mockup lane typed the word CERES
   instead of loading these files, because it built outside the app. */
/* S-178, his instruction: "make sure the Ceres logo uses the full height, move the
   version text to the bottom right side of the logo, so it does not block space
   below it". The first pass rendered it at 56px in an 81px band and the leftover
   25px simply went empty -- his references keep a small mark because a nav row
   fills the rest of their bar, and this header has no nav row. */
.board-logo { flex: 0 0 auto; align-self: stretch; position: relative; display: inline-flex; align-items: center; line-height: 0; }
.board-logo-img { display: block; block-size: var(--hdr-h); inline-size: auto; }
/* Absolutely positioned, so it costs ZERO layout -- which is the mechanism that lets
   the image grow into the strip it used to reserve. Restores his own 2026-08-21
   ruling ("Move the version number to the bottom right of the logo") that the R-141
   rewrite dropped; all four theme logos leave their lower-right quadrant empty for
   exactly this. */
.board-logo-link { display: block; line-height: 0; border-radius: calc(var(--radius) - 3px); transition: filter .15s ease; }
.board-logo-link:hover { filter: brightness(1.12) drop-shadow(0 0 6px color-mix(in srgb, var(--primary) 45%, transparent)); }
.board-logo-link:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.board-version { position: absolute; right: 0.15rem; bottom: 0.3rem; margin: 0; font-size: 0.58rem; line-height: 1; opacity: 0.72; letter-spacing: 0.02em; pointer-events: none; }

/* One flex row, one baseline, one height. */
/* The outgoing header stacked two rows in a grid column; this is one row, so the
   wrapper's `flex-direction: column` (ceres.css:104-112) must be overridden here,
   not left to cascade order -- and `justify-content` only pushes a child that is
   not itself growing, hence `flex: 0 0 auto` on the row. */
.board-header-rows { flex: 1 1 auto; display: flex; flex-direction: row; justify-content: flex-end; align-items: center; min-inline-size: 0; gap: 0; }
.board-header-row { flex: 0 0 auto; display: flex; flex-direction: row; align-items: center; gap: 0.35rem; inline-size: auto; max-inline-size: none; }

.board-header .player-switch { position: relative; display: inline-flex; align-items: center; gap: 0.4rem; }
.board-header .player-input {
  box-sizing: border-box;
  block-size: var(--hdr-ctl); min-block-size: var(--hdr-ctl); max-block-size: var(--hdr-ctl);
  line-height: 1; margin: 0; align-self: center;
}
.board-header .player-input { inline-size: 11rem; padding: 0 0.6rem; font-size: 0.82rem; background: color-mix(in srgb, var(--bg2) 75%, transparent); }
/* S-7870 / S-8440 (owner: "as tall as we can justify, left of the name field. Maybe slightly overlap? Something cool
   looking."): a 52px medallion (the header is 80px, the controls 34px) in the theme's main colour -- ring, soft halo
   and glow -- overlapping the field's left edge a little; the field pads its text clear of it. Theme tokens only. */
.board-header .player-avatar > img { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; object-fit: cover; } /* IR-5520: the avatar-builder layers, stacked in order */
.board-header .player-avatar { position: relative; z-index: 2; flex: none; box-sizing: border-box; inline-size: 60px; block-size: 60px; margin-inline-end: -18px; /* S-8460 */ overflow: hidden; appearance: none; padding: 0; border: 2px solid var(--primary); border-radius: 50%; background: var(--bg2); color: inherit; cursor: pointer; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent), 0 4px 14px color-mix(in srgb, var(--shadow) 60%, transparent), 0 0 18px color-mix(in srgb, var(--primary) 35%, transparent); transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; }
.board-header .player-avatar::before { content: ''; position: absolute; inset: 0; z-index: 1; border-radius: inherit; background: transparent; pointer-events: none; transition: background-color .2s ease; }
.board-header .player-avatar:hover::before, .board-header .player-avatar:focus-visible::before { background: color-mix(in srgb, var(--primary) 40%, transparent); }
.board-header .player-avatar[data-refresh-state="loading"]::before { background: color-mix(in srgb, var(--warn) 60%, transparent); }
.board-header .player-avatar[data-refresh-state="success"]::before { background: color-mix(in srgb, var(--ok) 60%, transparent); }
.board-header .player-avatar::after { content: '↻'; position: absolute; z-index: 2; inset: 0; display: grid; place-items: center; inline-size: 100%; block-size: 100%; border: 0; border-radius: 50%; background: transparent; color: var(--primary); font-family: inherit; font-size: 2.8rem; font-weight: 700; line-height: 1; text-shadow: 0 1px 3px var(--shadow); opacity: 0; transition: opacity 0.15s ease; pointer-events: none; }
.board-header .player-avatar:hover { transform: scale(1.06) rotate(-3deg); }
.board-header .player-avatar:hover::after, .board-header .player-avatar:focus-visible::after { opacity: .85; }
.board-header .player-avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.board-header .player-avatar[data-refresh-state="loading"] { animation: avatar-refresh-border 0.9s ease-in-out infinite; }
.board-header .player-avatar[data-refresh-state="loading"]::after { opacity: 0; }
.board-header .player-avatar > .avatar-refresh-spinner { display: none; position: absolute; z-index: 2; inset: 12%; width: 76%; height: 76%; opacity: .8; pointer-events: none; }
.board-header .player-avatar[data-refresh-state="loading"] > .avatar-refresh-spinner { display: block; }
.board-header .player-avatar[data-refresh-state="success"]::after { content: '✓'; opacity: 1; color: var(--ok); }
.board-header .player-avatar[data-refresh-state="error"]::after { content: '!'; opacity: .9; color: var(--danger); }
.board-header .player-avatar[hidden] { display: none; }
@keyframes avatar-refresh-border {
  0%, 100% { border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent), 0 4px 14px color-mix(in srgb, var(--shadow) 60%, transparent), 0 0 18px color-mix(in srgb, var(--primary) 35%, transparent); }
  50% { border-color: var(--accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 35%, transparent), 0 4px 18px color-mix(in srgb, var(--accent) 45%, transparent); }
}
.board-header .player-switch:has(.player-avatar:not([hidden])) .player-input { padding-inline-start: calc(0.6rem + 14px); } /* S-8460: clears the larger overlap */
@media (prefers-reduced-motion: reduce) { .board-header .player-avatar { transition: none; animation: none !important; } .board-header .player-avatar:hover { transform: none; } }

/* Emojicons (S-177): the hand-drawn SVG paths are gone from index.html. Sized off
   the button, not off the page font, so a theme's type scale cannot resize them. */
.board-header [hidden] { display: none !important; }
/* An explicit emoji stack: the glyphs are the instruction (S-177), so they must not
   depend on whichever font the page body happens to resolve. Verification saw tofu
   boxes in headless Chromium, which lacks a colour-emoji font. */
.board-header .btn-emoji { font-size: 1.05rem; line-height: 1; filter: saturate(1.05);
  font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', 'Segoe UI Symbol', sans-serif; }

@media (forced-colors: active) {
}

/* Detached by R-141: rows.js still renders the five figures into this element and
   nothing else changed, but it is out of the header and hidden until he rules on
   where they belong. */
.totals-strip-detached[hidden] { display: none; }

/* The strip of nothing above the table, traced to source rather than guessed:
   `.board-notices` keeps a .5rem block margin on a board with zero notices, and
   because body is a flex column those margins do not collapse -- 16px of empty
   space on every load. Notices, when present, still show in full. */
.board-notices { margin: 0; }
.board-notices:empty { display: none; }
/* S-2100: the quiet phase label -- one muted line, no motion, nothing over the board. */

/* ==========================================================================
   R-144 — the picker says which tile it is for.
   ==========================================================================
   His request: "Grey out all tiles but the selected/active tile. All greyed out tiles
   must still be clearly visible but clearly fade into the background. must look
   beautiful."

   HIS SECOND SENTENCE RULES OUT THE OBVIOUS IMPLEMENTATION. A plain `opacity: .35`
   fails both halves at once: it dims the card ART past reading while leaving the
   tile's own border and elevation at full strength — so the thing that recedes is the
   content and the thing that stays forward is the chrome, which is backwards.

   The recession is therefore taken off the SURFACE, not off the content: saturation
   drops, the elevation the tile normally has (S-176) is withdrawn so it lies flat, and
   the border quietens. Opacity moves only a little, because "clearly visible" is a
   requirement. The active tile is LIFTED rather than merely left alone — a difference
   the eye reads as "one is nearer" is stronger and calmer than one it reads as "one is
   brighter". */
.slot-tile.is-picker-dimmed {
  filter: saturate(0.2) brightness(0.86); /* S-4990: no opacity -- stays at 1 */
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
  box-shadow: none;
  transition: filter 0.18s ease, opacity 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.slot-tile.is-picker-focus {
  filter: none;
  opacity: 1;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5),
              inset 0 1px 0 rgba(255, 255, 255, 0.18);
  transition: filter 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
  z-index: 1;
}
.picker-focus-ring { position: absolute; inset: 0; z-index: 4; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.picker-focus-ring path { fill: none; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
/* S-2520 (2): theme tokens only, no white. The gradients live in each ring's <defs>; the
   stops take their colours here so every Iris theme colours its own ring. The comet = the
   arc (30% lit, full opacity) plus two longer, fainter dash layers whose dash start is pushed
   back along the path by --picker-ring-shift, so they trail behind the head and fade. */
.picker-ring-stop-a { stop-color: var(--accent); }
.picker-ring-stop-b { stop-color: var(--primary); }
.picker-ring-stop-rail-a { stop-color: color-mix(in srgb, var(--primary) 55%, var(--bg)); }
.picker-ring-stop-rail-b { stop-color: color-mix(in srgb, var(--accent) 45%, var(--bg)); }
.picker-focus-ring-rail { stroke-width: calc(4 * var(--eos-effect-unit)); }
.picker-focus-ring-glow { stroke-width: calc(10 * var(--eos-effect-unit)); opacity: 0.22; filter: blur(calc(1.5 * var(--eos-effect-unit))); }
.picker-focus-ring-tail-far { stroke-width: calc(4 * var(--eos-effect-unit)); opacity: 0.14; }
.picker-focus-ring-tail-near { stroke-width: calc(4 * var(--eos-effect-unit)); opacity: 0.38; }
.picker-focus-ring-arc { stroke-width: calc(4 * var(--eos-effect-unit)); }
.picker-focus-ring-glow, .picker-focus-ring-tail, .picker-focus-ring-arc {
  /* S-2520 (3): one lap is 2.6 s (30 % slower than the 2.0 s he saw). */
  animation: picker-ariadne-loop 2600ms linear infinite;
  animation-delay: var(--picker-ring-delay, 0ms);
}
/* px, not unitless: a unitless calc() on stroke-dashoffset is dropped by Chromium and the loop
   then sits still (probed 2026-09-08, S-2520); in SVG 1px == 1 user unit == 1 pathLength unit. */
@keyframes picker-ariadne-loop {
  from { stroke-dashoffset: calc(var(--picker-ring-shift, 0) * 1px); }
  to { stroke-dashoffset: calc(var(--picker-ring-shift, 0) * 1px - 100px); }
}
/* The dim must never eat a state the plot depends on: staged, cooling and
   pending-removal paint with `outline` INSIDE the border box, and outline is not
   affected by `filter` on the same element the way a border is — but the colour still
   needs to survive desaturation, so those three keep full saturation. */
.slot-tile.is-picker-dimmed.is-staged,
.slot-tile.is-picker-dimmed.is-cooling,
.slot-tile.is-picker-dimmed.is-pending-removal { filter: brightness(0.9); }

@media (prefers-reduced-motion: reduce) {
  .slot-tile.is-picker-dimmed, .slot-tile.is-picker-focus { transition: none; }
  .picker-focus-ring-glow, .picker-focus-ring-tail, .picker-focus-ring-arc { display: none; animation: none; }
  .picker-focus-ring-rail { stroke: var(--primary); }
}
@media (forced-colors: active) {
  .slot-tile.is-picker-dimmed { filter: none; opacity: 1; }
  .slot-tile.is-picker-focus { box-shadow: none; outline: 3px solid Highlight; outline-offset: -3px; }
  .picker-focus-ring { display: none; }
}
[data-eos-motion="disabled"] .picker-focus-ring-glow,
[data-eos-motion="disabled"] .picker-focus-ring-tail,
[data-eos-motion="disabled"] .picker-focus-ring-arc { display: none; animation: none; }
[data-eos-motion="disabled"] .picker-focus-ring-rail { stroke: var(--primary); }

/* FR-1945: the header scene drifts with the page scroll (CSS scroll-driven
   animation on the root scroller, no JS). Only the third layer (the scene)
   moves; the two side gradients stay put. Still image under reduced motion. */
@keyframes hdr-scene-drift {
  from { background-position: center, center, center 40%; }
  to   { background-position: center, center, center 80%; }
}
/* IR-5330: a worksite under construction -- no resource, no amount; the build's name and a live countdown. */
.harvest-bar.is-building .harvest-bar-mid { inline-size: auto; padding-inline: .3rem .5rem; }
.harvest-bar-build-ic { display: inline-grid; place-items: center; width: 2rem; height: 2rem; color: var(--warn); }
.harvest-bar-build-ic .status-hourglass { height: 1.7rem; width: auto; display: block; } /* S-8060: house-style hourglass SVG (14x18 box) */
/* S-8400 (owner: "Replace the hour glass in the building worksite harvest box ... for our official splinterlands
   spinner. Also lower the bottom padding"): the building box (24 h change and a first clear) shows the official
   spinner (.spinner, S-5880) and, with no capacity edge, the same padding top and bottom. */
.harvest-bar-build-ic .spinner { display: block; width: 1.9rem; height: 1.9rem; vertical-align: 0; }
.harvest-bar.is-building .harvest-bar-lbl { color: var(--heading); }
.harvest-bar.is-building .harvest-bar-build-name { display: inline-flex; align-items: center; gap: .25rem; }
.harvest-bar.is-building .harvest-bar-build-name .resource-icon { inline-size: .9rem; block-size: .9rem; }
.harvest-bar.is-building .harvest-bar-build-eta { display: inline-flex; align-items: center; gap: .35rem; font-size: .86rem; font-weight: 800; color: var(--warn); block-size: auto; line-height: 1.3; }
.harvest-bar.is-building .harvest-bar-build-eta.is-done { font-size: .85rem; color: var(--ok); }
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    header.board-header {
      animation: hdr-scene-drift linear both;
      animation-timeline: scroll(root block);
    }
  }
}

/* IR-4200: a card staked beyond the plot's powered worker slots stays visible, dimmed and flagged. */
.slot-tile.is-unpowered .eos-portrait-fill { filter: saturate(0.6); }
.slot-flag-unpowered { color: var(--warn); }
/* IR-4230: the red no-power overlay fades in with its card, on the same mark and timing as the
   card name (is-caption-pending is removed when the picture starts to fade in). */
.slot-tile .slot-no-power { transition: opacity 420ms cubic-bezier(0.25, 0.1, 0.25, 1); }
.slot-tile.is-caption-pending .slot-no-power { opacity: 0; }
/* S-5010: flags sit above the red no-power overlay (both were z-index 2; the overlay came later). */
.slot-tile.is-unpowered .slot-flag { z-index: 3; }
.slot-tile.is-unpowered { position: relative; }
.slot-tile.is-unpowered .slot-no-power { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--danger) 45%, transparent); border-radius: inherit; pointer-events: none; z-index: 2; } /* S-4950: the game's red semi-transparent overlay with its no-power icon centred */
.slot-tile.is-unpowered .slot-no-power-icon { width: 38%; height: auto; filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--shadow) 60%, transparent)); }
.editor-base-pp-cell.is-unpowered b { color: color-mix(in srgb, var(--danger) 65%, var(--hi)); text-decoration: line-through; }

/* ---------- S-5390/S-5450 harvest: shared modal, header button, plot bar ----------
   Global canonical tokens only. Capacity colour: ok < 70%, warn 70-99%, danger at 100%. */
/* S-5640: the header Harvest button is one of the S-177 header family (the .board-header rules);
   only the sickle is sized here, like the family's emoji icons. */
.board-header .harvest-header-btn svg { inline-size: 1.05rem; block-size: 1.05rem; }
/* Harvest bar v3 (V3-2, S-5470 §22), S-5630: inside .slot-editor-footer, vertically centred on
   the Base PP badge line (top .1rem, 1.7625rem tall) and horizontally centred under the worksite
   column (the footer starts at --worksite-x). Absolute, so the Base PP badges never shift.
   No size transitions; the amount box has a fixed width (--amt-ch), so ticking never moves it. */
/* S-5670: one padding on all four sides; the bottom inset sits above the 4px capacity edge, so the
   buttons clear the edge by the same distance they keep from the sides. Fixed height. */
.editor-band { --hv-pad: .35rem; --hv-edge: 4px; --hv-btn: 2.15rem; --hv-h: calc(var(--hv-btn) + 2 * var(--hv-pad) + var(--hv-edge) + 2px); }
.slot-editor-footer > .harvest-bar { position: absolute; z-index: 3; top: calc(.1rem + 1.7625rem / 2); left: calc(var(--worksite-w) / 2); transform: translate(-50%, -50%); block-size: var(--hv-h); display: inline-flex; align-items: center; gap: .55rem; padding: var(--hv-pad) var(--hv-pad) calc(var(--hv-pad) + var(--hv-edge)); box-sizing: border-box; border: 1px solid var(--border-bright); border-radius: var(--radius); background: color-mix(in srgb, var(--bg) 86%, transparent); box-shadow: 0 4px 14px color-mix(in srgb, var(--shadow) 50%, transparent); overflow: hidden; font-variant-numeric: tabular-nums; line-height: 1.1; white-space: nowrap; transition: none; }
/* S-8360 (owner: "place the harvest box for uncleared plots in the center left of the tile ... the same padding top
   and bottom"; his pick: mid-height, just right of the splinter icons). Uncleared plots only: the box's centre sits on
   the icon column's centre line (facts + controls + 7.2rem, see .terrain-affinity[data-terrain-affinity="editor"]),
   measured from the footer's top; no capacity edge here, so the bottom padding equals the top. */
/* S-8620 (owner: "adjust the padding bottom"): the staged-build box has no capacity edge either -- same padding top and bottom. */
.slot-editor-footer > .harvest-bar.is-staged-build { padding: var(--hv-pad); block-size: calc(var(--hv-btn) + 2 * var(--hv-pad) + 2px); }
.slot-editor-footer > .harvest-bar.is-no-worksite { top: calc(7.2rem - var(--eos-cards, 12.45rem)); left: 2.75rem; transform: translateY(-50%); padding: var(--hv-pad); block-size: calc(var(--hv-btn) + 2 * var(--hv-pad) + 2px); }
.harvest-bar-mid { display: inline-flex; align-items: center; justify-content: center; gap: .45rem; padding: 0 .15rem; font-size: 1.4rem; inline-size: calc(2rem + .45rem + var(--amt-ch, 8ch)); } /* IR-4680: width reserved on the group (no bounce), icon+text centred inside it */
.harvest-bar-res { width: 2rem; height: 2rem; object-fit: contain; display: block; }
.harvest-bar-stack { display: flex; flex-direction: column; flex: none; }
.harvest-bar-lbl { font-size: .66rem; font-weight: 700; color: var(--body); letter-spacing: .02em; }
.harvest-bar-amt { font-size: 1.4rem; line-height: 1.1; font-weight: 800; color: var(--heading); font-variant-numeric: tabular-nums; }
.harvest-bar-stack { font-size: 1.4rem; }
.harvest-bar-lbl { font-size: .66rem !important; }
.harvest-bar-state { padding: 0 .4rem; font-size: .8rem; font-weight: 700; color: var(--body); }
.harvest-bar-btn { position: relative; flex: none; }
.harvest-bar-btn svg { width: 1.2rem; height: 1.2rem; }
/* S-8350: the Power Core quick-stage button next to Build on an unpowered, uncleared plot. */
.harvest-bar-power img { width: 1.25rem; height: 1.25rem; object-fit: contain; }
.harvest-bar-go.is-short::after { content: ''; position: absolute; top: .12rem; right: .12rem; width: .55rem; height: .55rem; border-radius: 50%; background: var(--danger); box-shadow: 0 0 0 2px var(--bg2); }
.harvest-bar-edge { position: absolute; left: 0; right: 0; bottom: 0; height: var(--hv-edge, 4px); background: color-mix(in srgb, var(--body) 16%, transparent); }
.harvest-bar-edge i { display: block; height: 100%; width: var(--pct, 0%); background: var(--ok); }
/* S-5890: below ~4% the bar sat inside the rounded bottom-left corner and was clipped away; any capacity > 0 now shows a stub past the corner. */
.harvest-bar.has-cap .harvest-bar-edge i { min-width: 10px; } /* S-5900: 10px, his value */
.harvest-bar[data-level="warn"] .harvest-bar-edge i { background: var(--warn); }
.harvest-bar[data-level="danger"] .harvest-bar-edge i { background: var(--danger); }
.slot-editor-footer > .harvest-bar[data-level="danger"] { border-color: color-mix(in srgb, var(--danger) 60%, var(--border-bright)); }

.harvest-overlay[hidden] { display: none; }
.harvest-overlay { position: fixed; inset: 0; z-index: 1200; display: flex; align-items: center; justify-content: center; padding: 1rem; background: color-mix(in srgb, var(--shadow) 62%, transparent); }
.harvest-panel { box-sizing: border-box; width: fit-content; min-width: min(64rem, calc(var(--ceres-vw, 1vw) * 100 - 2rem)); max-width: calc(var(--ceres-vw, 1vw) * 100 - 2rem); max-height: calc(var(--ceres-vh, 1dvh) * 100 - 2rem); /* IR-5590 (owner: "Changes modal is broken on a large screen. top and bottom are not visible."): on a large screen the page is zoom-scaled (index.html), so raw 100vw/100dvh grew with the zoom and overflowed; the scale-aware --ceres-vw/--ceres-vh keep every house modal inside the window */ display: flex; flex-direction: column; border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg2); color: var(--body); box-shadow: 0 18px 60px color-mix(in srgb, var(--shadow) 70%, transparent); overflow: hidden; font-variant-numeric: tabular-nums; }
.hm-headr { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.1rem 1.4rem .9rem; border-bottom: 1px solid var(--border); }
.hm-headr h2 { margin: 0; font-size: 1.5rem; font-weight: 800; color: var(--heading); }
.hm-headr p { margin: .25rem 0 0; font-size: .95rem; color: var(--body); }
.harvest-x { flex: none; width: 2.4rem; display: grid; place-items: center; }

.harvest-x svg { width: 1.1rem; }

.hm-body { flex: 1; min-height: 0; overflow: auto; padding: 1.1rem 1.4rem 1.3rem; display: flex; flex-direction: column; gap: 1.1rem; }
.hm-body > * { flex-shrink: 0; }
/* S-6370: a long list scrolls; it never squashes the status line */
.hm-foot { display: flex; align-items: center; justify-content: space-between; gap: .6rem 1rem; flex-wrap: wrap; padding: .9rem 1.4rem; border-top: 1px solid var(--border); background: var(--bg3); }
.hm-note { font-size: .9rem; color: var(--body); }
.hm-btns { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: .6rem; margin-left: auto; }
.hm-btn.is-primary { min-width: 11rem; }

.hm-btn.is-primary.is-off { max-width: 100%; }

/* IR-5580 (owner: "Hover? padding between buttons who touch each other?"): Stage change and Sign sit inside their own
   wrapper, which the footer's gap never reached -- the wrapper now spaces them like the rest of the row. Hover is
   scoped to the footer itself (a clear tint change, not only a brightness shift). */
.hm-foot .worksite-modal-sign { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
.hm-foot .worksite-modal-close { transition: background-color .15s ease, border-color .15s ease, filter .15s ease; }

.hm-foot .worksite-modal-close:hover { border-color: var(--accent); background: color-mix(in srgb, var(--primary) 22%, var(--bg2)); }

/* IR-6500 (2026-09-20): shared desktop+mobile, formerly unscoped in mobile-plot.css --
   the worksite modal footer/Sign row are today's rendering on every tier (S-7910
   frozen look), not a mobile-only leak; moved here so mobile-plot.css can require
   every one of its own selectors to be viewport-scoped without changing this. */
#worksite-modal-overlay .hm-foot { display: flex; flex-wrap: wrap; block-size: auto; flex-shrink: 0; padding-block: 1rem; }
#worksite-modal-overlay .hm-foot > .hm-note { flex: 1 0 100%; max-inline-size: none; }
#worksite-modal-overlay .hm-foot > .hm-btns { inline-size: 100%; margin-inline-start: 0; }
#worksite-modal-overlay .worksite-modal-sign { display: flex; gap: 0.65rem; inline-size: 100%; }
#worksite-modal-overlay .worksite-modal-sign > button { flex: 1; }
#worksite-modal-overlay .worksite-modal-sign { flex-wrap: nowrap; }
#worksite-modal-overlay .worksite-modal-sign > button { flex: 1 1 0; min-width: 0; min-inline-size: 0; white-space: normal; }
#worksite-modal-overlay .worksite-modal-sign > .wallet-sign-pair { flex: 1 1 0; min-inline-size: 0; }
#worksite-modal-overlay .worksite-modal-sign > .wallet-sign-pair > .hm-btn { flex: 1 1 0; min-width: 0; min-inline-size: 0; white-space: normal; }

.worksite-modal-panel .hm-btn.worksite-modal-stage { min-width: 11rem; }
/* S-8570: a staged (not yet signed) worksite build is drawn on the plot, translucent until it is built. */
.editor-band.is-staged-worksite .editor-worksite-art { opacity: .55; }
.hm-scope { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.hm-segs { display: inline-grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; }
.hm-select { font: inherit; font-size: .92rem; font-weight: 700; padding: .4rem .7rem; margin-left: .4rem; border: 1px solid var(--border-bright); border-radius: calc(var(--radius) - 3px); background: var(--bg3); color: var(--heading); }
.hm-checks { display: flex; flex-wrap: wrap; gap: .35rem .9rem; }
.hm-check { display: inline-flex; align-items: center; gap: .3rem; font-size: .9rem; font-weight: 700; color: var(--heading); }
.hm-lbl { font-size: .85rem; font-weight: 700; color: var(--body); letter-spacing: .02em; }
.hm-dim { font-size: .9rem; color: var(--body); }
.hm-card { border: 1px solid var(--border); border-radius: var(--radius); background: color-mix(in srgb, var(--bg) 40%, var(--bg2)); padding: 1rem 1.1rem; }
.hm-sec-h { margin: 0 0 .6rem; font-size: .8rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--heading); }
.hm-receipt { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap; }
.hm-hero-ic { width: 4rem; height: 4rem; object-fit: contain; }
.hm-hero { display: flex; flex-direction: column; margin-right: auto; }
.hm-hero-amt { font-size: 2.6rem; font-weight: 800; color: var(--heading); line-height: 1.05; }
.hm-hero-amt small { font-size: 1.1rem; font-weight: 700; color: var(--body); margin-left: .4rem; }
.hm-split { display: flex; gap: 1.6rem; margin: 0; }
.hm-split div { display: flex; flex-direction: column; align-items: flex-end; }
.hm-split dt { font-size: .85rem; font-weight: 700; color: var(--body); }
.hm-split dd { margin: 0; font-size: 1.2rem; font-weight: 800; color: var(--heading); }
.hm-split .is-net dd { color: var(--ok); }
.hm-grid2 { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 1.1rem; }
/* S-10690: the five cost fields need the full receipt width. IR-6500 (2026-09-20):
   shared desktop+mobile, formerly unscoped in mobile-plot.css -- see the comment
   above #worksite-modal-overlay .hm-foot for why it moved. */
#harvest-modal-overlay .harvest-panel:not(.is-batch) .hm-grid2 { grid-template-columns: minmax(0, 1fr); }
.hm-table-wrap { overflow-x: auto; }
.hm-table { width: 100%; border-collapse: collapse; font-size: 1rem; }
.hm-table th { text-align: right; font-size: .8rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--body); padding: 0 .6rem .5rem; border-bottom: 1px solid var(--border); }
.hm-table th:first-child, .hm-table td:first-child { text-align: left; padding-left: 0; }
.hm-table td { text-align: right; padding: .55rem .6rem; font-weight: 700; color: var(--heading); border-bottom: 1px solid var(--border); }
.hm-table tr:last-child td { border-bottom: 0; }
.hm-results td { text-align: left; }
.hm-tok { display: inline-flex; align-items: center; gap: .45rem; white-space: nowrap; }
.hm-tok img { width: 1.5rem; height: 1.5rem; object-fit: contain; }
.hm-tok b { font-weight: 800; color: var(--heading); }
.hm-tok small { font-size: .9rem; font-weight: 700; color: var(--body); }
.hm-table .hm-tok small { font-size: 1rem; color: var(--heading); }
.hm-okc { color: var(--ok) !important; }
.hm-need { color: var(--danger) !important; }
.hm-disc { margin: .6rem 0 0; font-size: .9rem; color: var(--body); }
.hm-disc b { color: var(--ok); }
.hm-cap-top { display: flex; align-items: baseline; gap: .7rem; flex-wrap: wrap; margin-bottom: .55rem; }
.hm-cap-top b { font-size: 1.2rem; color: var(--heading); }
.hm-meter { display: block; height: .55rem; border-radius: 999px; background: color-mix(in srgb, var(--body) 16%, transparent); overflow: hidden; }
.hm-meter i { display: block; height: 100%; width: var(--pct); background: var(--ok); border-radius: inherit; }
.hm-cap[data-level="warn"] .hm-meter i { background: var(--warn); }
.hm-cap[data-level="danger"] .hm-meter i { background: var(--danger); }
.hm-finds { display: flex; flex-direction: column; gap: .7rem; }
.hm-find { display: flex; align-items: center; gap: .7rem; }
.hm-find img { width: 2.2rem; height: 2.2rem; object-fit: contain; flex: none; }
.hm-find b { display: block; font-size: 1.15rem; color: var(--heading); }
.hm-find small { font-size: .85rem; color: var(--body); }
.hm-hint { font-size: .85rem; color: var(--body); margin: .6rem 0 0; }
.hm-totals { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .6rem; }
.hm-total { display: flex; align-items: center; gap: .55rem; padding: .6rem .75rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg3); min-width: 0; }
.hm-total img { width: 2rem; height: 2rem; object-fit: contain; }
.hm-total b { display: block; font-size: 1.3rem; font-weight: 800; color: var(--heading); line-height: 1.1; }
.hm-total small { font-size: .8rem; font-weight: 700; color: var(--body); }
.hm-alert { display: flex; align-items: center; gap: .8rem; padding: .8rem 1rem; border-radius: var(--radius); border: 1px solid var(--danger); background: color-mix(in srgb, var(--danger) 12%, var(--bg2)); color: var(--heading); font-size: 1rem; }
.hm-alert b { color: var(--danger); }
.hm-alert.is-info { border-color: var(--info); background: color-mix(in srgb, var(--info) 12%, var(--bg2)); }
.hm-regions { display: flex; flex-direction: column; gap: .5rem; }
.hm-region { border: 1px solid var(--border); border-radius: var(--radius); background: color-mix(in srgb, var(--bg) 40%, var(--bg2)); }
.hm-region.is-short { border-color: color-mix(in srgb, var(--danger) 70%, var(--border)); }
.hm-rhead { display: grid; grid-template-columns: 1.4rem 11rem minmax(0, 1fr) auto; align-items: center; gap: .9rem; padding: .7rem 1rem; cursor: pointer; }
.hm-rhead.is-static { cursor: default; }
.hm-chev { color: var(--body); font-size: .9rem; }
.hm-rname b { display: block; font-size: 1.05rem; color: var(--heading); }
.hm-rname small { font-size: .85rem; color: var(--body); }
.hm-rrec { display: flex; gap: .4rem 1.1rem; flex-wrap: wrap; min-width: 0; }
.hm-rrec .hm-tok img { width: 1.3rem; height: 1.3rem; }
.hm-status { font-size: .92rem; font-weight: 800; white-space: nowrap; padding: .3rem .7rem; border-radius: 999px; }
.hm-status.is-ok { color: var(--ok); background: color-mix(in srgb, var(--ok) 14%, transparent); }
.hm-status.is-bad { color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.hm-status.is-muted { color: var(--body); background: var(--bg3); }
.hm-rbody { padding: 0 1rem .9rem 3.3rem; }
.hm-rsplit { margin: 0 0 .4rem; font-size: .92rem; color: var(--body); }
.hm-rsplit b { color: var(--heading); }
@media (max-width: 1400px) { .hm-hero-amt { font-size: 2.2rem; } .hm-rhead { grid-template-columns: 1.4rem 9rem minmax(0, 1fr) auto; } }
@media (prefers-reduced-motion: reduce) { .harvest-overlay * { transition: none !important; } }
/* S-5610: exact operation preview in the plot harvest modal */
.hm-ops { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg3); padding: .6rem .9rem; }
.hm-ops summary { cursor: pointer; font-size: .85rem; font-weight: 800; color: var(--heading); }
.hm-ops pre { margin: .5rem 0 0; max-height: 14rem; overflow: auto; font-family: var(--font-mono, monospace); font-size: .78rem; color: var(--body); white-space: pre-wrap; word-break: break-all; }

/* IR-4470: harvest result states and an aligned result grid (values under their headers). */
.hm-run { display: flex; align-items: center; gap: .8rem; padding: .9rem 1.1rem; border-radius: var(--radius); border: 1px solid var(--border-bright); background: var(--bg3); color: var(--heading); font-size: 1.05rem; }
.hm-run.is-ok { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 16%, var(--bg2)); }
.hm-run.is-ok b, .hm-run.is-ok .hm-run-ic { color: var(--ok); }
.hm-run.is-bad { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, var(--bg2)); }
.hm-run.is-bad b, .hm-run.is-bad .hm-run-ic { color: var(--danger); }
.hm-run.is-pending { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, var(--bg2)); }
.hm-run.is-pending b { color: var(--warn); }
.hm-run .hm-btn { margin-left: auto; }
.hm-run-ic { font-size: 1.6rem; font-weight: 900; line-height: 1; }
.hm-spin { flex: none; width: 1.3rem; height: 1.3rem; border-radius: 50%; border: 3px solid color-mix(in srgb, var(--body) 25%, transparent); border-top-color: var(--primary); animation: hm-spin .9s linear infinite; }
@keyframes hm-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .hm-spin { animation-duration: 3s; } }
.hm-rgrid { display: grid; grid-template-columns: minmax(8rem, 1.2fr) minmax(8rem, 1fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, .8fr); }
.hm-rgrid-row { display: contents; }
.hm-rgrid-row > span { display: flex; align-items: center; flex-wrap: wrap; gap: .4rem; padding: .55rem .6rem .55rem 0; border-bottom: 1px solid var(--border); font-weight: 700; color: var(--heading); text-align: left; min-width: 0; }
.hm-rgrid-row.is-head > span { font-size: .8rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--body); padding-top: 0; }
.hm-out.is-success { color: var(--ok); }
.hm-out.is-failed { color: var(--danger); }
.hm-out.is-pending { color: var(--warn); }
/* IR-4480: the batch modal never changes size or value twice. Fixed panel height, a fixed
   status line, 7 fixed total slots, fixed-height region rows and footer; skeletons of the
   final size stand in until the fresh after-tax numbers arrive. */
.harvest-panel.is-batch { height: min(56rem, calc(var(--ceres-vh, 1dvh) * 100 - 2rem)); } /* IR-5590: scale-aware on large screens */
.hm-statusline { display: flex; align-items: center; gap: .7rem; box-sizing: border-box; block-size: 3.4rem; padding: 0 1rem; overflow: hidden; border-radius: var(--radius); border: 1px solid var(--border); background: var(--bg3); color: var(--heading); font-size: .95rem; line-height: 1.25; }
.hm-statusline.is-bad { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, var(--bg2)); }
.hm-statusline.is-bad b { color: var(--danger); }
.hm-statusline.is-ok { border-color: color-mix(in srgb, var(--ok) 60%, var(--border)); }
.hm-statusline.is-info { border-color: color-mix(in srgb, var(--warn) 60%, var(--border)); background: color-mix(in srgb, var(--warn) 10%, var(--bg2)); } /* IR-4690 cooldown */
.hm-statusline.is-info b { color: var(--warn); }
.hm-status.is-warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, transparent); } /* S-5850 */
.hm-region.is-cool { border-color: color-mix(in srgb, var(--warn) 65%, var(--border)); }
.hm-region.is-idle { opacity: .45; }
.hm-wait { display: contents; }
.hm-wait .hm-tok b { color: var(--warn); }
.hm-wait-note { font-size: .85rem; font-weight: 700; color: var(--warn); align-self: center; }
.hm-total.is-wait { border-color: color-mix(in srgb, var(--warn) 60%, var(--border)); }
.hm-total b.is-wait, .hm-total.is-wait small { color: var(--warn); }
.hm-total.is-wait small { white-space: nowrap; font-size: .72rem; }
.hm-statusline.is-ok > span:first-child { color: var(--ok); font-weight: 900; }
.hm-totals { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: 3.6rem; }
.hm-total { box-sizing: border-box; block-size: 3.6rem; }
.hm-total b { font-variant-numeric: tabular-nums; min-block-size: 1.43rem; }
.hm-total b.is-zero { color: var(--body); opacity: .55; }
.hm-rhead { box-sizing: border-box; block-size: 4.6rem; overflow: hidden; }
.hm-rrec { max-block-size: 3.4rem; overflow: hidden; }
.hm-status { display: inline-flex; justify-content: center; min-inline-size: 12.5rem; box-sizing: border-box; }
.hm-foot { flex-wrap: nowrap; box-sizing: border-box; block-size: 4.6rem; }
.hm-note { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hm-skel { display: inline-block; border-radius: 999px; background: color-mix(in srgb, var(--body) 22%, transparent); vertical-align: middle; }
.hm-skel.is-num { inline-size: 6.5rem; block-size: 1.1rem; }
.hm-skel.is-line { inline-size: 16rem; block-size: 1rem; }
.hm-skel.is-pill { inline-size: 7rem; block-size: .9rem; }
.hm-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip: rect(0 0 0 0); }
/* S-5650: the Regions tab is a checkbox list; the checkbox takes the chevron's column so rows keep one geometry. */
.hm-rhead.is-pick { cursor: pointer; }
.hm-region.is-tax .hm-rhead { grid-template-columns: 1.4rem max-content minmax(0, 1fr) auto; }
.hm-region.is-tax .hm-rname b, .hm-region.is-tax .hm-rname small { white-space: nowrap; } /* IR-4920: the name never wraps */
/* S-16775b: picture before the plot badge, region/terrain small under it. */
.hm-rname.is-tax-name { display: flex; align-items: center; gap: .55rem; min-inline-size: 0; }
.hm-tax-art { inline-size: 28px; block-size: 28px; object-fit: contain; flex: none; }
.hm-tax-id { display: flex; flex-direction: column; align-items: flex-start; gap: .15rem; min-inline-size: 0; }
.hm-tax-id .ch-plot-badge { cursor: default; }
.hm-region.is-tax .hm-rhead { block-size: auto; min-block-size: 4.6rem; } /* IR-4920: a Keep/Castle holds up to 7 resources -- the row grows, nothing is clipped */
.hm-region.is-tax .hm-rrec { max-block-size: none; }
.hm-kind-btn { display: inline-flex; align-items: center; gap: .6rem; padding-inline: .9rem 1.2rem; }
.hm-kind-btn img { inline-size: 2rem; block-size: 2rem; object-fit: contain; filter: drop-shadow(0 1px 2px rgb(0 0 0 / .5)); }
.hm-kind-btn:disabled { opacity: .5; cursor: default; }
.hm-kind-btn:disabled img { filter: grayscale(1); }
.hm-tax-kinds { display: flex; flex-wrap: wrap; gap: .6rem; margin-block-end: .7rem; }
.hm-rcheck { inline-size: 1.15rem; block-size: 1.15rem; margin: 0; accent-color: var(--primary); }
.hm-rhead.is-pick:has(.hm-rcheck:disabled) { cursor: default; opacity: .75; }
/* IR-4490: until the fresh plot read lands the bar keeps its final size with no number:
   an amount-sized skeleton and an empty capacity edge. */
.harvest-bar.is-tax.is-pending .harvest-bar-amt::before, .slot-editor-footer > .harvest-bar.is-pending .harvest-bar-amt::before { content: ''; display: inline-block; inline-size: 70%; block-size: .9em; border-radius: 999px; background: color-mix(in srgb, var(--body) 22%, transparent); vertical-align: middle; }
.harvest-bar-amt { display: block; block-size: 1.54rem; }

/* S-5690: the band's first line is .slot-group-controls; every status badge (the staged-change
   badges) sits right-aligned on that same line, left of Close. */
.editor-band .editor-statusline { position: absolute; z-index: 5; inset: var(--eos-facts, .45rem) calc(.8rem + 4.1rem) auto auto; block-size: 1.3rem; display: flex; align-items: center; justify-content: flex-end; gap: .3rem; max-inline-size: calc(100% - 30rem); overflow: hidden; pointer-events: auto; }
.editor-band .editor-statusline .editor-change-badges { flex-wrap: nowrap; }
/* IR-4570: the staged-change badges dock on the FAR LEFT, directly after "Clear plot", inside the
   controls line; a thin pipe separates them from the buttons and exists only when badges render. */
/* S-14050: the badges share the compact buttons' box height and start-align with them, so badge text and the pipe
   sit on the buttons' own centreline (the controls line is taller than its buttons and start-aligned). */
.slot-group-controls > .editor-change-badges { display: inline-flex; align-items: center; gap: .3rem; flex-wrap: nowrap; pointer-events: auto; align-self: start; min-block-size: 1.875rem; }
.slot-group-controls > .editor-change-badges::before { content: ''; inline-size: 1px; block-size: 1.1rem; margin-inline: .15rem .2rem; background: var(--border-bright); flex: 0 0 auto; }

/* S-14620 (owner, verbatim: "mirror the plots splinter icons with the horizontal style we
   alreader use in the picker filter. Place it before fill plots with a vertical devider,
   centrally aligned with the buttons. keep their sizes."): the worker group's terrain-
   affinity row -- the exact `.terrain-affinity`/`.terrain-affinity-badge` markup the worker
   picker's filter bar already renders (terrainAffinityHtml(row, 'picker')) -- sits before
   the Fill plot button inside `.slot-group-controls`.
   IR-6870 (owner, verbatim: "remove the old vertical one. Increase the horizontal ones and
   align them with the button sizes right of them"): the old vertical `.editor-main` column
   this badge row used to sit beside is retired (see the two comments a few rules up), and
   these badges now match the button box height instead of the picker's fixed 1.5rem --
   `var(--ceres-control-min-block, 1.875rem)` is the same token `.slot-group-controls`'s own
   buttons size from (buttons.css `:root`), so the badge box and the Fill plot button are
   always the same height by construction, not by a matched literal. Alignment copies the
   S-14050 `.editor-change-badges` technique exactly, not `align-self: center`: the buttons
   themselves are `align-items: start` inside `.editor-band .slot-group-controls`'s own
   shrunk `--eos-controls` box (they visually overflow past it), so centring an item against
   that collapsed container centres it on the WRONG reference and measures off by several
   px. Two same-height items that both start-align at the container's top edge instead share
   a vertical centre by construction, with no correction term. `min-block-size` here is a
   safety net matching the badges' own token, since `.terrain-affinity`'s picker-sized fixed
   width/height (12rem / 1.5rem, and its `.terrain-affinity-scroll`/`.terrain-affinity-
   viewport` wrappers, a few rules down) are also overridden to auto so the row hugs its 6
   badges instead of centring them inside a box sized for the picker's filter bar -- without
   that, the first badge bled past the controls' left edge and the last badge touched the
   divider with no gap. The divider mirrors `.picker-bar-end .picker-bar-sep` (S-4121) at the
   same 1px proportion, now spanning the button height instead of a fixed 1.4rem. */
.slot-group-controls > .editor-splinter-affinity { display: inline-flex; pointer-events: auto; align-self: start; min-block-size: var(--ceres-control-min-block, 1.875rem); }
.slot-group-controls > .editor-splinter-affinity-sep { flex: 0 0 1px; inline-size: 1px; align-self: start; min-block-size: var(--ceres-control-min-block, 1.875rem); margin-inline: calc(var(--plot-control-pipe-gap) - .25rem); background: var(--primary); pointer-events: none; } /* S-16852/S-16852d: plain opaque --primary line (no border/halo) so both pipes read over the plot scene art */
.slot-group-controls > .editor-plot-lock { display: inline-flex; align-self: start; pointer-events: auto; }
.plot-lock-button { inline-size: var(--ceres-control-min-block, 1.875rem); min-inline-size: var(--ceres-control-min-block, 1.875rem); max-inline-size: var(--ceres-control-min-block, 1.875rem); block-size: var(--ceres-control-min-block, 1.875rem); max-block-size: var(--ceres-control-min-block, 1.875rem); display: inline-grid; place-items: center; padding: .1375rem; border-radius: .25rem; }
.plot-lock-button svg { inline-size: 1.36rem; block-size: 1.36rem; min-inline-size: 1.36rem; min-block-size: 1.36rem; display: block; max-inline-size: 100%; max-block-size: 100%; } /* S-16830 / IR-8373: min sizes stop the inline-grid track collapsing the icon to ~7px */
.plot-lock-icon svg { inline-size: 1.1rem; block-size: 1.1rem; display: block; }
table.plot-table td.lock-cell .plot-lock-icon svg { inline-size: 1.1rem; block-size: 1.1rem; }
/* IR-8532/IR-8533 (owner): a staged, not yet signed lock change shows its TARGET padlock in warn orange, pulsing like the badge
   clock (same 1.3 s rhythm, same shared pulse timeline). Signed: back to the normal lock colours. Reduced motion: steady. */
table.plot-table td.lock-cell .plot-lock-icon .plot-lock-staged { color: var(--warn); animation: lock-clock-pulse 1.3s ease-in-out infinite; }
@keyframes lock-clock-pulse {
  0%, 100% { opacity: .3; filter: drop-shadow(0 0 0 transparent); }
  50% { opacity: 1; filter: drop-shadow(0 0 5px var(--warn)) drop-shadow(0 0 2px var(--warn)); }
}
@media (prefers-reduced-motion: reduce) {
  table.plot-table td.lock-cell .plot-lock-icon .plot-lock-staged { animation: none; opacity: 1; filter: none; }
}
.plot-lock-icon { display: inline-flex; align-items: center; justify-content: center; }
.plot-lock-button.is-staged { color: var(--warn); border-color: var(--warn); animation: plot-lock-pulse 1.3s ease-in-out infinite; }
@keyframes plot-lock-pulse { 50% { opacity: .45; box-shadow: 0 0 .55rem var(--warn); } }
@media (prefers-reduced-motion: reduce) { .plot-lock-button.is-staged { animation: none; } }
/* S-16875 (owner: "must follow 100% of the same logic: text aligned, image aligned in the upper half vertically
   centered between top and text, like totems and titles, no grey block"): the lock tile is built like an item tile --
   the item-tile fill, the padlock centred in the area above the standard 52px caption, and the caption/name exactly
   as on the totem and title tiles (no caption background). */
.changes-panel .ch-lock-plan { flex: 0 0 var(--slot-w); cursor: pointer; background: color-mix(in srgb, var(--bg3) 66%, transparent); }
.changes-panel .ch-lock-plan[data-lock-in-flight] { cursor: default; }
.ch-lock-art { position: absolute; inset: 0 0 52px 0; display: grid; place-items: center; color: var(--heading); pointer-events: none; }
.ch-lock-art svg { inline-size: 3rem; block-size: 3rem; }
.changes-panel .ch-lock-plan .slot-caption > span { color: var(--text); font-size: .61rem; line-height: 1.2; white-space: nowrap; }
.changes-panel .ch-lock-plan:hover { box-shadow: 0 0 0 2px var(--accent); }
.changes-panel .ch-plot .ceres-notice.slot-hazard-inline { border: 1px solid var(--warn); }
.editor-band .editor-splinter-affinity :is(.terrain-affinity, .terrain-affinity-scroll, .terrain-affinity-viewport) { inline-size: auto; min-inline-size: 0; max-inline-size: none; block-size: auto; }

/* S-5710: crafted two-tone header icons — a light body (--hi/--primary mix), dark primary-tinted
   details and a gold accent; global tokens only, readable on every theme's --primary button. */
.board-header .board-header-row .hdr-ic { inline-size: 1.25rem; block-size: 1.25rem; display: block; overflow: visible; }
/* S-5770: the same crafted sickle on the plot harvest box's button -- identical two-tone token colours. */
/* S-6940: the same two-tone colours on the Changes modal's Sign button (same treatment as the harvest box, S-5770/S-5800) */
.changes-panel .hm-btn .hdr-ic { display: inline-block; overflow: visible; }
.harvest-bar-btn .hdr-ic { display: block; overflow: visible; }
button.slot-bulk.fill-action,
.picker-bar-end .picker-keep-open.fill-action { display: inline-flex; align-items: center; gap: .32rem; border-color: var(--primary); background: color-mix(in srgb, var(--primary) 18%, var(--bg3)); color: var(--heading); font-weight: 700; }

.fill-action .hdr-ic { display: block; overflow: visible; inline-size: 1.2rem; block-size: 1.2rem; flex: 0 0 auto; }
.fill-action .hdr-ic .f { fill: color-mix(in srgb, var(--hi) 84%, var(--primary)); }
.fill-action .hdr-ic .d { fill: none; stroke: color-mix(in srgb, var(--primary) 62%, var(--shadow)); stroke-width: 1.6; stroke-linecap: round; }
.fill-action .hdr-ic .a.h { fill: none; stroke: var(--gold); stroke-width: 2.7; stroke-linecap: round; }
:root:not([data-theme]) .fill-action .hdr-ic .a.h,
[data-theme="alchemical"] .fill-action .hdr-ic .a.h { stroke: var(--hi); }

/* S-5780: non-selected tiles fade softly into AND out of the greyish dimmed state (the transition lives on
   the tile itself, so removing the dim animates too); the worker picker pushes out gently on open and
   back in before it closes. Reduced motion: none of it. */
.editor-band .slot-grid .slot-tile { transition: filter 0.35s ease, opacity 0.35s ease, box-shadow 0.35s ease, border-color 0.35s ease; }
@keyframes picker-push-out-open { from { opacity: 0; transform: translateY(-10px) scaleY(.96); } to { opacity: 1; transform: none; } }
@keyframes picker-push-in-close { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-10px) scaleY(.96); } }
/* IR-4740: the push-open plays once, on the first mount only -- redraws (cards arriving) used to replay it. */
.inline-picker { transform-origin: top center; }
.inline-picker.is-opening { animation: picker-push-out-open .28s ease-out; }
.inline-picker.is-closing { animation: picker-push-in-close .22s ease-in forwards; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .editor-band .slot-grid .slot-tile { transition: none; } .inline-picker, .inline-picker.is-closing { animation: none; } }

/* S-5860: tile rim + bottom art fade for the poster shadow (last in the file so it wins over the tile base rules). */
/* The tile itself: no own drop shadow (the wrapper has it); a hairline lighter rim like the reference. */
.editor-band .slot-grid .slot-tile { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hi) 7%, transparent); }
.editor-band .slot-grid .slot-tile.has-slot-art { border-color: color-mix(in srgb, var(--hi) 14%, var(--shadow)); }
.editor-band .slot-grid .slot-tile.has-slot-art:hover { border-color: var(--accent); }
.editor-band .slot-grid .slot-tile.is-picker-focus { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hi) 12%, transparent); }
/* Art fades to near-black at the bottom, inside the card, under the caption. */
.editor-band .slot-grid .slot-tile.has-slot-art .slot-art::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(0deg, color-mix(in srgb, var(--shadow) 88%, transparent) 0%, color-mix(in srgb, var(--shadow) 55%, transparent) 22%, transparent 48%); }
@media (prefers-reduced-motion: reduce) { .editor-band .slot-grid :is(.slot-tile-cell, .asset-slot, .eos-permanent-frame[data-reveal-group="power"]):has(.slot-tile.is-picker-focus) { transform: none; } }

/* S-5870: loading states for harvest amounts show the official Splinterlands spinner (assets/ui/loading-spinner.gif)
   instead of a pale empty bar -- the modal's number/line/pill skeletons, its busy status line, and the plot box. */
.hm-skel, .hm-skel.is-num, .hm-skel.is-line, .hm-skel.is-pill, .hm-spin { inline-size: 2rem; block-size: 2rem; border: 0; border-radius: 0; background: url(/static/assets/ui/loading-spinner.gif) center / contain no-repeat; animation: none; }
.slot-editor-footer > .harvest-bar.is-pending .harvest-bar-amt::before { inline-size: 1.8rem; block-size: 1.8rem; border-radius: 0; background: url(/static/assets/ui/loading-spinner.gif) center / contain no-repeat; }
/* S-6340: the tax box shows the storage fill and one small icon per resource held (amounts in the tooltip). */
.harvest-bar.is-tax .harvest-bar-amt { display: inline-flex; align-items: center; gap: .45rem; }
.harvest-bar-tax-icons { display: inline-flex; align-items: center; gap: .15rem; }
.harvest-bar-tax-ic { inline-size: 1.05rem; block-size: 1.05rem; object-fit: contain; }
.harvest-bar.is-tax .harvest-bar-mid { inline-size: auto; } /* S-6350: the icon strip needs its own width -- the reserved harvest width clipped "Tax storage" and the % */

/* S-7610: a worksite build in progress in the Worksite column -- the NEW worksite's icon dimmed with a corner mark:
   red clock while building (tooltip carries the live countdown), green tick once the time is up. Palette colours only. */
.ws-build { position: relative; display: inline-block; line-height: 0; margin-right: 0.3rem; }
.ws-build .resource-icon { margin-right: 0; }
.ws-build.is-building .resource-icon { opacity: 0.55; }
.ws-build-mark { position: absolute; right: -0.4rem; bottom: -0.3rem; width: 0.9rem; height: 0.9rem; box-sizing: border-box; border-radius: 50%; border: 1px solid currentColor; background: var(--bg3); color: var(--danger); display: inline-flex; align-items: center; justify-content: center; line-height: 0; }
.ws-build.is-done .ws-build-mark { color: var(--ok); }
.ws-build-eta { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* IR-5480: first-clear note under the worksite tiles */
.ws-first-clear-note { margin: .6rem 0 0; font-size: .85rem; line-height: 1.35; color: var(--text-dim); }

/* S-9360 FLUID TABLE START -- replaces the FR-1946 A-B width tiers (le-1659/le-1439).
   Every size is a clamp() over the board width (cqi of `.table-scroll`), linear
   from a 1280 px board to Full HD, where it equals the approved Full HD value.
   The badge row may wrap only when the columns cannot all get their content
   width (the browser decides; nothing is measured). */
@container ceres-table (max-width: 90rem) { /* 1440 px at the default 16px root font; in rem so a larger text size switches too */
  .fr1946-column-label-full { display: none; }
  .fr1946-column-label-short::before { content: attr(data-label); }
}
table.plot-table tbody tr.plot-row > td .badge-row { display: flex; flex-wrap: wrap; align-content: center; gap: 0.06rem max(0.06rem, 0.0998cqi - 0.0179rem); }
table.plot-table tbody tr.plot-row > td .badge-row .badge { padding-inline: max(0.16rem, 0.3495cqi - 0.1125rem); }
/* S-14330 (version A, ceres-table-redesign plan §2/§5): plot/resource/terrain/totem/title
   artwork inside the plot table share one fluid clamp -- 1.25rem (20px) at a 1280 board,
   1.375rem (22px) at 1920 -- replacing the three formulas that used to differ here (fixed
   1.25rem terrain-emblem, fixed 1.3rem resource-icon/totem-icon/title-icon two blocks
   above, and this selector list's own max(1.05rem, .563rem + .624cqi)). Scoped to
   table.plot-table so the identical classes used elsewhere (worksite modal, mobile cards,
   tooltips) keep their own untouched sizing. Aspect ratio/object-fit and fallback text are
   unaffected -- only width/height. Includes the Rarity column's own gem
   (td.rarity-cell .rarity-gem), not just the title-cell's rarity-gem override.
   Pre-landing fix: excludes `.resource-icon.is-tax` (IR-5360's approved, frozen
   1.7rem Keep/Castle landscape art per S-7910) -- this selector list's specificity
   would otherwise have overridden it.
   IR-6690 fix (post-0.9.677 review finding #1): the Type column's own icon,
   `td.type-cell .type-mark`, was left out of this list -- it kept the fixed 1.25rem
   (20.0px at every board width) from the unscoped `.type-mark` rule elsewhere in this
   file (still correct for the mobile card, where that unscoped rule still applies),
   while every other table icon here scaled to 21.7px at 1920. Added below so all six
   table icon columns are the exact same size.
   S-14420 review fix: the Wood/hr/Stone/hr/Iron/hr cell icons (`.material-cost-icon`)
   join this same clamp instead of the small fixed-14px `.dec-stake-icon` class they
   used to share -- see that class's own comment. */
table.plot-table td.resource-cell .resource-icon:not(.is-tax),
table.plot-table td.terrain-cell .terrain-emblem,
table.plot-table td.rarity-cell .rarity-gem,
table.plot-table td.type-cell .type-mark,
table.plot-table td.totem-cell .totem-icon,
table.plot-table td.title-cell .title-icon,
table.plot-table td.title-cell .rarity-gem,
table.plot-table td.col-w-wood .material-cost-icon,
table.plot-table td.col-w-stone .material-cost-icon,
table.plot-table td.col-w-iron .material-cost-icon { width: clamp(1.25rem, 1rem + .3125cqi, 1.375rem); height: clamp(1.25rem, 1rem + .3125cqi, 1.375rem); }
/* IR-8140: removed the fluid margin-right/margin-left pair here too -- same
   gap, same fix, this rule's higher specificity is what was actually
   winning the cascade over the plain .title-cell rule above. */
/* S-9360 FLUID TABLE END */

/* S-9360 FLUID OPEN PLOT START
   Worksite and spacing follow the container. Tiles divide the remaining width
   between every slot, with no pagination or fixed maximum card dimensions. */
.editor-band {
  --worksite-w: calc(8.44rem + 12.26cqi);
  --eos-slot-w: calc(4.048rem + 2.246cqi);
  --eos-slot-h: calc(var(--eos-slot-w) * 9.4 / 6.7);
  --eos-tile-gap: calc(0.0186rem + 0.8736cqi);
  --eos-divider-m: max(0px, 0.8736cqi - 0.1313rem);
}
.editor-band .slot-grid,
.editor-band .slot-power { --slot-w: var(--eos-slot-w); --slot-h: var(--eos-slot-h); }
.editor-band .slot-grid { gap: var(--eos-tile-gap); padding-left: calc(var(--worksite-x) + var(--worksite-w) + var(--eos-tile-gap)); }
.editor-band .editor-base-pp-row { left: calc(var(--worksite-x) + var(--worksite-w) + var(--eos-tile-gap)); gap: var(--eos-tile-gap); }
.editor-band .editor-base-pp-cell { flex-basis: var(--eos-slot-w); inline-size: var(--eos-slot-w); }
.editor-band .slot-grid-divider,
.editor-band .editor-base-pp-divider { margin-inline: var(--eos-divider-m); }
/* IR-6120: divide the actual tile strip between the visible slots. The strip
   excludes body insets, grid padding, worksite, gaps, and the optional divider.
   Tile height and the footer use that same width, preserving card proportions. */
/* Resolve once on the band so nested card containers cannot change the scale. */
@property --eos-effect-unit { syntax: "<length>"; inherits: true; initial-value: 1px; }
html:not(.ceres-lw-le-1279) .slot-editor { container-type: inline-size; }
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band {
  --eos-tile-gap: calc((100cqi - 1.6rem - 0.6rem - var(--worksite-x) - var(--worksite-w)) / (var(--eos-visible-slots, 1) + var(--eos-visible-dividers, 0)) * 0.08);
  --eos-slot-w: calc((100cqi - 1.6rem - 0.6rem - var(--worksite-x) - var(--worksite-w)
    - (var(--eos-visible-slots, 1) + var(--eos-visible-dividers, 0)) * var(--eos-tile-gap)
    - var(--eos-visible-dividers, 0) * (1px + 2 * var(--eos-divider-m))) / var(--eos-visible-slots, 1));
  --eos-slot-h: calc(var(--eos-slot-w) * 9.4 / 6.7);
  --eos-ability-room: 0rem;
  --eos-effect-unit: calc(var(--eos-slot-w) / 107.2);
  --eos-card-top: max(2.5rem, calc(var(--eos-slot-w) * 2.3 / 6.7 / 2 + 0.2rem));
  --eos-cards: calc(var(--eos-slot-h) + var(--eos-card-top) + 8.8 * var(--eos-effect-unit) + var(--eos-ability-room));
  --eos-content-height: calc(var(--eos-facts) + var(--eos-controls) + var(--eos-cards) + var(--eos-footer) + 1.3125rem);
  block-size: var(--eos-content-height);
  min-block-size: var(--eos-content-height);
}
/* Use the same scene-scaled facts, controls, cards, footer and spacing as the
   normal desktop band; the captured rem height omits those computed terms. */
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band[data-eos-render-error] {
  block-size: var(--eos-content-height, var(--eos-band-height, 18.5125rem));
  min-block-size: var(--eos-content-height, var(--eos-band-height, 18.5125rem));
}
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band[data-eos-render-error][data-eos-layout-counts-unavailable] {
  block-size: var(--eos-band-height, 18.5125rem);
  min-block-size: var(--eos-band-height, 18.5125rem);
}
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band:has(.slot-ability-arc.is-vov) {
  --eos-ability-room: 1.5rem;
}
.editor-band .slot-group-controls { flex-wrap: nowrap; }
.editor-band .slot-tile { container-type: inline-size; }
/* S-13580: keep the Changes tile artwork proportions as plot tiles grow.
   Reference inner width: 6.7rem - 2px = 105.2px at the 16px base. */
.editor-band .slot-tile-item :is(.slot-art-totem, .slot-art-title) img {
  padding: calc(100cqi * 10.56 / 105.2) calc(100cqi * 5.6 / 105.2) calc(100cqi * 59.04 / 105.2);
}

.editor-band .slot-name { display: block; flex: none; block-size: calc(100cqi * 29.4 / 107.2); max-height: none; white-space: normal; overflow-wrap: normal; word-break: keep-all; text-wrap: balance; text-overflow: clip; -webkit-line-clamp: unset; font-size: calc(100cqi * 12.8 / 107.2); line-height: calc(100cqi * 14.7 / 107.2); }
.editor-band .slot-caption--long .slot-name { font-size: calc(100cqi * 11.5 / 107.2); line-height: calc(100cqi * 14.7 / 107.2); }
.editor-band .slot-caption { height: 48.5cqi; padding-block-start: 2.425cqi; }
/* Original geometry: 2.3rem satellite / 6.7rem tile, halfway above its top. */
.editor-band .slot-power-satellite,
.editor-band .slot-energized-satellite {
  inline-size: calc(100% * 2.3 / 6.7);
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  container-type: inline-size;
}
.editor-band .power-satellite {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
}
.editor-band .power-satellite .slot-art img { padding: calc(100% * 0.18 / 2.3); }
.editor-band .slot-energized-satellite .slot-art img { padding: 0; }
.editor-band .power-satellite-plus { font-size: calc(100cqi * 1.25 / 2.3); }
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band .slot-grid { padding-top: calc(var(--eos-card-top) + var(--eos-ability-room)); }
/* Ability coordinates retain the original 107.2px-wide card geometry. */
.editor-band .slot-tile-cell { container-type: inline-size; --ability-unit: calc(100cqi / 107.2); }
.editor-band .slot-tile-cell > .slot-tile { width: 100%; height: auto; min-height: 0; max-height: none; aspect-ratio: 6.7 / 9.4; }
.editor-band .slot-foot { bottom: calc(48.5cqi + 3 * var(--ability-unit)); gap: calc(3 * var(--ability-unit)); padding-inline: calc(3 * var(--ability-unit)); } /* IR-9159: 48.5cqi = the band's name strip */
.editor-band .slot-ability-row { block-size: calc(24 * var(--ability-unit)); }
.editor-band .slot-kin-badges { gap: calc(2 * var(--ability-unit)); }
.editor-band .slot-kin-badge { min-height: calc(18 * var(--ability-unit)); padding: calc(1 * var(--ability-unit)) calc(4 * var(--ability-unit)); gap: calc(3 * var(--ability-unit)); font-size: calc(8.6 * var(--ability-unit)); border-radius: calc(4 * var(--ability-unit)); }
.editor-band .slot-kin-badge .slot-kin-icon { inline-size: calc(11 * var(--ability-unit)); block-size: calc(11 * var(--ability-unit)); }
.editor-band .slot-ability-lane { gap: calc(2 * var(--ability-unit)); }
.editor-band .slot-ability-row.is-centred .slot-ability-lane { gap: 0; }
.editor-band .slot-ability-row.is-split { padding-inline: calc(2 * var(--ability-unit)); }
.editor-band .slot-ability-row.is-split .slot-ability-lane { flex-basis: calc(22 * var(--ability-unit)); }
.editor-band .slot-ability-item { width: calc(24 * var(--ability-unit)); height: calc(24 * var(--ability-unit)); }
.editor-band .slot-ability-row:is(.is-centred, .is-split) .slot-ability-item,
.editor-band .slot-ability-arc .slot-ability-item { width: calc(22 * var(--ability-unit)); height: calc(22 * var(--ability-unit)); }
.editor-band .slot-ability-item .ability-icon,
.editor-band .slot-ability-item > span { width: calc(22 * var(--ability-unit)); height: calc(22 * var(--ability-unit)); }

.editor-band .slot-ability-overflow { width: calc(22 * var(--ability-unit)); }
/* IR-9318: five passive visual slots stay inside the portrait at every width. */
.editor-band .slot-ability-row.is-centred { inline-size: 100%; }
.editor-band .slot-ability-row.is-centred .slot-ability-lane { inline-size: 100%; padding-inline: calc(3 * var(--ability-unit)); box-sizing: border-box; justify-content: center; }
.editor-band .slot-ability-row.is-centred .slot-ability-item,
.editor-band .slot-ability-row.is-centred .slot-ability-count { flex: 0 0 min(calc(22 * var(--ability-unit)), calc((100% - 6 * var(--ability-unit)) / 5)); inline-size: min(calc(22 * var(--ability-unit)), calc((100% - 6 * var(--ability-unit)) / 5)); }
.editor-band .slot-ability-row.is-centred .slot-ability-item .ability-icon { inline-size: 100%; block-size: auto; max-block-size: 100%; }
.editor-band .slot-ability-row.is-centred .slot-ability-count { display: inline-flex; align-items: center; justify-content: center; pointer-events: none; font-size: calc(8 * var(--ability-unit)); }

.editor-band .slot-ability-item > span { font-size: calc(7.68 * var(--ability-unit)); }
.editor-band .slot-grid .slot-ability-arc.is-vov { top: calc(-13 * var(--ability-unit)); }
.editor-band .editor-base-pp-cell { container-type: inline-size; }
.editor-band .editor-base-pp-cell b { font-size: calc(100cqi * 1.08 / 6.7); line-height: 1.1; }
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band { --eos-footer: max(2.7rem, calc(var(--eos-slot-w) * 0.3)); }
/* The old fixed-height row centered growing PP cells upward into the cards. */
html:not(.ceres-lw-le-1279) .editor-band .editor-base-pp-row {
  top: calc(3.2 * var(--eos-effect-unit));
  block-size: auto;
  align-items: start;
}
html:not(.ceres-lw-le-1279) .editor-band .slot-tile {
  border-width: var(--eos-effect-unit);
}
html:not(.ceres-lw-le-1279) .editor-band .slot-tile:is(.is-stage-ready, .is-stage-blocked, .is-stage-elsewhere, .is-pending-add, .is-pending-removal) {
  outline-width: calc(2 * var(--eos-effect-unit));
  outline-offset: calc(-2 * var(--eos-effect-unit));
}

html:not(.ceres-lw-le-1279) .slot-editor > .editor-band:has(.slot-ability-arc) { --eos-card-top: max(2.5rem, calc(var(--eos-slot-w) * 55 / 107.2)); }
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band:has(.slot-ability-arc.is-vov) { --eos-ability-room: calc(var(--eos-slot-w) * 24 / 107.2); }
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band { --eos-affinity-size: calc(var(--eos-slot-w) * 1.5 / 6.7); }
/* IR-6870: the `.editor-main > .terrain-affinity[data-terrain-affinity="editor"]` position/gap
   override that used to live here is retired -- the vertical column it sized is no longer
   rendered on desktop (see editor.js's IR-6870 comment). `--eos-affinity-size` itself stays:
   the art-fit calcs below (`--art-fit`, twice) still read it and were left untouched per the
   IR-6870 brief's own caution against changing art fit without checking it first. */
:is(.editor-band, .mobile-plot-affinity) .terrain-affinity-badge {
  flex-basis: var(--eos-affinity-size);
  inline-size: var(--eos-affinity-size);
  block-size: var(--eos-affinity-size);
}
:is(.editor-band, .mobile-plot-affinity) .terrain-affinity-badge > .splinter-icon { inline-size: 66.666667%; block-size: 66.666667%; }
:is(.editor-band, .mobile-plot-affinity) .terrain-affinity-badge::after { inset: 12.5%; }
:is(.editor-band, .mobile-plot-affinity) .terrain-affinity-badge[data-affinity-state="favoured"]::before { border-width: calc(var(--eos-affinity-size) / 8); }
:is(.editor-band, .mobile-plot-affinity) .terrain-affinity-badge[data-affinity-state="penalised"]::before { padding: 12.5%; }
:is(.editor-band, .mobile-plot-affinity) .terrain-affinity-badge:is([data-affinity-state="unchanged"], [data-affinity-state="unknown"])::before { border-width: calc(var(--eos-affinity-size) / 24); }
/* S-9360 FLUID OPEN PLOT END */

/* S-14620: `.editor-splinter-affinity` sits inside `.editor-band`, so without this override it
   would inherit the fluid `--eos-affinity-size` scaling above (the OVERLAY affinity display's
   own sizing) -- but the owner asked to "keep their sizes" against the PICKER's affinity row,
   which is always the fixed base size (ceres.css's plain `.terrain-affinity-badge` rule, never
   scaled by editor-band). These rules restore those exact fixed values, scoped narrowly enough
   (3 classes) to outrank the `:is(.editor-band, .mobile-plot-affinity) .terrain-affinity-badge`
   rules above (2 classes) without touching the overlay affinity display those rules still own.
   IR-6920 (owner, verbatim: "there is a new outer grey border around the splinter icons in
   the plot view. remove that and bring back the previous shadows"): `terrainAffinityHtml(row,
   'picker')` -- the exact call this row has made since S-14620 -- renders a real `<button>`
   (terrainAffinity.js's interactive branch), and only `.picker-bar .terrain-affinity-badge`
   resets that element's UA button chrome (`padding: 0; border: 0; ... appearance: none;`).
   This row sits under `.editor-band .editor-splinter-affinity`, outside `.picker-bar`, so the
   browser's default `2px outset` button border was never neutralised here and paints over the
   base `.terrain-affinity-badge` rule's own `box-shadow` -- that box-shadow was never actually
   removed, just visually buried under the UA border. Mirroring the picker's own reset at this
   scope removes the border and lets the existing shadow show through unchanged. */
.editor-band .editor-splinter-affinity .terrain-affinity-badge { padding: 0; border: 0; font: inherit; appearance: none; background: var(--bg); --eos-splinter-row-size: var(--ceres-control-min-block, 1.875rem); flex-basis: var(--eos-splinter-row-size); inline-size: var(--eos-splinter-row-size); block-size: var(--eos-splinter-row-size); }
/* IR-6970/S-14940 (owner: "Make them plain icons"): these badges render as non-interactive
   <span role="img"> now (terrainAffinity.js's 'editor-row' surface), never a <button> -- an
   explicit cursor keeps that true regardless of any inherited pointer cursor. */
.editor-band .editor-splinter-affinity .terrain-affinity-badge { cursor: default; }
.editor-band .editor-splinter-affinity .terrain-affinity-badge > .splinter-icon { inline-size: 66.666667%; block-size: 66.666667%; }
.editor-band .editor-splinter-affinity .terrain-affinity-badge::after { inset: 12.5%; }
.editor-band .editor-splinter-affinity .terrain-affinity-badge[data-affinity-state="favoured"]::before { border-width: calc(var(--eos-splinter-row-size) / 8); }
.editor-band .editor-splinter-affinity .terrain-affinity-badge[data-affinity-state="penalised"]::before { padding: 12.5%; }
.editor-band .editor-splinter-affinity .terrain-affinity-badge:is([data-affinity-state="unchanged"], [data-affinity-state="unknown"])::before { border-width: calc(var(--eos-splinter-row-size) / 24); }

/* FR-1946 PHASE C LOW-RES PANELS START (modal/picker slice widths; not the table) */
html.ceres-lw-le-1919.ceres-lw-le-1659 .candidate-visible-slice { inline-size: 100%; max-inline-size: 100%; }
/* IR-5790 / S-9140: no tier column counts for the picker grid -- its auto-fill rule sits with the picker styles. */

html.ceres-lw-le-1919.ceres-lw-le-1659 .harvest-panel.fill-panel { box-sizing: border-box; width: fit-content; min-width: min(56rem, calc(var(--ceres-vw) * 100 - 2rem)); max-width: calc(var(--ceres-vw) * 100 - 2rem); }
html.ceres-lw-le-1919.ceres-lw-le-1659 .harvest-panel.changes-panel:not(.fill-panel):not(.worksite-panel) { box-sizing: border-box; width: fit-content; min-width: min(68rem, calc(var(--ceres-vw) * 100 - 2rem)); max-width: calc(var(--ceres-vw) * 100 - 2rem); }
html.ceres-lw-le-1919.ceres-lw-le-1659 .harvest-panel.worksite-panel { box-sizing: border-box; width: fit-content; min-width: min(60rem, calc(var(--ceres-vw) * 100 - 2rem)); max-width: calc(var(--ceres-vw) * 100 - 2rem); }
/* FR-1946 PHASE C LOW-RES PANELS END */

.plot-card-list, .mobile-action-bar, .mobile-filter-sheet-head, .mobile-filter-backdrop { display: none; }
.mobile-plot-sheet { display: none; }

/* FR-1946 PHASE D MOBILE BOARD START */
html.ceres-lw-le-1919.ceres-lw-le-1279 body { padding-block-end: calc(4.5rem + env(safe-area-inset-bottom)); }
html.ceres-lw-le-1919.ceres-lw-le-1279 header.board-header { display: flex; align-items: center; gap: 0.7rem; padding: 0.4rem 0.65rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-logo { align-self: center; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-version { font-size: 0.55rem; inset-inline-end: 0.1rem; inset-block-end: 0.05rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-header-rows { flex: 1 1 auto; min-inline-size: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-header-row { inline-size: 100%; gap: 0.3rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-header .player-avatar::after { font-size: 1.9rem; }

/* S-14430 (2026-09-19, Florian: "Make the player field narrower, move more to
   the right with the no rounded corners left und fully covered by the avatar
   and the logo larger on mobile"). Mobile-only. The switch no longer grows, so
   margin-inline-start: auto pushes it to the row's right edge (the row's other
   buttons are already display:none at this tier -- see below). The avatar's
   own sizing (real 3.25rem/52px) and the field's narrowing/flex fix live in
   mobile-plot.css, which loads AFTER this file and wins the cascade for those
   shared selectors -- do not re-declare inline-size/block-size/flex here, it
   is dead. This file keeps only the overlap margin (not set in mobile-plot.css,
   so it is not shadowed) and the field's left-corner/padding treatment (a more
   specific selector, so it always wins regardless of load order): the field's
   left corners are squared off (border-start/end-start-radius: 0) so the
   avatar's circle fully caps the field's left end with no seam; the field's
   own padding-inline-start still exceeds the overlap so the caret and text
   never sit under the avatar. */
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-header .player-switch { flex: 0 0 auto; min-inline-size: 0; margin-inline-start: auto; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-header .player-avatar { margin-inline-end: -1.15rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-header .player-switch:has(.player-avatar:not([hidden])) .player-input { padding-inline-start: 1.35rem; border-start-start-radius: 0; border-end-start-radius: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-header .harvest-header-btn,
html.ceres-lw-le-1919.ceres-lw-le-1279 .board-header .checkout-dock { display: none !important; }
/* IR-9253 (owner: "place the switcher as the last button on both views at all times"): on a phone the header has two lines on
   both pages -- line 1 the logo and the player (and the Cart on Cards), line 2 the page's own tools (My cards, Market, Wallet
   on Cards) and the Plots | Cards switcher, always last at the right edge. The 390 px logo line has no room for it beside the
   player field, and one shared line 2 puts the switcher on the same pixels on both pages. Desktop keeps one row. */
.board-header-break { display: none; }
html.ceres-lw-le-1919.ceres-lw-le-1279 header.board-header { flex-wrap: wrap; row-gap: 0.4rem; block-size: auto; min-block-size: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 header.board-header :is(.board-header-rows, .board-header-row) { display: contents; }
html.ceres-lw-le-1919.ceres-lw-le-1279 header.board-header .board-header-break { display: block; order: 8; flex: 0 0 100%; block-size: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 header.board-header .ceres-page-switch { order: 11; margin-inline-start: auto; }
html.ceres-lw-le-1919.ceres-lw-le-1279 header.board-header .player-switch { flex: 1 1 0; min-inline-size: 0; justify-content: flex-end; } /* stays on line 1 beside the logo; the field keeps its own width cap */
html.ceres-lw-le-1919.ceres-lw-le-1279 main#board-main { padding: 0.7rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #table-head-sticky,
html.ceres-lw-le-1919.ceres-lw-le-1279 #plot-table-scroll { display: none !important; }

html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.7rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card { min-inline-size: 0; padding: 0.8rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); box-shadow: 0 0.2rem 0.7rem color-mix(in srgb, var(--shadow) 24%, transparent); }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card.row-alt { background: color-mix(in srgb, var(--bg2) 82%, var(--bg3)); }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 0.7rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-label { display: block; color: var(--heading); font-size: 1rem; font-variant-numeric: tabular-nums; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-region { display: block; margin-block-start: 0.15rem; color: var(--meta); font-size: 0.72rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-marks { display: inline-flex; align-items: center; justify-content: flex-end; gap: 0.45rem; min-inline-size: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-worksite { display: inline-flex; align-items: center; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-terrain { display: inline-flex; align-items: center; gap: 0.15rem; min-inline-size: 0; color: var(--body); font-size: 0.72rem; white-space: nowrap; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-terrain .terrain-emblem { inline-size: 1.1rem; block-size: 1.1rem; margin: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.35rem; margin: 0.75rem 0 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-stats > div { min-inline-size: 0; padding: 0.45rem; border-radius: calc(var(--radius) - 3px); background: var(--bg3); }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-stats dt { color: var(--meta); font-size: 0.65rem; font-weight: 700; text-transform: uppercase; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-stats dd { margin: 0.2rem 0 0; color: var(--heading); font-size: 0.9rem; font-weight: 800; font-variant-numeric: tabular-nums; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-stats .resource-icon { inline-size: 1.05rem; block-size: 1.05rem; margin-inline-end: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-badges { margin-block-start: 0.65rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-badges .badge-row { inline-size: auto; max-inline-size: none; flex-wrap: wrap; gap: 0.18rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card-empty { grid-column: 1 / -1; margin: 1rem; color: var(--meta); text-align: center; }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 .plot-card-list { grid-template-columns: minmax(0, 1fr); }

html.ceres-lw-le-1279 .controls-row { display: none; }
html.ceres-lw-le-1279 .controls-row.is-mobile-open { position: fixed; inset: auto 0 0; z-index: 1200; display: flex; visibility: visible; align-content: flex-start; gap: 0.65rem; box-sizing: border-box; max-block-size: calc(var(--ceres-vh) * 100 - 3.5rem); padding: 0.8rem max(0.8rem, env(safe-area-inset-right)) calc(1rem + env(safe-area-inset-bottom)) max(0.8rem, env(safe-area-inset-left)); overflow-y: auto; border: 1px solid var(--border-bright); border-radius: var(--radius) var(--radius) 0 0; background: var(--bg2); box-shadow: 0 -0.5rem 2rem color-mix(in srgb, var(--shadow) 55%, transparent); }
html.ceres-lw-le-1279 .controls-row .mobile-filter-sheet-head { display: flex; flex: 1 0 100%; align-items: center; justify-content: space-between; box-sizing: border-box; padding-inline-end: 3.5rem; color: var(--heading); }
html.ceres-lw-le-1279 .controls-row .mobile-filter-sheet-head button { flex: none; }
html.ceres-lw-le-1279 #header.theme-picker-dock { display: none; }
html.ceres-lw-le-1279 #header.theme-picker-dock:has(+ .controls-row.is-mobile-open) { position: fixed; top: var(--mobile-filter-sheet-top, 3.5rem) !important; right: var(--mobile-filter-sheet-right, 0px); display: block; }
/* S-14180: desktop-only -- mobile keeps the filter sheet's own Wallet button and has
   no badge overlay to toggle. */
html.ceres-lw-le-1279 .edge-tabs { display: none; }
html.ceres-lw-le-1279 #scroll-top { display: none; }
/* IR-6730: #badge-bar/#badge-flag moved OUT of #plot-table-scroll (see that
   element's own comment) -- they no longer inherit this tier's hide rule from an
   ancestor (`html.ceres-lw-le-1919.ceres-lw-le-1279 #plot-table-scroll`, above)
   the way they used to as its descendants; each needs its own explicit rule now. */
html.ceres-lw-le-1279 #badge-bar { display: none; }
html.ceres-lw-le-1279 #badge-flag { display: none; }
html.ceres-lw-le-1279 .controls-row .board-filter-field { flex: 1 1 calc(50% - 0.35rem); }
html.ceres-lw-le-1279 .controls-row .board-filter-field .csel-wrap { inline-size: 100%; }
html.ceres-lw-le-1279 .controls-row .board-filter-field .csel-trigger { inline-size: 100%; }

html.ceres-lw-le-1279 .controls-row input[type="text"]#text-filter { box-sizing: border-box; inline-size: 100%; min-block-size: 2.7rem; }
html.ceres-lw-le-1279 .controls-row .board-filter-pill { box-sizing: border-box; justify-content: center; }
html.ceres-lw-le-1279 .controls-row .board-clear-filters-row { display: flex; flex: 1 0 100%; justify-content: center; }
html.ceres-lw-le-1279 .controls-row .board-clear-filters-row:has([hidden]) { display: none; }

html.ceres-lw-le-1279 .controls-row > .close-all-btn { display: none !important; }
html.ceres-lw-le-1279 #header.theme-picker-dock:has(+ .controls-row.is-search-mode),
html.ceres-lw-le-1279 .controls-row.is-search-mode > .board-filter-pill,
html.ceres-lw-le-1279 .controls-row.is-search-mode > .board-filter-field:not(:has(#text-filter)) { display: none; }
html.ceres-lw-le-1279 .controls-row.is-search-mode .board-filter-field:has(#text-filter) { flex-basis: 100%; }
html.ceres-lw-le-1279.ceres-lw-le-767 .controls-row .board-filter-field { flex-basis: 100%; }
html.ceres-lw-le-1279 .mobile-filter-backdrop:not([hidden]) { position: fixed; inset: 0; z-index: 1190; display: block; inline-size: 100%; block-size: 100%; border: 0; background: color-mix(in srgb, var(--shadow) 60%, transparent); }

html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-action-bar { position: fixed; inset: auto 0 0; z-index: 20; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); padding: 0.35rem max(0.35rem, env(safe-area-inset-right)) calc(0.35rem + env(safe-area-inset-bottom)) max(0.35rem, env(safe-area-inset-left)); border-top: 1px solid var(--border-bright); background: color-mix(in srgb, var(--bg2) 94%, transparent); box-shadow: 0 -0.3rem 1rem color-mix(in srgb, var(--shadow) 35%, transparent); }

html.ceres-lw-le-1919.ceres-lw-le-1279 #mobile-filters-count:not([hidden]) { display: inline-grid; place-items: center; min-inline-size: 1.15rem; block-size: 1.15rem; padding-inline: 0.15rem; border-radius: 999px; background: var(--accent); color: var(--bg); font-size: 0.62rem; }
/* FR-1946 PHASE D MOBILE BOARD END */

/* FR-1946 PHASE E MOBILE PLOT SHEET START */
html.ceres-lw-le-1919.ceres-lw-le-1279 body.mobile-plot-sheet-open { overflow: hidden; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card { cursor: pointer; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .plot-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet:not([hidden]) { position: fixed; inset: 0; z-index: 1100; display: flex; flex-direction: column; min-inline-size: 0; background: var(--bg); color: var(--body); }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet-head { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; padding: calc(0.75rem + env(safe-area-inset-top)) max(0.8rem, env(safe-area-inset-right)) 0.75rem max(0.8rem, env(safe-area-inset-left)); border-bottom: 1px solid var(--border-bright); background: var(--bg2); }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet-head > div { min-inline-size: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet-head strong:not(.ch-plot-badge) { display: block; color: var(--heading); font-size: 1.15rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet-head span { display: block; margin-block-start: 0.15rem; overflow: hidden; color: var(--meta); font-size: 0.72rem; text-overflow: ellipsis; white-space: nowrap; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet-head .btn-close { flex: none; }

html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet-body { flex: 1 1 auto; min-block-size: 0; padding: 0.8rem max(0.8rem, env(safe-area-inset-right)) calc(1rem + env(safe-area-inset-bottom)) max(0.8rem, env(safe-area-inset-left)); overflow-y: auto; overscroll-behavior: contain; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet-body > .plot-card-stats { grid-template-columns: minmax(0, 1fr); margin: 0.8rem 0 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-worksite { position: relative; isolation: isolate; min-block-size: 10rem; margin-block-end: 0.8rem; overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-worksite-art { display: block; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-worksite-label { position: absolute; inset: auto 0.6rem 0.6rem; z-index: 1; display: flex; align-items: center; gap: 0.45rem; inline-size: fit-content; max-inline-size: calc(100% - 1.2rem); padding: 0.4rem 0.55rem; border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb, var(--bg) 86%, transparent); color: var(--heading); }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-worksite-label strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .slot-editor { inline-size: 100%; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .editor-band { inline-size: 100%; block-size: auto; min-block-size: 0; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); overflow: visible; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .eos-scenic-veil,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .eos-worker-cover,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .editor-band > .eos-close,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .slot-group-controls,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .editor-base-pp-row,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .terrain-affinity,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .mobile-phase-f-action { display: none !important; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .editor-band > .editor-main { position: relative; inset: auto; display: block; padding: 0.8rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .editor-band .slot-editor-body { position: static; inset: auto; block-size: auto; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .editor-band .slot-group { block-size: auto; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .editor-band .slot-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(6.7rem, 1fr)); block-size: auto; margin: 0; padding: 2rem 0.4rem 0.8rem; overflow: visible; gap: 1.4rem 0.8rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .slot-grid > * { justify-self: center; flex-shrink: 1; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .slot-grid-divider { display: none; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .slot-tile { cursor: default; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .mobile-plot-actions { position: relative; inset: auto; display: flex; justify-content: center; block-size: auto; min-block-size: 4rem; margin: 0; padding: 0.7rem 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .slot-editor-footer > .harvest-bar { position: relative; inset: auto; transform: none; max-inline-size: 100%; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .eos-status { position: relative; inset: auto; justify-content: center; max-inline-size: none; padding: 0.65rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 .mobile-plot-sheet .editor-band .slot-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

html.ceres-lw-le-1919.ceres-lw-le-1279 #harvest-modal-overlay,
html.ceres-lw-le-1919.ceres-lw-le-1279 #worksite-modal-overlay { align-items: stretch; padding: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #harvest-modal-overlay > .harvest-panel,
html.ceres-lw-le-1919.ceres-lw-le-1279 #worksite-modal-overlay > .harvest-panel { inline-size: 100%; min-inline-size: 0; max-inline-size: none; block-size: 100%; max-block-size: none; border: 0; border-radius: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #harvest-modal-overlay .hm-headr,
html.ceres-lw-le-1919.ceres-lw-le-1279 #worksite-modal-overlay .hm-headr { padding-block-start: calc(0.9rem + env(safe-area-inset-top)); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #harvest-modal-overlay .hm-foot,
html.ceres-lw-le-1919.ceres-lw-le-1279 #worksite-modal-overlay .hm-foot { padding-block-end: calc(0.9rem + env(safe-area-inset-bottom)); }
/* FR-1946 PHASE E MOBILE PLOT SHEET END */

/* FR-1946 PHASE F MOBILE STAGING START */
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .slot-tile,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet button.power-satellite { cursor: pointer; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .candidate-visible-slice { position: static; inline-size: 100%; max-inline-size: 100%; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .inline-picker { inline-size: 100%; max-inline-size: 100%; overflow: visible; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-bar { align-items: flex-start; padding: 0.65rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-bar-title { flex-basis: 100%; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-bar-end { order: 3; flex: 1 1 100%; justify-content: space-between; margin-inline-start: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-filters { order: 2; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-view-switch,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-combine { display: none !important; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .mobile-desktop-only-note { margin: 0.25rem 0.8rem 0.8rem; color: var(--muted); font-size: 0.78rem; text-align: center; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-filter-field,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-filter-field input,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-filter-field .csel-wrap,
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .picker-filter-field .csel-trigger { max-inline-size: none; }
html.ceres-lw-le-1919.ceres-lw-le-1279 .mobile-plot-sheet .candidate-list.is-card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.65rem; padding: 0.65rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 .mobile-plot-sheet .candidate-list.is-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-544 .mobile-plot-sheet .candidate-list.is-card-grid { grid-template-columns: minmax(0, 1fr); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay { align-items: stretch; padding: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay > .changes-panel { inline-size: 100%; min-inline-size: 0; max-inline-size: none; block-size: 100%; max-block-size: none; border: 0; border-radius: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-headr { padding: calc(0.9rem + env(safe-area-inset-top)) max(0.8rem, env(safe-area-inset-right)) 0.75rem max(0.8rem, env(safe-area-inset-left)); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-body { padding: 0.8rem max(0.8rem, env(safe-area-inset-right)) 0.9rem max(0.8rem, env(safe-area-inset-left)); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot { align-items: stretch; padding: 0.75rem max(0.8rem, env(safe-area-inset-right)) calc(0.75rem + env(safe-area-inset-bottom)) max(0.8rem, env(safe-area-inset-left)); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot,
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot .hm-btns { flex-wrap: wrap; }
/* IR-8483: the phone lists changes as rows, so a plot card never scrolls on its own (overflow-x: auto made every card a
   scroll box with its own vertical scroll). */
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-plot { max-inline-size: 100%; overflow: visible; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-plot-layout { display: block; }
/* IR-8484 (owner: "no background leaking through ... suddenly the board is scrolled instead of the changes screen"): while
   Changes is open the page behind it cannot scroll, and a scroll that reaches the end of Changes stops there. */
html.ceres-lw-le-1919.ceres-lw-le-1279:has(#changes-overlay:not([hidden])), html.ceres-lw-le-1919.ceres-lw-le-1279:has(#changes-overlay:not([hidden])) body { overflow: hidden; overscroll-behavior: none; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay, html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-body { overscroll-behavior: contain; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mlist { display: grid; gap: 0.45rem; margin: 0.6rem 0 0; padding: 0; list-style: none; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow { --ch-tone: var(--ok); display: grid; grid-template-columns: 2.6rem minmax(0, 1fr) auto; align-items: center; gap: 0.6rem;
  padding: 0.45rem 0.5rem; border: 1px solid var(--border); border-inline-start: 3px solid var(--ch-tone); border-radius: var(--radius); background: var(--bg2); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow.is-warn { --ch-tone: var(--warn); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow.is-waiting { --ch-tone: var(--warn); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow.is-bad { --ch-tone: var(--danger); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow.is-dim { --ch-tone: var(--border-bright); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow.is-undone { --ch-tone: var(--border); border-style: dashed; opacity: 0.7; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow-thumb { display: grid; place-items: center; inline-size: 2.6rem; block-size: 2.6rem; overflow: hidden; border-radius: 6px; background: var(--bg3); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow-thumb img { inline-size: 100%; block-size: 100%; object-fit: cover; object-position: center 20%; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow-thumb.is-icon svg { inline-size: 1.5rem; block-size: 1.5rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow-text { display: grid; gap: 0.1rem; min-inline-size: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow-text strong { overflow: hidden; color: var(--heading); font-size: 0.88rem; text-overflow: ellipsis; white-space: nowrap; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow-what { color: var(--meta); font-size: 0.74rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow-text .ch-status { font-size: 0.74rem; line-height: 1.3; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow-undo { min-inline-size: 0; padding-inline: 0.6rem; font-size: 0.78rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .mobile-keychain-guidance { border-color: var(--warn); }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .mobile-keychain-guidance p { margin: 0 0 0.7rem; color: var(--heading); }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 #changes-overlay .hm-foot .hm-note,
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 #changes-overlay .hm-foot .hm-btns { flex: 1 1 100%; margin-inline-start: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 #changes-overlay .hm-foot .hm-btns { justify-content: stretch; }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 #changes-overlay .hm-foot .hm-btn { flex: 1 1 auto; }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 #changes-overlay .hm-foot .wallet-sign-pair { flex: 1 1 100%; }
/* IR-8479 (owner: "Wallet switcher and sign button should sit side by side in one row. Make the undo all button smaller
   and move to the top, left of the x button ... Remove the large empty space. Optimize the signing section for mobile"). */
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-head-actions { display: flex; flex: none; align-items: center; gap: 0.5rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-head-actions .checkout-undo-all { min-inline-size: 0; padding-inline: 0.7rem; font-size: 0.8rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot { gap: 0.4rem; padding-block-start: 0.5rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot .hm-note { margin: 0; font-size: 0.78rem; line-height: 1.3; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot .hm-note:empty { display: none; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot .wallet-sign-pair { display: flex; flex: 1 1 100%; flex-wrap: nowrap; align-items: stretch; gap: 0.5rem; min-inline-size: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot .wallet-sign-pair > .wallet-controls { flex: none; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot .wallet-sign-pair > .hm-btn { flex: 1 1 0; min-inline-size: 0; padding-inline: 0.6rem; gap: 0.4rem; white-space: nowrap; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-foot .wallet-sign-pair .wallet-sign-label { flex-wrap: nowrap; white-space: nowrap; }
html.ceres-lw-le-1919.ceres-lw-le-1279.ceres-lw-le-767 #changes-overlay .hm-foot .wallet-sign-pair > .hm-btn { font-size: 0.85rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-plot-aside:empty { display: none; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-body { display: grid; align-content: start; gap: 0.6rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .hm-body > * { margin-block: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-signing { padding: 0.7rem 0.8rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-signing .hm-sec-h { margin: 0 0 0.3rem; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-signing-status { min-block-size: 0; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-signing-summary { margin: 0 0 0.25rem; font-size: 0.9rem; line-height: 1.35; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-signing-status > p:last-child { margin-block-end: 0; }

html.ceres-lw-le-1919.ceres-lw-le-1279 #mobile-changes-count:not([hidden]) { display: inline-grid; place-items: center; min-inline-size: 1.15rem; block-size: 1.15rem; padding-inline: 0.15rem; border-radius: 999px; background: var(--accent); color: var(--bg); font-size: 0.62rem; }
/* FR-1946 PHASE F MOBILE STAGING END */

/* Custom picker menus extend beyond their label; text fields keep their clipping. */
.picker-filters .picker-filter-field:has(.csel-wrap) { overflow: visible; }

.candidate-source-plot { justify-content: center; max-inline-size: 100%; min-inline-size: 0; }
.candidate-source-plot .ch-plot-jump { max-inline-size: 100%; }
.candidate-source-plot .ch-plot-badge { white-space: nowrap; }
.candidate-grid-card > .candidate-source-plot { justify-self: center; }
html.ceres-lw-le-1279 .candidate-source-plot .ch-plot-jump { min-block-size: 2.75rem; }

.candidate-list.is-card-grid > .candidate-grid-card { grid-template-areas: none; }
.candidate-grid-card > .candidate-source-plot { grid-column: 1 / -1; }

.harvest-receipt-summary, .harvest-receipt { box-sizing: border-box; min-inline-size: 0; padding: 1rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); }
.harvest-receipt-summary { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 8%, var(--bg2)); }
.harvest-receipt-success { display: flex; align-items: center; gap: .75rem; margin-block-end: 1rem; }
.harvest-receipt-success > span { color: var(--ok); font-size: 1.8rem; }
.harvest-receipt-success h2 { margin: 0; color: var(--ok); font-size: 1.25rem; }
.harvest-receipt-success p, .harvest-receipt-note { margin: .35rem 0 0; color: var(--body); font-size: .9rem; line-height: 1.5; overflow-wrap: anywhere; }
.harvest-receipt-summary > h3, .harvest-receipt h4 { margin: 0 0 .65rem; color: var(--meta); font-size: .8rem; font-weight: 700; }
.harvest-receipt-resources { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr)); gap: .75rem; margin: 0; }
.harvest-receipt-resources > div { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: .1rem .45rem; min-inline-size: 0; }
.harvest-receipt-resources img { grid-row: 1; inline-size: 1.45rem; block-size: 1.45rem; object-fit: contain; }
.harvest-receipt-resources dt { grid-column: 2; font-size: .78rem; color: var(--body); }
.harvest-receipt-resources dd { grid-column: 1 / -1; margin: 0; color: var(--heading); font-size: 1rem; font-weight: 800; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.harvest-receipts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr)); align-items: start; gap: 1rem; }
.harvest-receipt > header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem; margin-block-end: 1rem; }
.harvest-receipt h3 { margin: 0; font-size: 1rem; color: var(--heading); overflow-wrap: anywhere; }
.harvest-receipt-status { padding: .18rem .5rem; border-radius: var(--radius); color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, var(--bg2)); font-size: .78rem; font-weight: 700; }
.harvest-receipt > section + section, .harvest-receipt-bonus { margin-block-start: 1rem; padding-block-start: 1rem; border-block-start: 1px solid var(--border); }
.harvest-receipt-costs { display: grid; gap: .8rem; margin-block-start: 1rem; padding-block-start: 1rem; border-block-start: 1px solid var(--border); }
.harvest-receipt-rewards { display: grid; gap: .5rem; list-style: none; padding: 0; margin: 0; }
.harvest-receipt-rewards li { display: flex; flex-direction: column; gap: .2rem; padding: .6rem .75rem; border-inline-start: 3px solid var(--ok); border-radius: var(--radius); background: color-mix(in srgb, var(--ok) 8%, var(--bg2)); overflow-wrap: anywhere; }
.harvest-receipt-rewards strong { color: var(--heading); font-size: .95rem; }
.harvest-receipt-rewards span { color: var(--body); font-size: .82rem; }
.harvest-receipt-transaction { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem; font-size: .9rem; padding: .75rem; border: 1px solid var(--border); border-radius: var(--radius); text-underline-offset: .2rem; }
.harvest-receipt-transaction span { color: inherit; font-variant-numeric: tabular-nums; }

.harvest-receipt-summary.is-partial { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 8%, var(--bg2)); }
.harvest-receipt-summary.is-partial :is(.harvest-receipt-success h2, .harvest-receipt-success > span) { color: var(--warn); }
.harvest-receipt-status.is-muted { color: var(--meta); background: var(--bg3); }

.hm-grid2 > .hm-card:only-child { grid-column: 1 / -1; }

/* Receipt amounts share one responsive row; a single collection needs no duplicate total. */
.harvest-receipt-summary .harvest-receipt-success:last-child { margin-block-end: 0; }
.harvest-receipt-financials { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)); gap: 1rem; }
.harvest-receipt-rewards li { flex-direction: row; align-items: center; gap: .65rem; }
.harvest-receipt-rewards li > div { display: flex; flex-direction: column; gap: .25rem; min-inline-size: 0; }
.harvest-receipt-rewards .ability-icon { inline-size: 2rem; block-size: 2rem; flex: 0 0 auto; }

/* S-11440: the regional resource drawer starts with DEC. */
#regional-dec-button { margin-inline-start: auto; flex: 0 0 auto; }
#regional-dec-button img, .dec-drawer h2 img { width: 1.5em; height: 1.5em; object-fit: contain; }
.dec-drawer { --dec-panel-width: min(34rem, 100%); position: fixed; inset: 0; margin: 0; width: 100%; height: 100dvh; max-height: none; max-width: 100%; padding: 0; border: 0; background: transparent; color: var(--text); box-sizing: border-box; overflow: hidden; }
.dec-drawer[open] { display: block; }
/* S-14210: the wallet drawer joins the shared right-edge recipe (blur, radius, glow)
   at 92% (denser than the overlay panel's 88%) since it carries the densest text of
   the three surfaces. Pre-landing review fix: fill is `--bg2`, not the picker/bar's
   `--shadow` tint -- this drawer is read, not glanced at, and `--shadow` reads too
   dark for text contrast in the light/wood themes; `--shadow` stays reserved for the
   glyph-only surfaces (`.edge-picker`, `.badge-bar`). */
.dec-drawer-panel { position: absolute; inset: 0 0 0 auto; width: var(--dec-panel-width); height: 100%; display: flex; flex-direction: column; box-sizing: border-box; background: color-mix(in srgb, var(--bg2) 92%, transparent); backdrop-filter: saturate(130%) blur(4px); -webkit-backdrop-filter: saturate(130%) blur(4px); border-inline-start: 0; border-radius: 5px 0 0 5px; box-shadow: 0 0 18px -6px var(--primary); overflow: hidden; }

.dec-drawer-backdrop { position: absolute; inset: 0 auto 0 0; width: calc(100% - var(--dec-panel-width)); }
.dec-drawer::backdrop { background: color-mix(in srgb, var(--shadow) 55%, transparent); }
.dec-drawer-header, .dec-drawer-footer { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem; flex: 0 0 auto; }
.dec-drawer-header { border-bottom: 1px solid var(--border); }
/* S-13860: wallet resource tabs under the drawer header; the DEC tab keeps the regional stake view. */
.wallet-tabs { padding-inline: 1rem; border-bottom: 1px solid var(--border); flex: 0 0 auto; }
.wallet-tabs .wallet-tab { gap: .3rem; }
/* S-14250 (owner): the drawer's own tab row mirrors the edge picker's icon-only tray
   look -- no text label, Ceres tooltip (data-tip) instead. `.wallet-tab--icon` keeps
   every tab reachable to switch between resources -- the active one stays fully
   visible (opacity 1, plus the S-14310 Iris chip below) rather than hidden; the
   picker's own `.edge-opt--active` (S-14250 pre-landing fix) already made the same
   choice for the same reason. */
.wallet-tabs--icons {
  display: inline-flex;
  align-items: center;
  gap: 0;
  padding: 0 4px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--shadow) 40%, transparent);
  border-bottom: 0;
  margin: .6rem 1rem;
}
.wallet-tab--icon {
  height: 40px;
  min-height: 40px;
  background: none;
  border: 0;
  padding: 0 8px;
  opacity: .4;
  cursor: pointer;
  transition: opacity .2s ease;
}
.wallet-tab--icon:hover { opacity: .8; }
/* S-14310 (owner: "make sure it has the same subtle secondary background for the
   selected resource like iris"): the same chip `.edge-opt--active`/`.edge-toggle`
   carry (canonical.css `.theme-toggle`'s own look). Pre-landing review fix: full `5px`
   radius (not `.edge-toggle`'s `5px 0 0 5px`) -- this tab sits mid-row, surrounded on
   all sides, unlike the toggle which is flush against the picker's own right edge. */
.wallet-tab--icon[aria-selected="true"] {
  opacity: 1;
  background: color-mix(in srgb, var(--primary) 22%, transparent);
  border-radius: 5px;
  box-shadow: 0 8px 22px color-mix(in srgb, var(--shadow) 35%, transparent), inset 0 1px 0 color-mix(in srgb, var(--hi) 6%, transparent);
}
.wallet-tab--icon[disabled] { opacity: .2; cursor: default; }
.wallet-tab--icon img { inline-size: 22px; block-size: 22px; object-fit: contain; }
.wallet-token-icon { inline-size: 1.25em; block-size: 1.25em; object-fit: contain; vertical-align: -0.25em; margin-inline-start: .25em; }
.wallet-tabs .wallet-token-icon { margin-inline-start: 0; }
.wallet-card { padding: 1rem; margin-block: 0 1rem; border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg); }
.wallet-card h3 { margin: 0 0 .75rem; font-size: 1.05rem; display: flex; align-items: baseline; gap: .5rem; }
.wallet-card h3 small { font-weight: 400; color: var(--text-dim); font-size: .8rem; }
.wallet-card .dec-figures { margin: .5rem 0; }
.wallet-regions { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.wallet-regions li { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: .6rem; }
.wallet-regions li.is-zero { opacity: .45; } /* S-14090: zero holdings greyed, still listed */
.wallet-reward-label { font-weight: 600; }
.wallet-claim-actions { margin-block: 0 1rem; }
.wallet-region-plots { color: var(--text-dim); font-size: .85rem; }
.wallet-figure { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.wallet-unavailable { color: var(--warn); font-style: italic; }
.wallet-meta { margin: .5rem 0 0; color: var(--text-dim); font-size: .85rem; line-height: 1.5; }
.dec-drawer-header h2 { display: flex; align-items: center; gap: .5rem; margin: 0; font-size: 1.25rem; }
.dec-drawer-content { padding: 1rem; overflow-y: auto; min-height: 0; flex: 1; }
.dec-drawer-footer { border-top: 1px solid var(--border); padding-bottom: max(1rem, env(safe-area-inset-bottom)); }
.dec-region, .dec-review { padding: 1rem; margin-block: 0 1rem; border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg); }
.dec-region h3, .dec-review h3 { margin: 0 0 1rem; font-size: 1.05rem; }
/* IR-8584 (owner: "hard to see where one box in the wallet ends and where the next begins ... all wallet features"):
   every wallet box stands off the panel -- a raised surface, a solid accent edge on top, a soft drop shadow and
   more room between boxes. The boxes inside a box (swap/pool fields) stay on the darker base, so they read as inset. */
.dec-drawer-content :is(.wallet-card, .dec-region, .dec-review) {
  margin-block-end: 1.35rem;
  border-block-start-width: 3px;
  box-shadow: 0 8px 18px -10px var(--bg), 0 0 0 1px color-mix(in srgb, var(--bg) 60%, transparent);
}
.dec-drawer-content :is(.wallet-card, .dec-region, .dec-review):not(.is-confirmed):not(.is-failed) { border-block-start-color: var(--primary); background: linear-gradient(160deg, color-mix(in srgb, var(--text) 14%, transparent), color-mix(in srgb, var(--text) 6%, transparent)); }  /* IR-9128: light glass, not an opaque fill */
.dec-drawer-content :is(.wallet-card, .dec-region, .dec-review):last-child { margin-block-end: 0; }
.dec-figures { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; margin: 1rem 0; }
.dec-figures dt { font-size: .85rem; color: var(--text-dim); }
.dec-figures dd { margin: .3rem 0 0; font-weight: 700; overflow-wrap: anywhere; }
.dec-shortfall, .dec-error { color: var(--danger); }
.dec-surplus { color: var(--ok); }
.dec-region-actions, .dec-shortcuts { display: flex; gap: .75rem; margin-top: .75rem; }
.dec-region-actions > button { flex: 1; }
.dec-region-actions > .wallet-sign-pair { flex: 1 0 0; min-inline-size: max-content; }
.dec-region-actions > .wallet-sign-pair > .ceres-btn { flex: 1 0 0; min-inline-size: max-content; white-space: nowrap; }
.dec-amount { display: grid; gap: .5rem; }
.dec-amount input { width: 100%; min-width: 0; box-sizing: border-box; padding: .75rem; font: inherit; color: var(--text); background: var(--bg3); border: 1px solid var(--border-bright); border-radius: var(--radius); }
/* S-16900 (owner): every wallet amount field is right-aligned. */
.dec-drawer .dec-amount input { text-align: right; font-variant-numeric: tabular-nums; }
.dec-warning { color: var(--warn); line-height: 1.5; }
.dec-review p { line-height: 1.5; }
.dec-review pre { white-space: pre-wrap; overflow-wrap: anywhere; font-size: .75rem; }
.dec-liquid { display: flex; justify-content: space-between; gap: 1rem; margin: 0 0 1rem; }
.dec-wallet { margin-top: 1rem; }
.dec-status { display: flex; align-items: center; gap: .75rem; }
.wallet-zero-regions > summary { margin-block-start: .35rem; color: var(--text-dim); cursor: pointer; } /* S-16889 */
/* S-16873: the wallet pool swap card (option B, pair card). Scoped to .wallet-swap only -- no rule here
   touches #wallet-picker, the edge tabs, the drawer panel geometry, shadows or hover (F9). Every control
   in the editor is the shared compact control (buttons, .ceres-wide-input, the shared dropdown), so they
   share one height and one font size (S-16855). */
.wallet-swap { display: grid; gap: .65rem; padding: .8rem; font-variant-numeric: tabular-nums; }
.wallet-swap h3 { margin: 0; }
.wallet-swap img { inline-size: 1.5rem; block-size: 1.5rem; object-fit: contain; flex: none; }
.wallet-swap-pcts { display: inline-flex; gap: .3rem; } /* S-16890: 25/50/75/100 % */
/* IR-8582: the pool position card under the swap -- Add / Withdraw toggles and the read-only paired figures */
.wallet-swap .wallet-pool-actions { display: flex; gap: .4rem; }
.wallet-swap .wallet-pool-actions > .ceres-btn { flex: 1 1 50%; }
.wallet-swap .wallet-pool-figure { font-size: 1.25rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.wallet-swap.wallet-pool .dec-figures { margin: 0; }
.wallet-swap-head, .wallet-swap-line, .wallet-swap-asset { display: flex; align-items: center; justify-content: space-between; gap: .55rem; min-inline-size: 0; }
.wallet-swap-head { padding-block-end: .35rem; border-block-end: 1px solid var(--border); }
.wallet-swap-title { display: flex; align-items: center; gap: .45rem; font-weight: 700; color: var(--heading); }
.wallet-swap-title > span { display: inline-flex; align-items: center; gap: .45rem; } /* S-16897: [icon] Wood → [icon] DEC */
.wallet-swap-note, .wallet-swap small { color: var(--text-dim); }
/* IR-8454 (owner: "A confirmed swap must use full green color coding"): the confirmed swap card is green throughout --
   the same --ok recipe as a confirmed signing step (.ch-signing-progress li.is-confirmed). */
.wallet-swap.is-confirmed { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, var(--bg2)); }
.wallet-swap.is-confirmed :is(h3, .dec-receipt-amount span, .dec-receipt-amount strong, .dec-receipt-amount small) { color: var(--ok); }
.wallet-swap.is-confirmed h3::before { content: '✓ '; }
.wallet-swap-box { display: grid; gap: .3rem; padding: .65rem; border: 1px solid var(--border); border-radius: calc(var(--radius) - 3px); background: var(--bg); }
.wallet-swap-name { display: inline-flex; align-items: center; gap: .55rem; min-inline-size: 0; }
.wallet-swap-name > strong { color: var(--heading); white-space: nowrap; }
/* S-16900 (owner): icon · name · field, the field flush right and exactly as wide as the % buttons above it; amounts are
   right-aligned. The pay box is a two-column grid; its "You pay" and asset rows lend their children to it. */
.wallet-swap-box:has(> .wallet-swap-asset > .wallet-swap-amount) { grid-template-columns: minmax(0, 1fr) auto; column-gap: .55rem; }
.wallet-swap-box:has(> .wallet-swap-asset > .wallet-swap-amount) > :is(.wallet-swap-line, .wallet-swap-asset) { display: contents; }
.wallet-swap-box:has(> .wallet-swap-asset > .wallet-swap-amount) > .wallet-swap-sub { grid-column: 1 / -1; }
.wallet-swap-amount.ceres-wide-input { inline-size: 100%; min-inline-size: 0; box-sizing: border-box; text-align: right; font-variant-numeric: tabular-nums; }
.wallet-swap-out { margin-inline-start: auto; font-size: 1.25rem; font-weight: 800; font-variant-numeric: tabular-nums; } /* S-16894: the one number that matters */
.wallet-swap-receive { border-color: var(--primary); }
.wallet-swap-sub { display: flex; align-items: center; justify-content: space-between; gap: .55rem; min-inline-size: 0; color: var(--text-dim); font-size: .75rem; }
.wallet-swap-where { display: inline-flex; align-items: center; gap: .4rem; }
.wallet-swap-where .csel-wrap { position: relative; display: inline-block; }
.wallet-swap-details { display: grid; gap: .5rem; }
.wallet-swap-details > .ceres-btn { justify-self: start; }
.wallet-swap-arrow { justify-self: center; display: inline-flex; align-items: center; justify-content: center; min-inline-size: 3rem; block-size: var(--ceres-control-min-block); color: var(--text-dim); }
.wallet-swap [data-lp-flip] { justify-self: center; min-inline-size: 3rem; }
.wallet-swap-line b { white-space: nowrap; }
.wallet-swap .is-stale { opacity: .5; }
.wallet-swap [data-lp-primary] { inline-size: 100%; }
.wallet-swap-grid { display: grid; gap: .5rem; }
.wallet-swap-grid > .wallet-swap-line { padding-block-end: .45rem; border-block-end: 1px solid var(--border); }
.wallet-swap-grid > .wallet-swap-line:last-child { padding-block-end: 0; border-block-end: 0; }
.wallet-swap-line b { color: var(--heading); text-align: end; }
.wallet-swap.dec-receipt { padding: 1.4rem; }
.wallet-swap .dec-receipt-amount > img { inline-size: 3rem; block-size: 3rem; }
.wallet-swap-sign { display: flex; align-items: stretch; gap: .75rem; } /* IR-8453: chooser left of Swap while no wallet is ready */
.wallet-swap-sign > [data-lp-primary] { flex: 1 1 auto; }

@media (max-width: 640px) { .dec-drawer { --dec-panel-width: 100%; } #regional-dec-button { margin-inline-start: 0; } .dec-shortcuts { flex-wrap: wrap; } .dec-region-actions { flex-wrap: wrap; } .dec-region-actions > button { flex: 1 1 100%; } .dec-region-actions > .wallet-sign-pair { flex: 1 1 auto; min-inline-size: 0; } .dec-region-actions > .wallet-sign-pair > .ceres-btn { flex: 0 0 auto; } }

/* S-14390 (owner, verbatim): "redesign the wallet to basically fit under der slide
   out and serve is icon picker or a disconnected modal below it with the selected
   wallet." Desktop only -- the edge tray (#wallet-picker) is now the ONLY resource
   picker, so the full-height drawer becomes a small floating panel anchored under
   that tray instead; mobile (below, `html.ceres-lw-le-1279`) is untouched.
   Team-lead correction: the picker's own position is not a fixed/derivable constant
   (a later version moves it onto the filter row, where it scrolls) -- `top`/`right`/
   `max-height` are therefore NOT CSS vars written by app.js's own dock math. They are
   plain inline styles `regional-dec.js::positionDesktopPanel()` sets directly from
   `#wallet-picker`'s own live `getBoundingClientRect()` on open and on every resize/
   scroll/`ceres:table-layout` while the panel stays open, so the panel tracks the
   picker wherever it ends up. The values below are only the pre-JS/no-JS fallback.
   "Disconnected": the panel is shown non-modal (regional-dec.js openWalletDrawer(),
   `drawer.show()` on desktop) -- no native backdrop, the board stays fully usable
   while it is open. */
html:not(.ceres-lw-le-1279) .dec-drawer {
  inset: auto 0 auto auto;
  top: 5rem;
  width: min(34rem, calc(100vw - 2rem));
  height: auto;
  max-height: calc(100vh - 6rem);
  max-width: none;
  z-index: 39; /* S-14980: tracks .edge-tabs' new value (40, was 20 under IR-6930) --
     still one below it so the wallet toggle stays reachable while this panel is
     open, still above the now-sticky `.controls-row` (30) so the panel never
     disappears under the filter row, and still below `header.board-header` (50)
     so the panel never paints over the header either. */
}
html:not(.ceres-lw-le-1279) .dec-drawer-panel {
  position: static;
  width: 100%;
  height: auto;
  max-height: inherit;
  transform: translateX(100%);
  transition: transform .5s cubic-bezier(.4, .8, .4, 1);
  border-radius: 5px;
  box-shadow: none; /* IR-7540f: no edge shadow on desktop -- it flickered as a hard shadow box at the panel's left edge (IR-7540e). */
  /* Iris tray recipe (matches .edge-picker[data-open="true"]/.badge-bar[aria-expanded="true"]). */
  background: color-mix(in srgb, var(--shadow) 82%, transparent);
}
html:not(.ceres-lw-le-1279) .dec-drawer[data-wallet-motion="open"] .dec-drawer-panel { transform: translateX(0); }
/* S-16896 (owner: "Give the entire wallet window the iris glow"): the Iris glow (0 0 18px -3px --primary) around the whole
   open wallet window, drawn once on the drawer's own layer and only faded by opacity (S-16878 M-4) -- the glass panel is never
   repainted. It fades in only after the slide has ended and drops at once on close, so it never sits apart from the panel.
   The drawer clips the sliding panel, so the clip edge is widened just enough for the glow to show. */
html:not(.ceres-lw-le-1279) .dec-drawer { overflow-clip-margin: 20px; }
html:not(.ceres-lw-le-1279) .dec-drawer::after {
  content: ''; position: absolute; inset: 0; border-radius: 5px;
  box-shadow: 0 0 18px -3px var(--primary); opacity: 0; transition: opacity 0s; pointer-events: none;
}
html:not(.ceres-lw-le-1279) .dec-drawer[data-wallet-motion="open"]::after { opacity: 1; transition: opacity .3s ease .5s; }
@media (prefers-reduced-motion: reduce) { html:not(.ceres-lw-le-1279) .dec-drawer[data-wallet-motion="open"]::after { transition: none; } }
@media (prefers-reduced-motion: reduce) {
  html:not(.ceres-lw-le-1279) .dec-drawer-panel { transition: none; }
}
/* Same light/wood contrast fix as .badge-bar's own open state (above): --shadow reads
   too dark for this panel's text on a light ground, so those two themes swap to a
   --bg2 fill instead -- 92%, matching the drawer's own long-standing mobile fill
   (S-14210's own pre-landing fix), not .badge-bar's 88% (this panel carries denser
   text, same reasoning as that earlier fix). */
html:not(.ceres-lw-le-1279)[data-theme="light"] .dec-drawer-panel,
html:not(.ceres-lw-le-1279)[data-theme="wood"] .dec-drawer-panel {
  background: color-mix(in srgb, var(--bg2) 92%, transparent);
}
/* The panel is non-modal and "disconnected" -- no dimming, no click-blocking backdrop.
   The custom `.dec-drawer-backdrop` div (used for the mobile modal's own backdrop
   click-to-close) is hidden outright; the native `<dialog>` `::backdrop` pseudo does
   not exist at all for a non-modal `.show()`, so no rule for it is needed here --
   click-outside/Escape close the desktop panel through regional-dec.js's own document
   listeners instead (added in initRegionalDec()). */
html:not(.ceres-lw-le-1279) .dec-drawer-backdrop { display: none; }
/* S-14390: the slim caption line that replaces the header/tab row on desktop (the
   tray is the only picker there) -- resource icon, name, and, only while a pending
   DEC receipt or SPS claim forces this tab open, a small "pending" note so a forced
   tab is never silently unexplained. */
.wallet-caption { display: flex; align-items: center; gap: .5rem; padding: 1rem 1rem 0; font-weight: 700; }
.wallet-caption img { inline-size: 1.5em; block-size: 1.5em; object-fit: contain; }
.wallet-caption-pending { margin-inline-start: auto; font-size: .8rem; font-weight: 400; color: var(--warn); }

/* S-14180: Wallet now opens from the edge tab on desktop -- this button stays in the
   DOM (the mobile filter sheet still shows and uses it) but is removed from the
   desktop filter row. The old sticky-to-row-end positioning below no longer applies
   at this width but is left in place; it is harmless under display: none. */
html:not(.ceres-lw-le-1279) #regional-dec-button { position: sticky; inset-inline-end: 0; z-index: 3; background: var(--bg2); box-shadow: -0.5rem 0 0.5rem var(--bg2); display: none; }

/* S-11420: actual finds deserve an artwork-led moment in the receipt. */
.harvest-discovery { margin-block: 1rem 1.5rem; padding: 1.4rem; border: 1px solid var(--warn); border-radius: 1rem; background: radial-gradient(ellipse at 50% 20%, color-mix(in srgb, var(--warn) 16%, transparent), transparent 70%), var(--bg2); text-align: center; overflow: hidden; }
.harvest-discovery > header p { margin: 0 0 .4rem; color: var(--warn); font-size: .85rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
.harvest-discovery > header h3 { margin: 0; font-size: 1.7rem; color: var(--heading); }
.harvest-discovery-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); gap: 1.5rem; margin-block-start: 1.3rem; }
.harvest-discovery-prize { min-inline-size: 0; display: grid; justify-items: center; align-content: start; gap: 1rem; }
.harvest-discovery-art { position: relative; inline-size: 55%; aspect-ratio: 5 / 7; display: grid; place-items: center; isolation: isolate; }
.harvest-discovery-art::before { content: ''; position: absolute; inset: -12%; z-index: -1; border-radius: 50%; background: radial-gradient(ellipse, color-mix(in srgb, var(--warn) 30%, transparent), transparent 68%); animation: harvest-prize-glow 3s ease-in-out 3; }
.harvest-discovery-art > img { position: static; display: block; inline-size: 100%; block-size: 100%; object-fit: contain; border-radius: .75rem; filter: drop-shadow(0 .5rem 1rem color-mix(in srgb, var(--warn) 24%, transparent)); }
.harvest-discovery-prize.is-fragment .harvest-discovery-art > img { inline-size: 90%; block-size: 90%; }
.harvest-discovery-caption { min-inline-size: 0; }
.harvest-discovery-caption p { margin: 0 0 .45rem; display: flex; gap: .45rem; justify-content: center; align-items: center; color: var(--warn); font-weight: 800; }
.harvest-discovery-caption .ability-icon { inline-size: 1.65rem; block-size: 1.65rem; }
.harvest-discovery-caption h4 { margin: 0 0 .5rem; color: var(--heading); font-size: 1.35rem; overflow-wrap: anywhere; }
.harvest-discovery-caption > span { color: var(--body); line-height: 1.5; }
.harvest-discovery-fallback { display: grid; justify-items: center; gap: 1rem; color: var(--body); }
.harvest-discovery-fallback .ability-icon { inline-size: 5rem; block-size: 5rem; }
@keyframes harvest-prize-glow { 0%, 100% { opacity: .6; transform: scale(.96); } 50% { opacity: 1; transform: scale(1.05); } }
@media (prefers-reduced-motion: reduce) { .harvest-discovery-art::before { animation: none; } }
@media (max-width: 600px) { .harvest-discovery { padding: 1rem .75rem; } .harvest-discovery > header h3 { font-size: 1.45rem; } }

.harvest-discovery-grid:has(> :only-child) .harvest-discovery-prize { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; }
@media (max-width: 600px) { .harvest-discovery-grid:has(> :only-child) .harvest-discovery-prize { grid-template-columns: minmax(0, 1fr); } }

@media (max-width: 600px) { .harvest-discovery-art { inline-size: 75%; } }

/* S-11310/S-11320/S-11330/S-11460/S-11470: harvest routing review. */
.harvest-routing, .hr-editor, .hr-preflight { display: grid; gap: 1rem; min-inline-size: 0; }
.harvest-routing { box-sizing: border-box; inline-size: min(100%, 56rem); max-inline-size: 56rem; align-self: flex-start; }
.hr-entry, .hr-heading { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.hr-entry { border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg2); padding: 1rem; }
.hr-entry h3, .hr-heading h3 { margin: 0 0 .4rem; }
.hr-entry p, .hr-heading p { margin: 0; line-height: 1.5; }
.hr-fields { display: flex; flex-wrap: wrap; align-items: end; gap: .75rem; }
.hr-field { display: grid; align-content: start; gap: .35rem; min-inline-size: 0; }
.hr-fields > .hr-field { flex: 0 1 auto; }
.hr-fields > .ceres-btn[data-control-density] { --ceres-control-min-block: 1.875rem; --ceres-control-font-size: .8rem; --ceres-control-inline-pad: .65rem; min-block-size: var(--ceres-control-min-block); font-size: var(--ceres-control-font-size); padding-inline: var(--ceres-control-inline-pad); }
.hr-field > span { font-size: .85rem; font-weight: 700; color: var(--body); }
.hr-field input { box-sizing: border-box; inline-size: 100%; min-inline-size: 0; padding: .65rem; font: inherit; color: var(--text); background: var(--bg3); border: 1px solid var(--border-bright); border-radius: var(--radius); }
.hr-field .csel-wrap { inline-size: 100%; min-inline-size: 0; }
.hr-field .csel-trigger { inline-size: 100%; }
.hr-dec-source { display: grid; gap: .5rem; margin: .75rem 0 0; padding: .75rem; border: 1px solid var(--border-bright); border-radius: var(--radius); min-inline-size: 0; }
.hr-dec-source legend { padding-inline: .35rem; color: var(--heading); font-weight: 700; }
.hr-dec-source-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
.hr-dec-source-choice { min-inline-size: 0; justify-content: flex-start; text-align: start; }
.hr-dec-source-choice-copy { display: grid; gap: .2rem; min-inline-size: 0; }
.hr-dec-source-choice-copy small { color: var(--muted); line-height: 1.4; font-weight: 500; }
.hr-dec-source p { margin: .25rem 0; }
.hr-dec-provenance { color: var(--heading); font-weight: 700; }
.hr-dec-source--rule { grid-column: 1 / -1; grid-template-columns: minmax(0, 1fr); margin-block: 0; }
.hr-dec-source--rule > small { color: var(--muted); line-height: 1.4; }
.hr-rule, .hr-step, .hr-review { border: 1px solid var(--border-bright); border-radius: var(--radius); padding: 1rem; background: var(--bg2); min-inline-size: 0; }
.hr-rule { display: grid; gap: .75rem; }
.hr-rule-heading { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.hr-rule-heading h4 { margin: 0; color: var(--heading); }
.hr-step { margin-block: .75rem; }
.hr-step > header, .hr-rule-bottom, .hr-actions { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.hr-rule-bottom { margin-block-start: 1rem; }
.hr-rule-bottom > label { display: flex; align-items: center; gap: .5rem; }
.hr-rule-bottom input { flex: none; }
.hr-flow { display: flex; align-items: center; flex-wrap: wrap; gap: .7rem; margin-block: 1rem .5rem; }
.hr-amount { display: inline-flex; align-items: center; flex-wrap: wrap; gap: .45rem; }
.hr-amount > img { inline-size: 1.5rem; block-size: 1.5rem; object-fit: contain; }
.hr-amount > b { font-size: 1.2rem; color: var(--heading); }
.hr-step p { margin: .5rem 0; }
.hr-step small { display: block; line-height: 1.5; }
.hr-slippage { margin-block-start: .5rem; }
.hr-warning { color: var(--warn); }
.hr-error { color: var(--danger); line-height: 1.5; overflow-wrap: anywhere; }
.hr-review pre { white-space: pre-wrap; overflow-wrap: anywhere; font-size: .8rem; }
.hr-actions { justify-content: flex-start; margin-block: 1rem; }
@media (max-width: 600px) {
  .hr-heading, .hr-entry { align-items: stretch; flex-direction: column; }
  .hr-fields { grid-template-columns: minmax(0, 1fr); }
  .hr-actions { align-items: stretch; flex-direction: column; }
  .hr-actions > button, .hr-rule-bottom > button { inline-size: 100%; }
  .hr-dec-source-options { grid-template-columns: minmax(0, 1fr); }
}

.harvest-routing h3, .hr-editor > p, .hr-preflight > p { margin-block: 0; }
.hr-editor, .hr-preflight { align-content: start; }

.hr-reserves { border: 1px solid var(--border-bright); border-radius: var(--radius); padding: 1rem; background: var(--bg2); min-inline-size: 0; }
.hr-reserves > summary { cursor: pointer; font-weight: 700; color: var(--heading); }
.hr-reserves > p { line-height: 1.5; }
.hr-reserve { display: grid; gap: .75rem; margin-block: 1rem; }
.hr-reserve > button { justify-self: start; }

.hr-receipt { display: grid; gap: .75rem; padding: 1rem; border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg2); }
.hr-receipt-counts, .hr-received { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; }
.hr-receipt-counts b { color: var(--heading); font-size: 1.2rem; }
.hr-receipt p { margin: 0; line-height: 1.5; }
.hr-received { margin-block: .75rem; padding-block: .75rem; border-block: 1px solid var(--border-bright); }
.hr-step > a { display: inline-block; margin-block-start: .75rem; }

/* S-11490/S-11500: retain canonical territory plaques and Dark Energy artwork. */
.dec-region h3 .ch-plot-badge { display: inline-flex; align-items: center; cursor: default; } /* IR-8473 (owner: "Dec regions in wallet look a bit broken"): the name is centred in the plaque like every other region plaque */
.dec-staked { display: inline-flex; align-items: center; gap: .35em; max-width: 100%; }
.dec-staked > span { min-width: 0; overflow-wrap: anywhere; }
.dec-energy-icon { inline-size: 1.4em; block-size: 1.4em; object-fit: contain; flex: 0 0 auto; }
.dec-current-stake { display: flex; align-items: center; flex-wrap: wrap; gap: .35em; }

.harvest-routing .hr-resource-icon { inline-size: 1.35em; block-size: 1.35em; object-fit: contain; flex: 0 0 auto; }

.dec-region .dec-figures > div:first-child { grid-column: 1 / -1; }
.dec-region .dec-staked { font-size: 1.3rem; }

/* S-11690: links inherit accessible theme colors, including browser visited state. */
:root { --ceres-link: var(--heading); --ceres-link-hover: var(--text); }
a:any-link:not(.ceres-btn):not(.ch-plot-badge) { color: var(--ceres-link); text-decoration-color: currentColor; text-underline-offset: .2em; text-decoration-thickness: .08em; }
a:any-link:not(.ceres-btn):not(.ch-plot-badge):hover { color: var(--ceres-link-hover); }
a:any-link:focus-visible { outline: 2px solid var(--accent); outline-offset: .2em; border-radius: .15em; }

.dec-receipt { display: grid; gap: 1.4rem; padding: 1.4rem; }
.dec-receipt > header { display: grid; justify-items: start; gap: .8rem; }
.dec-receipt h3, .dec-receipt p { margin: 0; }
.dec-receipt.is-confirmed { border-color: var(--ok); background: linear-gradient(135deg, color-mix(in srgb, var(--ok) 8%, transparent), transparent 65%), var(--bg); }
.dec-receipt.is-confirmed h3 { color: var(--ok); }
.dec-receipt.is-failed { border-color: var(--danger); }
.dec-receipt-amount { display: flex; align-items: center; gap: 1rem; min-inline-size: 0; }
.dec-receipt-amount > img { inline-size: 3rem; block-size: 3rem; object-fit: contain; flex: none; }
.dec-receipt-amount > div { display: grid; gap: .35rem; min-inline-size: 0; }
.dec-receipt-amount strong { font-size: 1.6rem; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.dec-receipt-amount small { font-size: .65em; color: var(--text-dim); }
.dec-receipt-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 1rem; padding-block-start: .5rem; border-top: 1px solid var(--border); }
.dec-receipt-actions > a { margin-inline-end: auto; padding-block: .6rem; }

/* Keep complete DEC amounts together; reflow cells instead of splitting digits. */
.dec-figures { display: flex; flex-wrap: wrap; }
.dec-figures > div { flex: 1 1 auto; min-inline-size: max-content; }
.dec-figures > div:first-child { flex-basis: 100%; }
.dec-figures > div:not(:first-child) .dec-staked { font-size: 1rem; }
.dec-staked > span { white-space: nowrap; }

/* DEC receipts reuse the shared harvest components within the drawer. */
.dec-harvest-receipt { display: grid; gap: 1rem; min-width: 0; margin-block: 1.25rem; }
.dec-harvest-receipt .harvest-receipt-financials { grid-template-columns: 1fr; }
.dec-harvest-receipt .harvest-receipt-resources { grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr)); }
.dec-harvest-receipt .harvest-receipt-success h2 { font-size: 1.25rem; }
.dec-harvest-receipt pre { max-width: 100%; overflow: auto; }
/* IR-9271 (owner: "the forced harvest all region after changing efficiency through dec staking has no proper styling in the
   wallet. adopt the usual harvest all receipt as much as possible"): regional-dec.js wraps the forced harvest's Harvest all
   receipt in .harvest-receipt-ledger, so the receipt's own ledger rules apply; here only its slot rows are laid out for the
   wallet's width (544 px panel, a phone drawer): the resource tiles wrap at their own width as the modal's narrow receipt does,
   empty slots are not drawn and the ledger rows lose the modal's toggle column. The drawer's id outranks the ledger rules'
   :is(#harvest-modal-overlay, ...) specificity. */
#regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-slots { display: flex; flex-wrap: wrap; align-items: center; align-content: flex-start; inline-size: 100%; gap: .3rem; }
#regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-badge { grid-template-columns: 1.1rem max-content max-content; column-gap: .3rem; flex: 0 1 auto; inline-size: max-content; max-inline-size: 100%; min-inline-size: 0; block-size: auto; min-block-size: 1.75rem; }
#regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-badge :is(.hm-batch-name, .hm-batch-number) { overflow: visible; white-space: nowrap; }
#regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-badge.is-empty { display: none; }
#regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-ledger-line { grid-template-columns: 5.75rem minmax(0, 1fr); }
#regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-ledger-line:has(> .hm-batch-ledger-label[aria-hidden="true"]) { grid-template-columns: minmax(0, 1fr); }
#regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-ledger-line > .hm-batch-ledger-label[aria-hidden="true"], #regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-toggle-spacer { display: none; }
#regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-total-head { flex-wrap: wrap; }
/* IR-9284: the opened cost view (Received / Cost / Net rows) on the phone drawer: the row label sits above its badges, not in a column that squeezes them. */
@media (max-width: 600px) {
  #regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-ledger-line:not(:has(> .hm-batch-ledger-label[aria-hidden="true"])) { grid-template-columns: minmax(0, 1fr); row-gap: .15rem; }
  #regional-dec-drawer .dec-harvest-receipt .harvest-receipt-ledger .hm-batch-ledger-line > .hm-batch-ledger-label { text-align: left; }
}

.hr-check-result { padding: 1rem; border-left: .25rem solid var(--ok); background: color-mix(in srgb, var(--ok) 10%, transparent); }
.hr-saved-rules { margin-block: 1rem; padding: 1rem; border: 1px solid var(--border); border-radius: .75rem; }
.hr-saved-rules ul { display: grid; gap: .75rem; padding: 0; list-style: none; }
.hr-saved-rules li { display: flex; flex-wrap: wrap; gap: .4rem 1rem; align-items: center; }
.hr-summary-icon { width: 1.3em; height: 1.3em; object-fit: contain; vertical-align: middle; }
.harvest-routing > section > .ceres-btn { margin-block: .5rem; margin-inline-end: .5rem; }

.hr-step.is-confirmed { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 9%, var(--bg)); }
.hr-step.is-confirmed .hr-step-status { color: var(--ok); }
.hr-step.is-pending { border-color: var(--warn); }
.hr-step.is-pending .hr-step-status { color: var(--warn); }
.hr-step.is-failed { border-color: var(--danger); }
.hr-step.is-failed .hr-step-status { color: var(--danger); }
.hr-step.is-ready { border-color: var(--accent); }
.hr-step-status { display: inline-flex; gap: .5rem; align-items: center; font-weight: 700; }
.hr-step > details { margin-top: .75rem; }
.hr-step > details pre { max-width: 100%; overflow: auto; }

/* S-11890/S-11900: intrinsic routing rows; numeric controls share dropdown sizing. */
.hr-fields .hr-field > input[type="number"] {
  inline-size: 14ch;
  min-block-size: var(--ceres-control-min-block);
  padding-block: 0;
  padding-inline: var(--ceres-control-inline-pad);
  font-size: var(--ceres-control-font-size);
  line-height: 1.25;
  border-radius: 8px;
}
.hr-fields .hr-field--percent > input[type="number"],
.hr-fields .hr-field--slippage > input[type="number"] { inline-size: 9ch; }
.hr-fields .hr-field .csel-wrap { inline-size: max-content; }
.hr-fields .hr-field > span { font-size: var(--ceres-control-font-size); }
.hr-fields > .hr-field--slippage { inline-size: min-content; }
.hr-field--slippage > span { white-space: nowrap; }
.hr-rule-bottom { margin-block-start: .75rem; font-size: .85rem; }
@media (max-width: 600px) {
  .hr-fields { gap: .75rem; }
  .hr-fields > .hr-field { flex: 1 1 9rem; }
  .hr-fields > .hr-field--percent, .hr-fields > .hr-field--slippage { flex-basis: 6rem; }
  .hr-fields .hr-field > input[type="number"] { inline-size: 100%; }
  .hr-fields .hr-field .csel-wrap { inline-size: 100%; }
  .hr-fields .hr-field > input[type="number"],
  .hr-fields .ceres-btn.csel-trigger,
  .hr-fields > .ceres-btn[data-control-density] { min-block-size: max(44px, 2.75rem); }
}

/* S-11920/S-15110: signing applies to the reviewed bundle and stays visible while its body scrolls. */
#harvest-modal-overlay .hr-bundle-action { position: sticky; inset-block-end: 0; z-index: 4; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .75rem; box-sizing: border-box; block-size: auto; min-block-size: 4.6rem; padding: .75rem 1.4rem; margin: 0; border: 0; border-block-start: 1px solid var(--accent); border-radius: 0; background: var(--bg3); flex-shrink: 0; }
.hr-bundle-action p { margin: 0; }
.hr-signing-status { display: flex; flex: 1 1 24rem; align-items: center; gap: .75rem; min-inline-size: 0; }
.hr-signing-status > strong { flex: none; color: var(--heading); }
.hr-signing-only { gap: 0; }
.hr-signing-focus { display: grid; gap: 1rem; min-inline-size: 0; }
.hr-signing-focus > header, .hr-signing-operation > header { display: flex; align-items: start; justify-content: space-between; gap: 1rem; }
.hr-signing-focus h2, .hr-signing-operation h3 { margin: 0; color: var(--heading); }
.hr-signing-focus p, .hr-signing-operation p { margin: 0; line-height: 1.5; }
.hr-signing-live { flex: none; padding: .35rem .6rem; border: 1px solid var(--warn); border-radius: 999px; color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, var(--bg3)); font-weight: 700; }
.hr-signing-operation { display: grid; gap: .65rem; padding: 1rem; border: 2px solid var(--accent); border-radius: var(--radius); background: color-mix(in srgb, var(--accent) 8%, var(--bg2)); }
.hr-signing-operation > header > span { color: var(--accent); font-weight: 700; }
.hr-signing-operation ol { display: grid; gap: .65rem; margin: 0; padding-inline-start: 1.5rem; }
.hr-signing-operation li { padding: .65rem; border: 1px solid var(--border-bright); border-radius: var(--radius); background: var(--bg3); }
.hr-signing-operation pre { max-block-size: 14rem; margin: .45rem 0 0; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font-size: .78rem; }
.hr-signing-progress { display: grid; gap: .5rem; margin: 0; padding: 0; list-style: none; }
.hr-signing-progress li { display: flex; align-items: center; gap: .65rem; padding: .65rem .75rem; border-inline-start: .3rem solid var(--border-bright); background: var(--bg2); }
.hr-signing-progress li.is-current { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, var(--bg2)); }
.hr-signing-progress li.is-signed { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg2)); }
.hr-signing-progress li.is-confirmed { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, var(--bg2)); }
.hr-signing-dot { flex: none; inline-size: .7rem; block-size: .7rem; border-radius: 50%; background: var(--border-bright); }
.hr-signing-progress li.is-current .hr-signing-dot { background: var(--warn); }
.hr-signing-progress li.is-signed .hr-signing-dot { background: var(--accent); }
.hr-signing-progress li.is-confirmed .hr-signing-dot { background: var(--ok); }
.hr-signing-progress small { display: block; color: var(--body); }
.hr-signing-preparing { display: flex; align-items: center; gap: .6rem; padding: 1rem; border: 1px solid var(--accent); border-radius: var(--radius); background: var(--bg2); }
.hr-signing-footer { justify-content: stretch; }
.hr-signing-footer .hr-signing-status { inline-size: 100%; }
/* IR-6860: a handoff-rejection error is a third flex item here — force it onto its own row above the button. */
.hr-bundle-action .hr-error { flex-basis: 100%; }
@media (max-width: 600px) {
  #harvest-modal-overlay .hr-bundle-action { align-items: stretch; padding: .75rem max(.8rem, env(safe-area-inset-right)) calc(.75rem + env(safe-area-inset-bottom)) max(.8rem, env(safe-area-inset-left)); }
  .hr-signing-status { align-items: stretch; flex-direction: column; flex-basis: 100%; gap: .4rem; }
  .hr-bundle-action > button, .hr-bundle-action > .wallet-sign-pair { inline-size: 100%; }
  .hr-bundle-action > .wallet-sign-pair > button { flex: 1 1 auto; }
}

/* S-11990: routing removals use the shared destructive icon control. */
.harvest-routing .hr-remove { color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, var(--bg3)); }
.harvest-routing .hr-remove > svg { inline-size: 1.1rem; block-size: 1.1rem; flex: none; }
@media (max-width: 600px) { .harvest-routing .hr-remove { min-inline-size: max(44px, 2.75rem); } }

/* S-12010: one resource-strip hierarchy with canonical Ceres fonts and artwork. */
.harvest-receipt-financials, .dec-harvest-receipt .harvest-receipt-financials {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: .8rem;
  padding: .85rem 1rem; border: 1px solid var(--border); border-radius: var(--radius);
  background: linear-gradient(110deg, var(--bg), var(--bg2));
}
.harvest-receipt-financials > section + section {
  display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1rem;
  border-block-start: 1px solid var(--border); padding-block-start: .75rem;
}
.harvest-receipt-financials > section + section h4 { margin: 0; }
.harvest-receipt-resources, .dec-harvest-receipt .harvest-receipt-resources,
.hm-resource-summary .hm-totals { display: flex; flex-wrap: wrap; gap: .75rem 0; grid-auto-rows: auto; }
.harvest-receipt-resources > div, .hm-resource-summary .hm-total {
  flex: 1 1 11rem; min-inline-size: 0; box-sizing: border-box;
  padding: .25rem .85rem; border: 0; border-inline-start: 1px solid var(--border);
  border-radius: 0; background: transparent; block-size: auto;
}
.harvest-receipt-resources > div:first-child, .hm-resource-summary .hm-total:first-child { border-inline-start-color: transparent; }
.harvest-receipt-resources > div { grid-template-columns: 2.6rem minmax(0, 1fr); column-gap: .65rem; }
.harvest-receipt-resources img { grid-column: 1; grid-row: 1 / 3; inline-size: 2.6rem; block-size: 2.6rem; }
.harvest-receipt-resources dd { grid-column: 2; grid-row: 1; font: 800 1.35rem/1.2 var(--font-body); font-variant-numeric: tabular-nums; }
.harvest-receipt-resources dt { grid-column: 2; grid-row: 2; font: 600 .85rem/1.35 var(--font-body); }
.harvest-receipt-financials > section + section .harvest-receipt-resources { flex: 1 1 12rem; }
.harvest-receipt-financials > section + section img { inline-size: 2rem; block-size: 2rem; }
.harvest-receipt-financials > section + section dd { font-size: 1rem; }
.hm-resource-summary { padding: .85rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); background: linear-gradient(110deg, var(--bg), var(--bg2)); min-inline-size: 0; }
.hm-resource-summary .hm-total img { inline-size: 2.6rem; block-size: 2.6rem; flex: none; }
.hm-resource-summary .hm-total > span { min-inline-size: 0; }
.hm-resource-summary .hm-total b { font: 800 1.35rem/1.2 var(--font-body); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.hm-resource-summary .hm-total small { font: 600 .85rem/1.35 var(--font-body); }
.hm-resource-costs { display: flex; flex-wrap: wrap; align-items: center; gap: .7rem 1rem; margin-block-start: .8rem; padding-block-start: .75rem; border-block-start: 1px solid var(--border); }
.hm-resource-costs h4 { margin: 0; font-size: .85rem; color: var(--meta); }
.hm-resource-costs .hm-tok { display: grid; grid-template-columns: 2rem auto; gap: 0 .5rem; }
.hm-resource-costs .hm-tok img { grid-row: 1 / 3; inline-size: 2rem; block-size: 2rem; }
.hm-resource-costs .hm-tok b, .hm-resource-costs .hm-tok small { grid-column: 2; }
@media (max-width: 600px) {
  .harvest-receipt-resources > div, .hm-resource-summary .hm-total { flex-basis: 9rem; padding-inline: .4rem; }
  .harvest-receipt-financials, .hm-resource-summary { padding: .75rem; }
  .harvest-receipt-resources dd, .hm-resource-summary .hm-total b { font-size: 1.15rem; }
}

/* S-13570: content may widen a dialog, but sparse content still needs room.
   Scope these limits to desktop so phone sheets retain their full usable width. */
html:not(.ceres-lw-le-1279) :is(#harvest-modal-overlay, #changes-overlay) > .harvest-panel {
  inline-size: fit-content;
  min-inline-size: min(var(--modal-comfort-width), calc(var(--ceres-vw, 1vw) * 100 - 2rem));
  max-inline-size: min(96rem, calc(var(--ceres-vw, 1vw) * 100 - 2rem));
  block-size: auto;
}
html:not(.ceres-lw-le-1279) #harvest-modal-overlay > .harvest-panel { --modal-comfort-width: 60rem; }
html:not(.ceres-lw-le-1279) #harvest-modal-overlay > .harvest-panel:is(.is-batch, :has(.harvest-routing), :has(.hm-rgrid), :has(.harvest-receipt)) { --modal-comfort-width: 76rem; }
html:not(.ceres-lw-le-1279) #harvest-modal-overlay > .harvest-panel:has(.harvest-receipts.is-batch-ledger) { --modal-comfort-width: 84rem; inline-size:min(96rem, calc(var(--ceres-vw, 1vw) * 100 - 2rem)); }
html:not(.ceres-lw-le-1279) #harvest-modal-overlay > .harvest-panel.is-batch { --modal-comfort-width: 84rem; }
html:not(.ceres-lw-le-1279) #harvest-modal-overlay > .harvest-panel.is-batch:has(.hm-batch-grid) { inline-size:min(96rem, calc(var(--ceres-vw, 1vw) * 100 - 2rem)); }
html:not(.ceres-lw-le-1279) #harvest-modal-overlay > .harvest-panel:has(.harvest-receipt-summary):has(.hm-batch-grid) { --modal-comfort-width: 84rem; inline-size:min(96rem, calc(var(--ceres-vw, 1vw) * 100 - 2rem)); }
html:not(.ceres-lw-le-1279) #changes-overlay > .changes-panel { --modal-comfort-width: 80rem; }
#changes-overlay .ch-plot-layout { display: flex; flex-wrap: wrap; align-items: flex-start; gap: .6rem 1rem; min-inline-size: 0; }
#changes-overlay .ch-strip-scroll { flex: 0 0 auto; max-inline-size: 100%; overflow-x: auto; overflow-y: hidden; }
#changes-overlay .ch-tiles { flex-wrap: nowrap; gap: .55rem; }
#changes-overlay .ch-cells { gap: .55rem; }
/* S-16899 (owner): a loading plot holds the strip's final size (--ch-n tiles of 6.7rem, .55rem apart; 13.45rem tall, measured on a loaded strip) with the shared spinner centred in it, so the window does not jump when details arrive. */
#changes-overlay .ch-strip-loading { display: grid; place-items: center; box-sizing: border-box; padding-block-start: 1.3rem;
  inline-size: calc(var(--ch-n, 1) * 6.7rem + (var(--ch-n, 1) - 1) * .55rem); min-block-size: 13.45rem; }  /* measured: a loaded one-tile strip is 215.2px tall */
#changes-overlay .ch-plot-aside { flex: 1 1 12rem; min-inline-size: min(12rem, 100%); display: grid; align-content: start; gap: .5rem; }
#changes-overlay .ch-plot-summary { margin: 0 0 0 auto; align-self: center; font-size: .8rem; line-height: 1.4; text-align: end; } /* S-16911: top-right of the card */
/* S-16913: an option a dropdown may not take (e.g. a duplicate route) is shown greyed out and cannot be picked. */
.csel-option.is-disabled { opacity: .4; cursor: not-allowed; }
#changes-overlay .ch-plot-aside .ceres-notice { max-inline-size: 35rem; line-height: 1.4; }
#changes-overlay .ch-signing-status { min-block-size: 3.5rem; }
#changes-overlay .ch-signing-summary { margin: 0 0 .4rem; color: var(--heading); font-weight: 700; }
html:not(.ceres-lw-le-1279) #harvest-modal-overlay > .harvest-panel:has(.hm-totals > :nth-child(7)) { --modal-comfort-width: 92rem; }
#changes-overlay :is(.hm-headr, .hm-foot) { flex-shrink: 0; }
#changes-overlay .hm-body { scrollbar-gutter: stable; }
#harvest-modal-overlay .hm-ops { min-inline-size: 0; }

/* Height follows content; only the body scrolls when the viewport is full. */
#harvest-modal-overlay .harvest-panel { inline-size: fit-content; min-inline-size: 0; block-size: auto; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #harvest-modal-overlay { align-items: center; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #harvest-modal-overlay > .harvest-panel { block-size: auto; max-block-size: calc(var(--ceres-vh, 1dvh) * 100); }
#harvest-modal-overlay .hm-body { flex: 0 1 auto; scrollbar-gutter: stable; }
#harvest-modal-overlay .hm-headr, #harvest-modal-overlay .hm-foot { flex-shrink: 0; }
#harvest-modal-overlay .hm-rhead { block-size: auto; min-block-size: 4.6rem; overflow: visible; }
#harvest-modal-overlay .hm-rrec { max-block-size: none; overflow: visible; display: flex; flex-wrap: wrap; gap: .65rem 1rem; }
.hm-rbody .hm-resource-costs:first-child { border-block-start: 0; margin-block-start: 0; padding-block-start: 0; }
.hm-balance-details { margin-block-start: .8rem; }
.hm-balance-details > summary { cursor: pointer; color: var(--meta); font-weight: 700; }
.hm-balance-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); gap: .8rem; margin-block-start: .8rem; }
.hm-balance-item { display: grid; gap: .3rem; font-size: .85rem; min-inline-size: 0; overflow-wrap: anywhere; }
.hm-balance-item strong { color: var(--heading); }
@media (max-width: 600px) {
  #harvest-modal-overlay .hm-rhead { grid-template-columns: auto minmax(0, 1fr); gap: .65rem; }
  #harvest-modal-overlay .hm-rrec, #harvest-modal-overlay .hm-rhead > .hm-status { grid-column: 1 / -1; }
}

/* S-12050: keep empty regions available without full-height balance cards. */
.dec-region-summary { display:flex; align-items:center; justify-content:space-between; gap:1rem; cursor:pointer; list-style:none; min-block-size:2.75rem; }
.dec-region-summary::-webkit-details-marker { display:none; }
.dec-region-summary:focus-visible { outline:2px solid var(--accent); outline-offset:.3rem; border-radius:.35rem; }
.dec-region-collapsible[open] > .dec-region-summary { margin-block-end:1rem; }

.dec-region-empty-label { margin-inline-start:auto; color:var(--text-dim); font-size:.85rem; }

/* S-12140: keep each debit attached to its resource through responsive reflow. */
.hm-resource-pair { flex:1 1 11rem; min-inline-size:0; border-inline-start:1px solid var(--border); padding-inline:.8rem; }
.hm-resource-summary .hm-resource-pair > .hm-total { border:0; padding:.4rem 0; margin:0; }
.hm-resource-summary .hm-resource-pair > .hm-resource-debit { border-block-start:1px solid var(--border); margin-block-start:.65rem; padding-block-start:.8rem; }
.hm-resource-summary .hm-resource-pair > .hm-resource-debits {
  grid-column:1 / -1; min-inline-size:0;
}
.hm-resource-summary .hm-resource-pair > .hm-resource-debits > .hm-resource-debit {
  margin-block-start:.65rem; padding-block-start:.8rem; border-block-start:1px solid var(--border);
}
.hm-resource-summary .hm-resource-pair > .hm-resource-debits > .hm-resource-debit b {
  font-size:1rem; line-height:1.25;
}
.hm-resource-debit b { color:var(--danger); }
@media(max-width:600px) { .hm-resource-pair { flex-basis:9rem; padding-inline:.5rem; } }

/* S-12100: summary and heading containers must not alter plaque typography. */
.dec-region .ch-plot-badge { font-family:var(--font-body); font-size:1rem; font-weight:800; line-height:1.25; }
/* S-12160: center each icon-and-amount group within its resource column. */
.hm-resource-summary .hm-total { justify-content:center; }
.hm-resource-summary .hm-resource-pair > .hm-total { justify-content:center; }
.harvest-receipt-resources > div { justify-content:center; grid-template-columns:auto auto; }

/* S-12170/80: shared columns prevent independent centering of credit and debit. */
.hm-resource-summary .hm-totals {
  display:grid; grid-template-columns:repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  gap:.8rem 0; align-items:stretch;
}
.hm-resource-summary .hm-resource-pair {
  display:grid; grid-template-columns:2rem minmax(0, max-content); grid-auto-rows:min-content;
  align-content:start; justify-content:center; column-gap:.5rem;
  padding-inline:.65rem; border-inline-start:1px solid var(--border); min-inline-size:0;
}
.hm-resource-summary .hm-resource-pair > .hm-total {
  display:grid; grid-column:1 / -1; grid-template-columns:subgrid;
  align-items:center; justify-content:normal; gap:.5rem; inline-size:auto;
}
.hm-resource-summary .hm-resource-pair > .hm-total img { inline-size:2rem; block-size:2rem; }
.hm-resource-summary .hm-resource-pair > .hm-total b { font-size:1.15rem; line-height:1.25; min-block-size:0; }
.hm-resource-summary .hm-resource-pair > .hm-resource-debit b { font-size:1rem; color:var(--danger); }
.hm-resource-summary .hm-resource-pair > .hm-total small { font-size:.8rem; }
.hm-resource-summary .hm-plot-ledger { display:flex; flex-direction:column; gap:.35rem; }
.hm-resource-summary .hm-plot-ledger-row { min-inline-size:0; }
.hm-resource-summary .hm-plot-ledger-row > .hm-total { display:flex; align-items:center; justify-content:flex-start; gap:.5rem; min-inline-size:0; padding:0; border:0; border-radius:0; background:transparent; }
.hm-resource-summary .hm-plot-ledger-row .hm-resource-art { inline-size:2rem; block-size:2rem; object-fit:contain; flex:none; }
.hm-resource-summary .hm-plot-ledger-row .hm-total > span { display:flex; flex-direction:column; min-inline-size:0; }
.hm-resource-summary .hm-plot-ledger-row:not(.is-gain) > .hm-total { padding:.653rem .75rem .403rem 1.03rem; gap:.47rem; }
.hm-resource-summary .hm-plot-ledger-row:not(.is-gain) .hm-resource-art { inline-size:1.5rem; block-size:1.5rem; }
.hm-resource-summary .hm-plot-ledger-row:not(.is-gain) .hm-total > span { flex-direction:row; align-items:baseline; gap:.3rem; }
.hm-resource-summary .hm-plot-ledger-row b { color:var(--danger); font-size:1rem; }
.hm-resource-summary .hm-plot-ledger-row small { color:var(--body); font-size:.8rem; }
.hm-resource-summary .hm-plot-ledger-row.is-gain { padding:.53rem .725rem .41rem; border:1px solid color-mix(in srgb, var(--ok) 45%, var(--border)); border-radius:var(--radius); background:color-mix(in srgb, var(--ok) 12%, var(--bg2)); }
.hm-resource-summary .hm-plot-ledger-row.is-gain > .hm-total { gap:.525rem; }
.hm-resource-summary .hm-plot-ledger-row:not(.is-gain) { padding-inline-start:.6rem; }
.hm-resource-summary .hm-plot-ledger-row.is-gain b { color:var(--ok); font-size:1.15rem; }
.hm-resource-summary .hm-plot-ledger-row.is-loss b { color:var(--danger); }
#harvest-modal-overlay .hm-rrec:has(.hm-resource-summary--compact) { display:block; }
.hm-resource-summary--compact { display:block; border:0; padding:0; background:none; min-inline-size:0; }
.hm-resource-summary--compact .hm-totals { grid-template-columns:repeat(auto-fit, minmax(min(100%, 8rem), 1fr)); gap:.5rem 0; }
.hm-resource-summary--compact .hm-resource-pair { grid-template-columns:1.35rem minmax(0, max-content); padding-inline:.4rem; column-gap:.35rem; }
.hm-resource-summary--compact .hm-resource-pair > .hm-total { padding-block:.15rem; gap:.35rem; }
.hm-resource-summary--compact .hm-resource-pair > .hm-total img { inline-size:1.35rem; block-size:1.35rem; }
.hm-resource-summary--compact .hm-resource-pair > .hm-total b { font-size:.9rem; }
.hm-resource-summary--compact .hm-resource-pair > .hm-resource-debit b { font-size:.8rem; }
.hm-resource-summary--compact .hm-resource-pair > .hm-total small { font-size:.7rem; }
.hm-resource-summary--compact .hm-resource-pair > .hm-resource-debit { margin-block-start:.35rem; padding-block-start:.4rem; }

@supports not (grid-template-columns:subgrid) {
  .hm-resource-summary .hm-resource-pair > .hm-total { grid-template-columns:2rem minmax(0, 1fr); }
  .hm-resource-summary--compact .hm-resource-pair > .hm-total { grid-template-columns:1.35rem minmax(0, 1fr); }
}

/* S-12190: opening focus must not scroll a translated panel inside the overlay. */
.dec-drawer { overflow:clip; }

/* S-12220: every region gets the same resource slots and available track width. */
.hm-regions { container-type:inline-size; }
#harvest-modal-overlay .hm-regions .hm-rhead { grid-template-columns:1.4rem 10rem minmax(0, 1fr) 11rem; }
.hm-resource-pair.is-absent .hm-total { opacity:.45; }
@container (max-width:88rem) {
  #harvest-modal-overlay .hm-regions .hm-rhead { grid-template-columns:1.4rem minmax(0, 1fr) 11rem; }
  #harvest-modal-overlay .hm-regions .hm-rname { grid-column:2; grid-row:1; }
  #harvest-modal-overlay .hm-regions .hm-rhead > .hm-status { grid-column:3; grid-row:1; }
  #harvest-modal-overlay .hm-regions .hm-rrec { grid-column:1 / -1; grid-row:2; }
}
@container (max-width:32rem) {
  #harvest-modal-overlay .hm-regions .hm-rhead { grid-template-columns:1.4rem minmax(0, 1fr); }
  #harvest-modal-overlay .hm-regions .hm-rhead > .hm-status { grid-column:2; grid-row:2; justify-self:start; }
  #harvest-modal-overlay .hm-regions .hm-rrec { grid-row:3; }
}

.hm-resource-summary .hm-resource-pair > .hm-total b { white-space:nowrap; overflow-wrap:normal; }

@media(max-width:480px) { .hm-resource-summary:not(.hm-resource-summary--compact) .hm-totals { grid-template-columns:minmax(0, 1fr); } }

.hm-resource-pair > .hm-total > span { grid-column:2; grid-row:1; }
.hm-resource-pair > .hm-total > img { grid-column:1; grid-row:1; }

/* S-12250: routing columns must not move with selected option text. */
.hr-editor { container-type:inline-size; }
.hr-rule .hr-fields { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); align-items:end; }
.hr-rule .hr-fields > .hr-field { inline-size:auto; }
.hr-rule .hr-fields .hr-field .csel-wrap,
.hr-rule .hr-fields .hr-field > input[type="number"] { inline-size:100%; }
.hr-rule .hr-fields > .ceres-btn { justify-self:end; }
@container (min-width:48rem) {
  .hr-rule .hr-fields { grid-template-columns:repeat(4,minmax(0,1fr)); }
}
@container (min-width:76rem) {
  .hr-rule .hr-fields { grid-template-columns:10rem 10rem 9rem 15rem 6rem 9rem 6rem 2.75rem; justify-content:start; }
  .hr-rule .hr-field--kind { grid-column:1; grid-row:1; }
  .hr-rule .hr-field--source_region { grid-column:2; grid-row:1; }
  .hr-rule .hr-field--symbol { grid-column:3; grid-row:1; }
  .hr-rule .hr-field--quantity_mode { grid-column:4; grid-row:1; }
  .hr-rule .hr-field--quantity { grid-column:5; grid-row:1; }
  .hr-rule .hr-field--destination_symbol,
  .hr-rule .hr-field--destination_region { grid-column:6; grid-row:1; }
  .hr-rule .hr-field--destination_symbol ~ .hr-field--destination_region { grid-row:2; }
  .hr-rule .hr-field--slippage { grid-column:7; grid-row:1; }
  .hr-rule .hr-fields > .ceres-btn { grid-column:8; grid-row:1; }
}
@container (max-width:22rem) { .hr-rule .hr-fields { grid-template-columns:minmax(0,1fr); } }

/* S-12260: keep the regional identity, resource pairs and coverage within their tracks. */
.hm-resource-summary .hm-resource-pair:not(:has(.hm-resource-debit)) { align-content:center; }
#harvest-modal-overlay .hm-regions .hm-rname { display:grid; justify-items:start; gap:.25rem; min-inline-size:0; }
#harvest-modal-overlay .hm-regions .hm-rname .ch-plot-badge { font-family:var(--font-body); font-size:1rem; font-weight:800; line-height:1.25; }
#harvest-modal-overlay .hm-regions .hm-rhead > .hm-status { min-inline-size:0; max-inline-size:100%; white-space:normal; text-align:center; justify-self:stretch; overflow-wrap:anywhere; }

.hm-rhead.is-static { cursor:default; }
.hm-balance-details > h4 { margin:0 0 .75rem; }

.hr-rule .hr-fields .csel-trigger { inline-size:100%; max-inline-size:none; }

/* S-16697: known resource slots reserve the same width before and after the preview. */
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-total { box-sizing:border-box; min-inline-size:0; border:2px solid var(--accent); border-radius:var(--radius); padding:.7rem .9rem; background:color-mix(in srgb, var(--accent) 14%, var(--bg2)); box-shadow:0 2px 10px color-mix(in srgb, var(--accent) 25%, transparent); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-total-head { display:flex; align-items:center; justify-content:space-between; gap:.6rem; min-block-size:1.9rem; margin-block-end:.45rem; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-total-head h3 { flex:none; margin:0; font-size:.86rem; font-weight:800; color:var(--heading); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-head-status { flex:1 1 auto; min-inline-size:0; block-size:1.2rem; overflow:hidden; color:var(--body); font-size:.76rem; line-height:1.2rem; text-overflow:ellipsis; white-space:nowrap; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-costs { flex:none; min-inline-size:0; inline-size:auto; padding:.2rem .55rem; font-size:.7rem; font-weight:700; line-height:1.2; white-space:nowrap; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-costs[disabled] { visibility:hidden; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-total-ledger, :is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-region-ledger { min-inline-size:0; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-regions { min-inline-size:0; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-grid { display:grid; gap:.6rem; inline-size:100%; min-inline-size:0; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-total-ledger, :is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-region-list { overflow-y:auto; scrollbar-gutter:stable; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-region-list { display:flex; flex-direction:column; gap:.3rem; max-block-size:62vh; padding-inline:calc(.9rem + 2px); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-region { position:relative; min-inline-size:0; border-block-end:1px dashed var(--border); padding-block-end:.2rem; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-region-line, :is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-ledger-line, :is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-scrollbar-row { display:grid; grid-template-columns:5.75rem minmax(0,1fr) 2.6rem; align-items:start; gap:.5rem; column-gap:.25rem; min-inline-size:0; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-leading { display:flex; align-items:center; gap:.25rem; min-inline-size:0; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-leading .hm-rcheck { flex:none; margin:0; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-leading .hm-batch-plaque { flex:1 1 auto; inline-size:auto; min-inline-size:0; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-plaque { box-sizing:border-box; flex:0 0 5.25rem; inline-size:5.25rem; text-align:center; font-family:var(--font-body); font-size:.76rem; font-weight:800; line-height:1.1; cursor:default; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-plaque small { display:block; font-size:.65rem; font-weight:700; text-transform:uppercase; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-no-production { display:flex; align-items:center; gap:.35rem; min-inline-size:0; margin-block-start:.45rem; color:var(--body); font-size:.76rem; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-no-production > span:first-child { flex:none; font-weight:700; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-no-production .hm-batch-plaque { flex:0 0 auto; inline-size:auto; min-inline-size:2.6rem; padding-inline:.35rem; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-ledger-label { box-sizing:border-box; min-inline-size:0; text-align:right; text-transform:uppercase; font-size:.7rem; font-weight:700; color:var(--body); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-ledger-line.is-net .hm-batch-ledger-label { color:var(--ok); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-ledger-label.is-net { color:var(--ok); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-viewport { position:relative; min-inline-size:0; overflow:visible; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-viewport.is-overflowing::after { display:none; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-slots { box-sizing:border-box; display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); inline-size:100%; min-inline-size:0; block-size:auto; gap:.3rem; overflow:visible; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge { box-sizing:border-box; display:grid; grid-template-columns:1.1rem minmax(0,1fr) max-content; align-items:center; column-gap:.15rem; flex:none; inline-size:auto; block-size:1.75rem; padding:.2rem 8px; padding-inline:10px; border:1px solid var(--border); border-radius:.4rem; background:color-mix(in srgb, var(--body) 10%, var(--bg2)); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-empty { visibility:hidden; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-skeleton { border-color:transparent; background:linear-gradient(100deg, color-mix(in srgb, var(--body) 5%, var(--bg2)) 25%, color-mix(in srgb, var(--hi) 13%, var(--bg2)) 50%, color-mix(in srgb, var(--body) 5%, var(--bg2)) 75%); background-size:200% 100%; animation:hm-batch-shimmer 1.8s ease-in-out infinite; }
@keyframes hm-batch-shimmer { to { background-position:-200% 0; } }
@media (prefers-reduced-motion: reduce) { :is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-skeleton { animation:none; } }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-net { border-color:color-mix(in srgb, var(--ok) 45%, var(--border)); background:color-mix(in srgb, var(--ok) 14%, var(--bg2)); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-net.is-loss { border-color:color-mix(in srgb, var(--danger) 45%, var(--border)); background:color-mix(in srgb, var(--danger) 14%, var(--bg2)); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-cost { border-color:color-mix(in srgb, var(--danger) 45%, var(--border)); background:color-mix(in srgb, var(--danger) 14%, var(--bg2)); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-icon, :is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-icon-fallback { inline-size:1.1rem; block-size:1.1rem; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-icon img { display:block; inline-size:1.05rem; block-size:1.05rem; object-fit:contain; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-icon-fallback { display:block; border-radius:50%; background:var(--border); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-name, :is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-number { min-inline-size:0; font-size:.9rem; white-space:nowrap; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-name { overflow:hidden; color:var(--body); text-overflow:ellipsis; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-number { justify-self:end; text-align:right; font-variant-numeric:tabular-nums; font-weight:700; overflow:visible; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-net .hm-batch-number { color:var(--ok); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-net.is-loss .hm-batch-number { color:var(--danger); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge.is-cost .hm-batch-number { color:var(--danger); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-region-actions { display:flex; align-items:center; justify-content:flex-start; gap:.2rem; min-inline-size:0; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-state-label { max-inline-size:9ch; min-inline-size:0; overflow:hidden; font-size:.68rem; font-weight:700; text-overflow:ellipsis; white-space:nowrap; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-idle-status { display:flex; align-items:center; min-block-size:1.75rem; max-inline-size:none; overflow:visible; text-overflow:clip; }
/* IR-8469 (owner: "make sure the cooldown for a region harvest is vertically aligned"): the idle status reads at the control
   size and sits on the row's centre line, like the plaque beside it. */
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-idle-status { align-self:center; font-size:.8rem; line-height:1; }
.hm-rcheck-hit { display: contents; }
html:not(.ceres-lw-le-1279) :is(.card-badges-toggle, .scroll-top-btn) { display: none; } /* S-16918: the badges-on-cards switch exists only where the cards do */ /* S-16916: the phone gives it a 44 px tap area (mobile-plot.css) */
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-state-label.is-bad { color:var(--danger); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-state-label.is-warn { color:var(--warn); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-state-dot { flex:0 0 .55rem; inline-size:.55rem; block-size:.55rem; border-radius:50%; background:var(--body); box-shadow:0 0 0 2px color-mix(in srgb, var(--body) 12%, transparent); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-state-dot.is-ok { background:var(--ok); box-shadow:0 0 0 2px color-mix(in srgb, var(--ok) 18%, transparent); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-state-dot.is-bad { background:var(--danger); box-shadow:0 0 0 2px color-mix(in srgb, var(--danger) 18%, transparent); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-state-dot.is-warn { background:var(--warn); box-shadow:0 0 0 2px color-mix(in srgb, var(--warn) 18%, transparent); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-row-toggle { box-sizing:border-box; flex:0 0 1.9rem; inline-size:1.9rem; block-size:1.9rem; border:1px solid var(--border); border-radius:50%; background:transparent; color:var(--body); font:inherit; font-size:.78rem; cursor:pointer; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-toggle-spacer { inline-size:4.75rem; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-row-toggle[disabled] { visibility:hidden; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-row-toggle:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-row-toggle[aria-expanded="true"] { border-color:var(--accent); color:var(--accent); }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-badge .spinner { inline-size:1.05rem; block-size:1.05rem; vertical-align:middle; }
:is(#harvest-modal-overlay, .harvest-receipt-ledger) .hm-batch-scrollbar-row { display:none; }
.harvest-receipt-ledger { box-sizing:border-box; inline-size:100%; max-inline-size:100%; min-inline-size:0; }
#harvest-modal-overlay:has(.harvest-receipt-summary) .harvest-x svg { display:block; inline-size:1.1rem; block-size:1.1rem; fill:none; stroke:currentColor; stroke-width:2.2; stroke-linecap:round; }
.harvest-receipts.is-batch-ledger { grid-template-columns:minmax(0,1fr); }
#harvest-modal-overlay .harvest-receipts.is-batch-ledger > .harvest-receipt { box-sizing:border-box; min-inline-size:0; max-inline-size:100%; }
#harvest-modal-overlay .harvest-receipt-ledger .hm-batch-name { overflow:visible; overflow-wrap:anywhere; text-overflow:clip; white-space:normal; line-height:1.05; }
#harvest-modal-overlay .harvest-receipt-ledger .hm-batch-badge { min-block-size:1.75rem; block-size:auto; }
#harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-slots { display:flex; flex-wrap:wrap; align-items:flex-start; gap:.3rem; }
.harvest-receipt-ledger .hm-batch-total .hm-batch-badge.is-empty { display:none; }
#harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-badge { box-sizing:border-box; inline-size:max-content; max-inline-size:100%; min-inline-size:0; }
#harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-name { line-height:1.2; white-space:nowrap; }
#harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total-ledger > .hm-batch-ledger-line:has(> .hm-batch-ledger-label[aria-hidden="true"]) { grid-template-columns:minmax(0,1fr) 2.6rem; }
#harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total-ledger > .hm-batch-ledger-line:has(> .hm-batch-ledger-label[aria-hidden="true"]) > .hm-batch-ledger-label { display:none; }

@media (max-width:26rem) {
  #harvest-modal-overlay .harvest-receipt-summary, #harvest-modal-overlay .harvest-receipt-summary *, #harvest-modal-overlay .harvest-receipts.is-batch-ledger, #harvest-modal-overlay .harvest-receipts.is-batch-ledger * { box-sizing:border-box; min-inline-size:0; }
  #harvest-modal-overlay .harvest-receipt-summary, #harvest-modal-overlay .harvest-receipts.is-batch-ledger, #harvest-modal-overlay .harvest-receipts.is-batch-ledger > .harvest-receipt { max-inline-size:100%; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-transaction span { overflow-wrap:anywhere; }
  #harvest-modal-overlay .harvest-receipt-ledger :is(.hm-batch-total, .hm-batch-region-ledger, .hm-batch-regions, .hm-batch-region-list, .hm-batch-region, .hm-batch-region-line, .hm-batch-viewport, .hm-batch-slots) { box-sizing:border-box; max-inline-size:100%; min-inline-size:0; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total-head { flex-wrap:wrap; align-items:flex-start; justify-content:flex-start; gap:.35rem .5rem; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total-head h3 { flex:0 0 100%; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-head-status { flex:1 1 0; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-costs { max-inline-size:100%; white-space:normal; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-slots { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); inline-size:100%; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-badge { grid-template-columns:1.1rem minmax(0,1fr); grid-template-rows:auto auto; align-items:start; inline-size:100%; max-inline-size:100%; min-inline-size:0; block-size:auto; padding:.25rem .35rem; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-icon { grid-row:1 / span 2; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-name { min-inline-size:0; overflow:visible; overflow-wrap:normal; word-break:normal; text-overflow:clip; white-space:normal; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-number { grid-column:2; justify-self:start; min-inline-size:0; max-inline-size:100%; overflow-wrap:normal; text-align:left; white-space:nowrap; }
  #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-badge:last-child { grid-column:1 / -1; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-region-line { grid-template-columns:minmax(0,1fr) 1.9rem; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-region-line > .hm-batch-leading { grid-column:1 / -1; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-region-line > .hm-batch-viewport { grid-column:1 / -1; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-slots { display:flex; flex-wrap:wrap; align-items:flex-start; align-content:flex-start; inline-size:100%; gap:.3rem; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-badge { box-sizing:border-box; grid-template-columns:1.1rem minmax(0,1fr); grid-template-rows:auto auto; flex:0 1 auto; inline-size:max-content; max-inline-size:100%; min-inline-size:0; block-size:auto; padding:.25rem .35rem; break-inside:avoid; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-icon { grid-row:1 / span 2; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-badge.is-empty { display:none; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-name { min-inline-size:0; overflow:visible; overflow-wrap:anywhere; word-break:normal; text-overflow:clip; white-space:normal; }
  #harvest-modal-overlay .harvest-receipt-ledger .hm-batch-number { grid-column:2; justify-self:start; min-inline-size:0; max-inline-size:100%; overflow-wrap:normal; text-align:left; white-space:nowrap; }
  #harvest-modal-overlay .hm-batch-total-head { flex-wrap:wrap; align-items:flex-start; justify-content:flex-start; gap:.35rem .5rem; }
  #harvest-modal-overlay .hm-batch-total-head h3 { flex:0 0 100%; }
  #harvest-modal-overlay .hm-batch-total-head .hm-batch-costs { max-inline-size:100%; white-space:normal; }
  #harvest-modal-overlay .harvest-receipt-summary .hm-batch-total .hm-batch-slots { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); inline-size:100%; }
  #harvest-modal-overlay .harvest-receipt-summary .hm-batch-total .hm-batch-badge { inline-size:100%; }
  #harvest-modal-overlay .harvest-receipt-summary .hm-batch-total .hm-batch-badge[data-resource="research"] { grid-column:1 / -1; }
}

/* S-12300: reserve balances need space for ten digits plus decimal precision. */
.hr-reserve .hr-field:has(input[data-hr-reserve="amount"]) { flex:0 1 22ch; min-inline-size:min(100%,22ch); }
.hr-reserve .hr-fields .hr-field > input[data-hr-reserve="amount"] { inline-size:100%; }

#harvest-modal-overlay .harvest-panel.is-r3 { inline-size: min(80rem, calc(var(--ceres-vw, 1vw) * 100 - 3rem)); max-height: calc(var(--ceres-vh, 1dvh) * 100 - 3rem); }
html:not(.ceres-lw-le-1279) #harvest-modal-overlay > .harvest-panel.is-r3 { inline-size:min(80rem, calc(var(--ceres-vw, 1vw) * 100 - 3rem)); min-inline-size:0; }
#harvest-modal-overlay .harvest-panel.is-r3 .hm-body { padding-block: 1rem; }
.r3-setup, .r3-window { display:grid; gap:.75rem; min-inline-size:0; font-size:12px; }
.r3-setup h3, .r3-window h3 { margin:0; color:var(--heading); font-size:18px; }
.r3-summary { display:flex; align-items:center; gap:12px; min-height:52px; padding:8px 12px; border:1px solid var(--border); border-radius:9px; background:linear-gradient(110deg,var(--bg2),var(--bg3)); color:var(--body); }
.r3-summary strong { color:var(--heading); font-size:15px; }
.r3-wallet { margin-inline-start:auto; padding:7px 10px; border:1px solid var(--border-bright); border-radius:8px; white-space:nowrap; }
.r3-flow-layout { display:grid; grid-template-columns:322px minmax(0,1fr); gap:16px; align-items:start; min-inline-size:0; }
.r3-route-index { display:grid; align-content:start; gap:6px; min-inline-size:0; }
.r3-bundle-head { margin:6px 2px 0; font-size:.8rem; font-weight:600; opacity:.8; }
.r3-route-chip { position:relative; display:flex; align-items:center; gap:7px; min-width:0; min-height:40px; padding:5px 8px; border:1px solid var(--border); border-radius:7px; background:linear-gradient(100deg,var(--bg2),var(--bg3)); color:var(--body); font:inherit; font-size:12px; text-align:start; }
button.r3-route-chip { width:100%; cursor:pointer; }
.r3-route-chip.is-selected { border-color:var(--warn); box-shadow:inset 2px 0 var(--warn); }
.r3-route-chip.is-selected::after { content:''; position:absolute; left:100%; top:50%; width:17px; border-top:2px solid var(--warn); filter:drop-shadow(0 0 4px color-mix(in srgb,var(--warn) 75%,transparent)); }
.r3-route-chip:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.r3-route-chip .ch-plot-badge { flex:none; min-width:58px; min-height:22px; padding:0 5px; font-size:10.5px; }
.r3-route-name { flex:none; white-space:nowrap; }
.r3-state { margin-inline-start:auto; white-space:nowrap; color:var(--meta); font-size:12px; }
.r3-state::before { content:''; display:inline-block; width:6px; height:6px; margin-inline-end:5px; border-radius:50%; background:currentColor; vertical-align:middle; }
.r3-state--ok { color:var(--ok); } .r3-state--bad { color:var(--danger); } .r3-state--warn { color:var(--warn); } .r3-state--info { color:var(--info); }
.r3-art { inline-size:20px; block-size:20px; flex:none; object-fit:contain; }
.r3-art--large { inline-size:40px; block-size:40px; }
.r3-pagebar { display:flex; align-items:center; justify-content:flex-end; gap:6px; min-height:30px; color:var(--body); }
.r3-pagebar .ceres-btn { min-height:28px; padding:0 7px; }
.r3-flow-stage { display:grid; gap:8px; min-width:0; padding:12px; border:1px solid color-mix(in srgb,var(--primary) 35%,var(--border)); border-radius:10px; background:linear-gradient(145deg,var(--bg2),var(--bg3)); box-shadow:0 10px 30px color-mix(in srgb,var(--bg) 35%,transparent); }
.r3-flow-stage:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.r3-stage-head { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:30px; color:var(--body); }
.r3-operation-flows { display:grid; gap:8px; }
.r3-pinned { display:flex; align-items:center; flex-wrap:wrap; gap:8px; color:var(--body); font-size:12px; }
.r3-pinned:empty { display:none; }
.r3-pinned > span { display:inline-flex; align-items:center; gap:4px; }
.r3-flowline { display:flex; align-items:center; justify-content:space-between; gap:10px; min-width:0; min-height:152px; padding:12px 16px; border:1px solid var(--border); border-radius:8px; background:linear-gradient(120deg,color-mix(in srgb,var(--bg3) 88%,var(--primary)),var(--bg2)); }
.r3-operation-flows:has(.r3-flowline:nth-child(2)) .r3-flowline { min-height:104px; padding:8px 10px; }
.r3-flow-sources { display:flex; flex-wrap:wrap; gap:8px; min-width:0; }
.r3-flow-source { display:grid; grid-template-columns:auto auto; align-items:center; gap:5px 8px; padding:8px; border:1px solid color-mix(in srgb,var(--primary) 34%,var(--border)); border-radius:8px; background:var(--bg2); }
.r3-flow-source .ch-plot-badge { grid-column:1/-1; min-width:70px; min-height:25px; }
.r3-flow-source b { color:var(--heading); font-size:19px; }
.r3-arrow { color:var(--ok); font-size:23px; white-space:nowrap; }
.r3-pool, .r3-destination { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; min-width:145px; min-height:86px; padding:8px; border:1px solid var(--primary); border-radius:10px; background:var(--bg3); text-align:center; }
.r3-destination { border-color:var(--ok); }
.r3-destination strong { color:var(--heading); font-size:19px; }
.r3-destination b { font-size:15px; }
.r3-pool small, .r3-destination small { color:var(--body); font-size:12px; }
.r3-members { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:5px; }
.r3-member { display:flex; align-items:center; justify-content:center; flex-wrap:wrap; gap:6px; min-width:0; min-height:62px; padding:4px 6px; border:1px solid color-mix(in srgb,var(--primary) 22%,var(--border)); border-radius:6px; background:var(--bg3); color:var(--body); font-size:12px; }
.r3-member .ch-plot-badge { min-width:58px; min-height:22px; }
.r3-member .r3-art { inline-size:30px; block-size:30px; }
.r3-member small { color:var(--body); font-size:12px; }
.r3-members:has(.r3-member:nth-child(4)) { grid-template-columns:repeat(4,minmax(0,1fr)); }
.r3-members:has(.r3-member:nth-child(4)) .r3-member { min-height:40px; }
.r3-members:has(.r3-member:nth-child(4)) .r3-art { inline-size:20px; block-size:20px; }
.r3-stage-status { display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:30px; color:var(--body); font-size:12px; }
.r3-changed-review { display:grid; gap:6px; padding:10px; border:1px solid var(--border); border-radius:8px; color:var(--body); }
.r3-changed-review span { display:flex; align-items:center; gap:5px; flex-wrap:wrap; }
.r3-status-bar { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:10px; min-height:46px; padding:8px 12px; border:1px solid color-mix(in srgb,var(--warn) 55%,var(--border)); border-radius:8px; background:linear-gradient(105deg,color-mix(in srgb,var(--primary) 12%,var(--bg2)),var(--bg2) 45%,var(--bg3)); }
.r3-status-bar strong { color:var(--heading); font-size:13px; }
.r3-terminal { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px; min-height:46px; padding:7px 10px; border:1px solid var(--border); border-radius:8px; background:var(--bg2); color:var(--body); }
.r3-terminal strong { color:var(--heading); font-size:13px; }
.r3-terminal:has([data-r3="check"]) strong { color:var(--warn); }
.r3-actions { display:flex; justify-content:flex-end; align-items:center; flex-wrap:wrap; gap:10px; }
.r3-actions:empty { display:none; }
.r3-edit { display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:6px; align-items:end; }
.r3-edit label { display:grid; gap:4px; min-width:0; color:var(--body); font-size:12px; }
.r3-edit strong { display:flex; align-items:center; min-height:34px; padding:5px 8px; border:1px solid var(--border); border-radius:7px; background:var(--bg3); }
.r3-edit input, .r3-edit select, .r3-edit .csel-wrap { width:100%; min-width:0; }
.r3-edit input:not(.ceres-wide-input) { min-height:34px; padding:5px 8px; border:1px solid var(--border-bright); border-radius:7px; background:var(--bg2); color:var(--body); }
.r3-percent-field { display:flex; align-items:center; min-width:0; }
.r3-percent-field input { border-start-end-radius:0; border-end-end-radius:0; }
.r3-percent-field > span { display:grid; place-items:center; min-height:34px; padding-inline:9px; border:1px solid var(--border-bright); border-inline-start:0; border-start-end-radius:7px; border-end-end-radius:7px; background:var(--bg3); color:var(--body); }
.r3-allocation-note { min-height:1em; color:var(--body); }
.r3-summary-line { margin-inline-start:auto; color:var(--body); }
.r3-summary-line:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.r3-dec-short { display:grid; gap:6px; grid-column:1/-1; padding:12px 14px; border:1px solid var(--warn); border-inline-start-width:4px; border-radius:var(--radius); background:color-mix(in srgb, var(--warn) 12%, transparent); color:var(--heading); line-height:1.35; }
.wallet-status.is-action, .hm-note.is-action, .r3-wallet-wait strong { color:var(--warn); font-size:1.25rem; line-height:1.25; white-space:normal; overflow:visible; text-overflow:clip; }
.r3-wallet-wait { display:grid; gap:4px; margin:10px 0; padding:12px 14px; border:1px solid var(--warn); border-inline-start-width:4px; border-radius:var(--radius); background:color-mix(in srgb, var(--warn) 12%, transparent); color:var(--heading); line-height:1.35; }
.r3-error, .r3-reserve-note { margin:10px 0 0; padding:10px 14px; border:1px solid var(--warn); border-inline-start-width:4px; border-radius:var(--radius); background:color-mix(in srgb, var(--warn) 12%, transparent); color:var(--heading); font-size:1rem; line-height:1.35; }
.r3-reserve-note { border-color:var(--border-bright); }
.r3-sr-only { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; }
@media (min-width:1800px) { .r3-flow-layout { grid-template-columns:322px minmax(0,1fr); gap:20px; } }
@media (max-width:1050px) { .r3-flow-layout { grid-template-columns:322px minmax(0,1fr); } .r3-flowline { flex-wrap:wrap; } .r3-edit { grid-template-columns:repeat(3,minmax(0,1fr)); } }
@media (max-width:760px) { .r3-flow-layout { grid-template-columns:minmax(0,1fr); } .r3-route-index { grid-template-columns:repeat(2,minmax(0,1fr)); } .r3-pagebar { grid-column:1/-1; } .r3-route-chip.is-selected::after { display:none; } .r3-members, .r3-members:has(.r3-member:nth-child(4)) { grid-template-columns:minmax(0,1fr); } }
.r3-route-index .r3-route-chip { box-sizing:border-box; max-inline-size:100%; }
.r3-route-index .r3-route-name { flex:1 1 auto; min-inline-size:0; white-space:normal; }
.r3-route-index .r3-state { flex:0 0 auto; font-size:11px; }
.r3-window .r3-flowline, .r3-window .r3-operation-flows:has(.r3-flowline:nth-child(2)) .r3-flowline { box-sizing:border-box; block-size:80px; min-block-size:80px; padding:5px 8px; gap:6px; }
.r3-window .r3-flow-sources { flex-wrap:nowrap; gap:5px; }
.r3-window .r3-flow-source { grid-template-columns:40px minmax(0,1fr); grid-template-rows:20px 22px; gap:0 5px; min-inline-size:0; padding:4px 5px; }
.r3-window .r3-flow-source .r3-art { grid-column:1; grid-row:1/3; }
.r3-window .r3-flow-source .ch-plot-badge { grid-column:2; grid-row:1; min-inline-size:58px; min-block-size:20px; }
.r3-window .r3-flow-source b { grid-column:2; grid-row:2; font-size:16px; white-space:nowrap; }
.r3-window .r3-pool, .r3-window .r3-destination { box-sizing:border-box; display:grid; grid-template-columns:40px minmax(0,1fr); align-content:center; align-items:center; gap:2px 7px; min-inline-size:156px; min-block-size:64px; padding:5px 7px; text-align:left; }
.r3-window .r3-pool .r3-art, .r3-window .r3-destination .r3-art { grid-column:1; grid-row:1/4; }
.r3-window .r3-pool small, .r3-window .r3-pool span, .r3-window .r3-destination small, .r3-window .r3-destination b, .r3-window .r3-destination strong { grid-column:2; white-space:nowrap; }
.r3-window .r3-destination strong { font-size:16px; }
.r3-flowline--lp .r3-pool-icons { display:flex; align-items:center; }
.r3-window .r3-flowline--lp .r3-pool .r3-pool-icons { grid-column:1; grid-row:1/5; }
.r3-flowline--lp .r3-pool-icons .r3-art { inline-size:22px; block-size:22px; }
.r3-window .r3-flowline--lp, .r3-window .r3-operation-flows:has(.r3-flowline:nth-child(2)) .r3-flowline--lp { block-size:80px; min-block-size:80px; }
.r3-window .r3-flowline--lp .r3-flow-source small { grid-column:2; grid-row:1; color:var(--body); }
.r3-window .r3-flowline--lp .r3-destination small { white-space:normal; }
.r3-window .r3-members { display:none; }
.r3-window .r3-stage-status { box-sizing:border-box; min-block-size:44px; }
.r3-status-bar { box-sizing:border-box; display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr) auto minmax(0,1.6fr) 16px; flex-wrap:nowrap; block-size:46px; min-block-size:46px; }
.r3-status-bar > :not(.r3-status-spinner) { min-inline-size:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.r3-status-spinner { inline-size:16px; block-size:16px; visibility:hidden; }
.r3-status-spinner.is-busy { visibility:visible; }
.r3-edit { grid-template-columns:minmax(0,1.2fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) minmax(0,.85fr) auto; align-items:end; }
.r3-edit .csel-trigger, .r3-edit input, .r3-edit strong, .r3-edit > button { box-sizing:border-box; block-size:34px; min-block-size:34px; }
.r3-edit .csel-trigger { min-inline-size:0; }
.r3-edit .csel-value img { inline-size:20px; block-size:20px; max-inline-size:20px; max-block-size:20px; object-fit:contain; flex:none; }
.r3-edit .csel-value .ch-plot-badge { min-inline-size:42px; min-block-size:20px; margin-inline-end:5px; font-size:10px; }
.r3-edit .csel-value { min-inline-size:0; }
.r3-edit .csel-value-text { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.r3-edit--lp { grid-template-columns:minmax(150px,1.2fr) minmax(92px,.75fr) minmax(130px,1fr) minmax(205px,1.35fr) minmax(128px,1fr) auto; }
.r3-edit--lp .csel-wrap, .r3-edit--lp .csel-trigger { box-sizing:border-box; inline-size:100%; min-inline-size:0; max-inline-size:100%; }
.r3-edit--lp > label:nth-of-type(6) { grid-row:2; grid-column:3; }
.r3-edit--lp > label:nth-of-type(7) { grid-row:2; grid-column:4; }
@media (min-width:1800px) {
  .r3-edit--lp { grid-template-columns:minmax(165px,1.2fr) minmax(100px,.8fr) minmax(135px,1fr) minmax(215px,1.45fr) minmax(145px,1fr) minmax(145px,1fr) minmax(145px,1fr) auto; }
  .r3-edit--lp > button { grid-column:8; grid-row:1; }
  .r3-edit--lp > label:nth-of-type(6), .r3-edit--lp > label:nth-of-type(7) { grid-row:1; }
  .r3-edit--lp > label:nth-of-type(6) { grid-column:6; }
  .r3-edit--lp > label:nth-of-type(7) { grid-column:7; }
}
.r3-window .r3-flowline--lp .r3-destination strong small { display:inline; font-size:11px; font-weight:400; white-space:normal; }
.r3-setup .r3-stage-status small { color:var(--meta); font-size:12px; }
@media (max-width:1050px) { .r3-edit { grid-template-columns:repeat(3,minmax(0,1fr)); } .r3-window .r3-flowline, .r3-window .r3-operation-flows:has(.r3-flowline:nth-child(2)) .r3-flowline { block-size:auto; } .r3-window .r3-flow-sources { flex-wrap:wrap; } }

.r3-setup, .r3-window { font-size:13px; }
.r3-flow-layout { grid-template-columns:244px minmax(0,1fr); gap:16px; }
/* IR-8460 (owner): no scrollbar in the route list -- it grows with its routes (the dialog scrolls when needed), so the
   selected route's pointer to the review panel is no longer clipped and the hover never shifts. */
.r3-route-chip { min-block-size:42px; }
.r3-route-chip.is-selected { border-color:var(--primary); box-shadow:inset 2px 0 var(--primary); }
.r3-route-chip.is-selected::after { border-color:var(--primary); }
.r3-route-chip.is-excluded { opacity:.6; }
.r3-route-index .r3-route-name { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* S-16914 (owner: "resource icon must have a space to resource name and be fully horizontally aligned with the text"). */
.r3-route-index .r3-route-name { display:inline-flex; align-items:center; gap:.3rem; min-inline-size:0; }
.r3-route-index .r3-route-name .r3-art { inline-size:18px; block-size:18px; flex:none; vertical-align:middle; }
.r3-flow-stage { align-content:start; }
.r3-flowline, .r3-window .r3-flowline, .r3-window .r3-operation-flows:has(.r3-flowline:nth-child(2)) .r3-flowline { display:grid; grid-template-columns:minmax(0,1fr) 32px minmax(145px,.8fr) 32px minmax(145px,.8fr); align-items:center; gap:10px; block-size:auto; min-block-size:82px; padding:8px 12px; }
.r3-flow-source, .r3-window .r3-flow-source { display:flex; align-items:center; flex-wrap:wrap; gap:6px; min-inline-size:0; padding:4px; border:0; background:transparent; }
.r3-flow-source .ch-plot-badge, .r3-window .r3-flow-source .ch-plot-badge { min-inline-size:58px; min-block-size:22px; }
.r3-flow-source b, .r3-window .r3-flow-source b { color:var(--heading); font-size:15px; }
.r3-flow-source > span:last-child { font-variant-numeric:tabular-nums; white-space:nowrap; }
.r3-flow-source .r3-art--large { inline-size:32px; block-size:32px; }
.r3-arrow { color:var(--primary); text-align:center; }
.r3-pool, .r3-destination, .r3-window .r3-pool, .r3-window .r3-destination { display:flex; flex-direction:column; align-items:flex-start; justify-content:center; gap:3px; min-inline-size:0; min-block-size:64px; padding:5px 8px; text-align:left; }
.r3-pool strong { color:var(--heading); font-size:13px; }
.r3-pool > span:first-child, .r3-destination > span { color:var(--body); font-size:12px; }
.r3-destination { flex-direction:row; flex-wrap:wrap; align-items:center; }
/* IR-8522 (owner): the sell-to-DEC box is two rows -- "Minimum" over a large DEC amount -- the height of the Sell harvested box. */
:is(.r3-window, .r3-setup) .r3-destination.r3-destination--two { display:grid; grid-template-columns:auto minmax(0,1fr); grid-template-rows:auto auto; align-content:center; align-items:center; column-gap:8px; row-gap:1px; }
/* equal height whatever the row height: the Pool box and the outcome box both stretch to the row (IR-8525) */
.r3-window .r3-flowline > .r3-pool, .r3-window .r3-flowline > .r3-destination--two { align-self:stretch; }
:is(.r3-window, .r3-setup) .r3-destination--two .r3-art { grid-column:1; grid-row:1/3; inline-size:34px; block-size:34px; }
:is(.r3-window, .r3-setup) .r3-destination--two > span { grid-column:2; grid-row:1; }
:is(.r3-window, .r3-setup) .r3-destination--two > strong { grid-column:2; grid-row:2; font-size:1.25rem; line-height:1.1; font-variant-numeric:tabular-nums; white-space:nowrap; }
.r3-dec-needed { display:inline-flex; align-items:center; gap:4px; color:var(--gold); font-variant-numeric:tabular-nums; }
.r3-dec-needed .r3-art { inline-size:16px; block-size:16px; }
.r3-window .r3-members { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); }
.r3-member { justify-content:flex-start; min-block-size:40px; font-size:12px; }
.r3-member.is-excluded { opacity:.55; }
.r3-flow-select { inline-size:18px; block-size:18px; accent-color:var(--primary); flex:none; }
.r3-edit, .r3-edit--lp { grid-template-columns:minmax(130px,1.1fr) minmax(110px,1fr) minmax(95px,.7fr) minmax(190px,1.3fr) minmax(100px,.7fr) auto; align-items:end; }
.r3-edit--lp > button { grid-column:auto; grid-row:auto; }
.r3-edit label { align-content:end; }
.r3-edit label > span:first-child { min-block-size:20px; white-space:nowrap; }
.r3-edit .r3-share-label { position:relative; }
.r3-edit .r3-allocation-note { grid-column:1/-1; min-block-size:18px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.r3-edit .r3-moved-note { grid-column:1/-1; color:var(--warn); } /* IR-8466 */
.r3-percent-field input { inline-size:8ch; }
.r3-protected-setting { grid-column:2; display:inline-flex; align-items:center; justify-self:start; gap:10px; color:var(--body); }
@media (min-width:1800px) { .r3-flow-layout { grid-template-columns:284px minmax(0,1fr); gap:20px; } }
@media (max-width:1050px) { .r3-flow-layout { grid-template-columns:200px minmax(0,1fr); } .r3-edit, .r3-edit--lp { grid-template-columns:repeat(3,minmax(0,1fr)); } }
@media (max-width:760px) { .r3-flow-layout { grid-template-columns:minmax(0,1fr); } .r3-route-index { max-block-size:175px; } .r3-protected-setting { grid-column:1; } .r3-flowline, .r3-window .r3-flowline, .r3-window .r3-operation-flows:has(.r3-flowline:nth-child(2)) .r3-flowline { grid-template-columns:1fr auto 1fr; } .r3-flowline .r3-pool { grid-column:1/-1; grid-row:2; } .r3-flowline .r3-arrow:last-of-type { display:none; } .r3-window .r3-members { grid-template-columns:1fr; } }

.r3-flow-layout { grid-template-columns:300px minmax(0,1fr); }
.r3-route-index .r3-route-name { white-space:normal; overflow:visible; text-overflow:clip; line-height:1.25; overflow-wrap:anywhere; }
.r3-route-index .r3-state { display:inline; margin-inline-start:0; white-space:normal; font-size:inherit; }
.r3-route-index .r3-state::before { content:none; }
.r3-edit .csel-trigger, .r3-edit .ceres-wide-input, .r3-edit > button, .r3-protected-setting .ceres-wide-input { block-size:var(--ceres-control-min-block, 2.75rem); min-block-size:var(--ceres-control-min-block, 2.75rem); font-size:var(--ceres-control-font-size, 1rem); }
.r3-percent-field > span { box-sizing:border-box; min-block-size:var(--ceres-control-min-block, 2.75rem); font-size:var(--ceres-control-font-size, 1rem); }
.r3-percent-field .ceres-wide-input { border-start-end-radius:0; border-end-end-radius:0; }
.r3-edit .r3-percent-field { --ceres-control-min-block:2.75rem; align-items:stretch; }
.r3-edit .r3-percent-field > input, .r3-edit .r3-percent-field > span { box-sizing:border-box; block-size:var(--ceres-control-min-block, 2.75rem); min-block-size:var(--ceres-control-min-block, 2.75rem); }
.r3-protected-setting .ceres-wide-input { inline-size:9ch; }
@media (max-width:1050px) { .r3-flow-layout { grid-template-columns:260px minmax(0,1fr); } }
@media (max-width:760px) { .r3-flow-layout { grid-template-columns:minmax(0,1fr); } }

.r3-summary { flex-wrap:wrap; }
.r3-summary .r3-protected-setting { display:inline-flex; align-items:center; gap:8px; white-space:nowrap; }
.r3-summary .r3-protected-setting .ceres-wide-input { inline-size:10ch; }
.r3-route-index { align-self:start; max-block-size:min(18rem, 45dvh); scrollbar-gutter:auto; }
.r3-setup .r3-route-index { block-size:max-content; }
/* IR-8467 (owner: "the setup must never scroll, just regions"): on desktop the route list takes the edit panel's height
   and scrolls on its own, without a visible bar; its padding reaches into the column gap so the selected pointer is not
   clipped and still meets the panel. */
@media (min-width:761px) {
  .r3-setup .r3-flow-layout { align-items:stretch; }
  .r3-setup .r3-route-index { align-self:stretch; position:relative; contain:size; block-size:auto; max-block-size:none; overflow-y:auto; overscroll-behavior:contain; scrollbar-width:none; margin:-4px -16px -4px -4px; padding:4px 16px 4px 4px; }
  .r3-setup .r3-route-index::-webkit-scrollbar { display:none; }
}
/* IR-8468 (owner: "Am I done? … Should the green color coding now apply to the entire routing window?"): every route of
   the shown approval is marked and points at it; a route opens its approval; and "all connected routes should turn fully green color code. So should the confirmed bundle": a confirmed route and a
   confirmed approval are green wherever they are. */
.r3-window .r3-route-chip[data-r3="view-bundle"] { cursor:pointer; }
.r3-window .r3-flow-layout { position:relative; }
.r3-window .r3-route-chip.is-selected::after, .r3-window .r3-route-chip.is-confirmed.is-selected::after { content:none; }
.r3-pointers { position:absolute; inset:0; overflow:visible; pointer-events:none; z-index:1; }
.r3-pointers path { fill:none; stroke:var(--primary); stroke-width:2; stroke-linecap:round; filter:drop-shadow(0 0 3px color-mix(in srgb, var(--primary) 70%, transparent)); }
.r3-pointers path.is-confirmed { stroke:var(--ok); filter:drop-shadow(0 0 3px color-mix(in srgb, var(--ok) 70%, transparent)); }
.r3-window .r3-stage-head h3 .r3-state { font-size:.8rem; font-weight:600; }
.r3-flowline.is-confirmed .r3-dec-needed, .r3-member.is-confirmed > span:last-child { color:var(--ok); }
.r3-terminal.is-complete { border-color:color-mix(in srgb, var(--ok) 55%, transparent); background:color-mix(in srgb, var(--ok) 12%, var(--bg3)); box-shadow:0 0 14px -4px var(--ok); }
.r3-terminal.is-complete strong { display:inline-flex; align-items:center; color:var(--ok); font-size:.9rem; }
.r3-complete-tick { display:inline-grid; place-items:center; inline-size:18px; block-size:18px; margin-inline-end:8px; border-radius:50%; background:var(--ok); color:var(--bg); font-size:.75rem; line-height:1; }
.r3-flow-stage.is-confirmed { border-color:var(--ok); box-shadow:0 0 14px -5px var(--ok); }
.r3-flow-stage.is-confirmed .r3-flowline { border-color:color-mix(in srgb, var(--ok) 45%, transparent); }
.r3-flow-stage.is-confirmed .r3-arrow { color:var(--ok); }
.r3-window .r3-route-chip.is-confirmed { border-color:color-mix(in srgb, var(--ok) 40%, transparent); }
.r3-window .r3-route-chip.is-confirmed.is-selected { border-color:var(--ok); box-shadow:inset 2px 0 var(--ok), 0 0 10px -4px var(--ok); background:color-mix(in srgb, var(--ok) 10%, var(--bg3)); }
.r3-window .r3-route-chip.is-confirmed.is-selected::after { border-color:var(--ok); filter:drop-shadow(0 0 4px color-mix(in srgb, var(--ok) 75%, transparent)); }
.r3-route-index .r3-route-name, .r3-route-index .r3-state { white-space:nowrap; overflow:visible; overflow-wrap:normal; }
.r3-route-index .r3-route-name { min-inline-size:max-content; }
.r3-edit, .r3-edit--lp { grid-template-columns:repeat(4,minmax(0,1fr)); }
.r3-edit > label:first-child, .r3-edit > label:nth-of-type(4) { grid-column:span 2; }
.r3-edit .board-filter-field .ceres-btn.csel-trigger { justify-content:flex-start; text-align:start; padding-inline:var(--ceres-control-inline-pad); }
.r3-edit .board-filter-field .csel-value { flex:1 1 auto; justify-content:flex-start; min-inline-size:0; }
.r3-edit .board-filter-field .csel-trigger { block-size:auto; min-block-size:var(--ceres-control-min-block, 2.75rem); }
.r3-edit .board-filter-field .csel-value-text { overflow:visible; text-overflow:clip; white-space:normal; overflow-wrap:anywhere; text-align:start; }
.r3-edit .csel-arrow { flex:none; margin-inline-start:auto; }
.r3-edit--lp > label:nth-of-type(6), .r3-edit--lp > label:nth-of-type(7) { grid-column:auto; grid-row:auto; }
.r3-edit--lp > button { grid-column:auto; grid-row:auto; }
#harvest-modal-overlay .harvest-panel.is-r3 .hm-foot .ceres-btn { block-size:var(--ceres-control-min-block, 2.75rem); min-block-size:var(--ceres-control-min-block, 2.75rem); font-size:var(--ceres-control-font-size, 1rem); }
.r3-window .r3-stage-status { min-block-size:0; }
@media (max-width:1050px) { .r3-edit, .r3-edit--lp { grid-template-columns:repeat(2,minmax(0,1fr)); } .r3-edit > label:first-child, .r3-edit > label:nth-of-type(4) { grid-column:span 2; } }
@media (max-width:760px) { .r3-edit, .r3-edit--lp { grid-template-columns:minmax(0,1fr); } .r3-edit > label:first-child, .r3-edit > label:nth-of-type(4) { grid-column:auto; } .r3-summary .r3-summary-line { margin-inline-start:0; } .r3-route-index .r3-route-name { min-inline-size:0; white-space:normal; } }

.hm-resource-summary:not(.hm-resource-summary--compact) .hm-resource-pair:first-child { border-inline-start:0; }

/* Shared helper anatomy: compact region rows keep the same paired resource vocabulary. */
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-pair {
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: .55rem;
  min-inline-size: 0;
  padding: .25rem .4rem;
  border-inline-start: 1px solid var(--border);
}
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-pair.is-row-start,
.hm-resource-summary--compact .hm-totals--net .hm-resource-pair.is-row-start { border-inline-start:0; }
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-pair > img {
  grid-column: 1;
  grid-row: 1 / 3;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  object-fit: contain;
}
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-label {
  grid-column: 2;
  grid-row: 1;
  min-inline-size: 0;
  font-size: .8rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-values {
  display: grid;
  grid-column: 2;
  grid-row: 2;
  justify-items: start;
  gap: .1rem;
  min-inline-size: 0;
}
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-values .hm-total {
  display: grid;
  justify-items: start;
  gap: 0;
  min-inline-size: 0;
  padding: 0;
  border: 0;
  background: transparent;
}
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-values b {
  font-size: .9rem;
  line-height: 1.15;
  white-space: nowrap;
}
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-values small {
  display: block;
  color: var(--text-dim);
  font-size: .65rem;
  font-weight: 700;
}
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-debit b { color: var(--danger); }
.hm-resource-summary .hm-totals:not(.hm-totals--receipt) .hm-resource-pair.is-absent { opacity: .45; }
.hm-resource-summary.hm-resource-summary--compact .hm-totals.hm-totals--net .hm-resource-pair {
  grid-template-columns: 1.35rem minmax(0, max-content);
  justify-content: center;
  column-gap: .35rem;
  padding-inline: .4rem;
}

/* W6b: one resource tile follows the wallet row grammar: art, name, paired figures. */
.hm-resource-summary .hm-totals--receipt {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: .75rem 0;
  align-items: stretch;
}
.hm-resource-summary .hm-totals--receipt .hm-resource-pair {
  display: grid;
  grid-column: span 3;
  grid-template-columns: 2rem minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: .55rem;
  min-inline-size: 0;
  padding: .6rem .65rem;
  border: 0;
  border-inline-start: 0;
  background: color-mix(in srgb, var(--bg2) 58%, transparent);
}
.hm-resource-summary .hm-totals--receipt .hm-resource-pair.is-no-art { grid-template-columns: minmax(0, 1fr); }
.hm-resource-summary .hm-totals--receipt .hm-resource-pair.is-no-art .hm-resource-label,
.hm-resource-summary .hm-totals--receipt .hm-resource-pair.is-no-art .hm-resource-values { grid-column: 1; }
.hm-resource-summary .hm-totals--receipt .hm-resource-pair > img {
  grid-column: 1;
  grid-row: 1;
  inline-size: 2rem;
  block-size: 2rem;
  object-fit: contain;
}
.hm-resource-summary .hm-totals--receipt .hm-resource-label {
  grid-column: 2;
  grid-row: 1;
  min-inline-size: 0;
  color: var(--heading);
  font-weight: 700;
  overflow-wrap: anywhere;
}
.hm-resource-summary .hm-totals--receipt .hm-resource-values {
  display: grid;
  grid-column: 1 / -1;
  grid-row: 2;
  justify-items: start;
  gap: .15rem;
  min-inline-size: 0;
}
.hm-resource-summary .hm-totals--receipt .hm-resource-values .hm-total {
  display: grid;
  justify-items: start;
  gap: 0;
  min-inline-size: 0;
  padding: 0;
  border: 0;
  background: transparent;
}
.hm-resource-summary .hm-totals--receipt .hm-resource-values b {
  font-size: 1.05rem;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.hm-resource-summary .hm-totals--receipt .hm-resource-values small {
  display: block;
  color: var(--text-dim);
  font-size: .72rem;
  font-weight: 700;
}
.hm-resource-summary .hm-totals--receipt .hm-resource-values .hm-resource-debit b { color: var(--danger); }
.hm-resource-summary .hm-totals--receipt .hm-resource-pair.is-absent { opacity: .45; }
.hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(5):nth-last-child(1) { grid-column: span 12; }
.hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(5):nth-last-child(2),
.hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(6):last-child { grid-column: span 6; }
.hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(5):nth-last-child(3),
.hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(6):nth-last-child(2),
.hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(7):last-child { grid-column: span 4; }
.hm-resource-summary .hm-totals--receipt::before { content: none; }
@media (min-width:64rem) {
  .harvest-receipt { container-type: inline-size; }
  @container (max-width:39rem) {
    .harvest-receipt .hm-totals--receipt .hm-resource-pair { padding-inline: .35rem; column-gap: .35rem; }
    .harvest-receipt .hm-totals--receipt .hm-resource-values b { font-size: .8rem; }
    .harvest-receipt .hm-totals--receipt .hm-resource-values small { font-size: .65rem; }
  }
}
@media (max-width: 42rem) {
  .hm-resource-summary .hm-totals--receipt { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .hm-resource-summary .hm-totals--receipt .hm-resource-pair { grid-column: span 3; }
  .hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(5):nth-last-child(1) { grid-column: span 6; }
  .hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(5):nth-last-child(2),
  .hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(6):last-child { grid-column: span 3; }
  .hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(5):nth-last-child(3),
  .hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(6):nth-last-child(2),
  .hm-resource-summary .hm-totals--receipt .hm-resource-pair:nth-child(7):last-child { grid-column: span 2; }
}

/* S-14080: .ceres-tabs is the global tab family (receipt family tabs, wallet resource tabs). */
.ceres-tabs { display:flex; flex-wrap:wrap; gap:.4rem; padding-block:.3rem .9rem; }
.ceres-tabs .ceres-btn { font-size:.75rem; padding:.4rem .65rem; min-block-size:2rem; }

html.ceres-lw-le-1919.ceres-lw-le-1279 .ceres-tabs .ceres-btn { min-block-size:2.75rem; }

.ceres-tabs [role="tab"][aria-selected="true"] { color:var(--heading); border-color:var(--accent); background:color-mix(in srgb,var(--accent) 22%,var(--bg3)); box-shadow:inset 0 -2px var(--accent); }

.hm-totals--net .hm-resource-net.is-gain b { color:var(--ok); }
.hm-totals--net .hm-resource-net.is-loss b { color:var(--danger); }

.worksite-panel .ws-signing-account { margin: 0 0 .6rem; }

/* S-12880: resolve a single desktop scene unit before nested card containers. */
@property --plot-scene-unit { syntax: "<length>"; inherits: true; initial-value: 1px; }
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band {
  --plot-scene-unit: calc(100cqi / 1920);
  --worksite-w: calc(370.432 * var(--plot-scene-unit));
  --worksite-x: calc(12.8 * var(--plot-scene-unit));
  --eos-card-column-start: calc(var(--worksite-x) + var(--worksite-w) + var(--eos-tile-gap));
  --eos-facts: calc(7.2 * var(--plot-scene-unit));
  --eos-controls: calc(25.6 * var(--plot-scene-unit));
  --eos-divider-m: calc(14.672 * var(--plot-scene-unit));
  --eos-tile-gap: calc((100cqi - 35.2 * var(--plot-scene-unit) - var(--worksite-x) - var(--worksite-w)) / (var(--eos-visible-slots, 1) + var(--eos-visible-dividers, 0)) * .08);
  --eos-slot-w: calc((100cqi - 35.2 * var(--plot-scene-unit) - var(--worksite-x) - var(--worksite-w) - (var(--eos-visible-slots, 1) + var(--eos-visible-dividers, 0)) * var(--eos-tile-gap) - var(--eos-visible-dividers, 0) * (var(--plot-scene-unit) + 2 * var(--eos-divider-m))) / var(--eos-visible-slots, 1));
  --eos-card-top: max(calc(40 * var(--plot-scene-unit)), calc(var(--eos-slot-w) * 2.3 / 6.7 / 2 + 3.2 * var(--plot-scene-unit)));
  --eos-footer: max(calc(43.2 * var(--plot-scene-unit)), calc(var(--eos-slot-w) * .3));
  --eos-content-height: calc(var(--eos-facts) + var(--eos-controls) + var(--eos-cards) + var(--eos-footer) + 21 * var(--plot-scene-unit));
  --hv-pad: calc(5.6 * var(--plot-scene-unit)); --hv-edge: calc(4 * var(--plot-scene-unit)); --hv-btn: calc(34.4 * var(--plot-scene-unit));
  --hv-h: calc(var(--hv-btn) + 2 * var(--hv-pad) + var(--hv-edge) + 2 * var(--plot-scene-unit));
  --art-gap: calc(8 * var(--plot-scene-unit));
}
/* IR-8352: a single row of land abilities sits 28/107.2 card widths above the tile (24 icon + 4 gap). The default card top
   (half the satellite) left it inside the plot controls, so the row collided with Fill plot / Clear workers / Clear plot.
   A plot holding such a card gets card top = row height + 8/107.2 clearance; the arc rule below still wins when both occur. */
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band:has(.slot-ability-arc) { --eos-card-top: max(calc(40 * var(--plot-scene-unit)), calc(var(--eos-slot-w) * 55 / 107.2)); }
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band[data-eos-render-error][data-eos-ability-arc] { --eos-card-top: max(calc(40 * var(--plot-scene-unit)), calc(var(--eos-slot-w) * 55 / 107.2)); }
html:not(.ceres-lw-le-1279) .slot-editor > .editor-band[data-eos-render-error][data-eos-vov-arc] { --eos-ability-room: calc(var(--eos-slot-w) * 24 / 107.2); }
html:not(.ceres-lw-le-1279) .editor-band .slot-editor-body,
html:not(.ceres-lw-le-1279) .editor-band .slot-editor-footer { inset-inline: calc(12.8 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .slot-grid { padding-right: calc(9.6 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .slot-grid-divider { width: var(--plot-scene-unit); flex-basis: var(--plot-scene-unit); }
html:not(.ceres-lw-le-1279) .editor-band .editor-worksite-art {
  --abox-top: calc(var(--eos-facts) + var(--eos-controls) + var(--eos-cards) - 10.12 * var(--plot-scene-unit) - var(--art-gap));
  --art-fit: min(calc(var(--ws-scale) * (var(--worksite-x) + var(--worksite-w) + 12.8 * var(--plot-scene-unit) - var(--ac)) / ((var(--art-r) - var(--art-cx)) * var(--art-ratio))), calc((var(--ac) - var(--eos-affinity-size) - 16.8 * var(--plot-scene-unit)) / ((var(--art-cx) - var(--art-l)) * var(--art-ratio))), calc((var(--eos-card-column-start) - var(--eos-tile-gap) - var(--ac)) / ((var(--art-r) - var(--art-cx)) * var(--art-ratio))), calc((var(--abox-top) - var(--eos-facts) - var(--eos-controls) - 4 * var(--plot-scene-unit)) / (var(--art-base) - var(--art-t))));
}
html:not(.ceres-lw-le-1279) .editor-worksite-art[data-worksite-kind="grain"] { --ws-scale: 1.25; }
html:not(.ceres-lw-le-1279) .editor-worksite-art[data-worksite-kind="wood"] { --ws-scale: 1.15; }
html:not(.ceres-lw-le-1279) .editor-worksite-art[data-worksite-kind="iron"] { --ws-scale: 1.12; }
html:not(.ceres-lw-le-1279) .editor-worksite-art[data-worksite-kind="stone"] { --ws-scale: 1.1; }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar { top: calc(15.7 * var(--plot-scene-unit)); gap: calc(8.8 * var(--plot-scene-unit)); border-width: var(--plot-scene-unit); border-radius: calc(10 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar.is-no-worksite { top: calc(115.2 * var(--plot-scene-unit) - var(--eos-cards)); left: calc(44 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar :is(.harvest-bar-mid, .harvest-bar-amt, .harvest-bar-stack) { font-size: calc(22.4 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar-mid { gap: calc(7.2 * var(--plot-scene-unit)); padding-inline: calc(2.4 * var(--plot-scene-unit)); inline-size: calc(39.2 * var(--plot-scene-unit) + var(--amt-ch, 8ch)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar-lbl { font-size: max(10.5px, calc(10.56 * var(--plot-scene-unit))) !important; }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar-state { font-size: max(10.5px, calc(12.8 * var(--plot-scene-unit))); padding-inline: calc(6.4 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar-res { width: calc(32 * var(--plot-scene-unit)); height: calc(32 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar .ceres-btn { --ceres-control-icon-size: calc(19.2 * var(--plot-scene-unit)); inline-size: var(--hv-btn); block-size: var(--hv-btn); min-inline-size: var(--hv-btn); min-block-size: var(--hv-btn); padding: 0; }

html:not(.ceres-lw-le-1279) .editor-band .harvest-bar:is(.is-building, .is-tax) .harvest-bar-mid { inline-size: auto; padding-inline: calc(4.8 * var(--plot-scene-unit)) calc(8 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar.is-building .harvest-bar-build-eta { font-size: max(10.5px, calc(13.76 * var(--plot-scene-unit))); gap: calc(5.6 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar.is-building .harvest-bar-build-eta.is-done { font-size: max(10.5px, calc(13.6 * var(--plot-scene-unit))); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar-build-ic { width: calc(32 * var(--plot-scene-unit)); height: calc(32 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar-build-ic .spinner { width: calc(30.4 * var(--plot-scene-unit)); height: calc(30.4 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar.is-building .harvest-bar-build-name .resource-icon { inline-size: calc(14.4 * var(--plot-scene-unit)); block-size: calc(14.4 * var(--plot-scene-unit)); }
html:not(.ceres-lw-le-1279) .editor-band .harvest-bar-tax-ic { inline-size: calc(16.8 * var(--plot-scene-unit)); block-size: calc(16.8 * var(--plot-scene-unit)); }

/* The confirmation receipt keeps one fixed set of rows through every phase. */
.ceres-pill.worksite-banner {
  width: min(34rem, calc(var(--ceres-vw) * 100 - 2rem));
  height: min(12.5rem, calc(var(--ceres-vh) * 100 - 2rem));
  min-width: 0;
  max-width: calc(var(--ceres-vw) * 100 - 2rem);
  min-height: 0;
  max-height: calc(var(--ceres-vh) * 100 - 2rem);
  overflow: hidden;
  font-size: .85rem;
  gap: .4rem;
  padding: .65rem .8rem .7rem;
}
html.ceres-lw-le-1099 .ceres-pill.worksite-banner {
  min-width: 0;
  max-width: calc(var(--ceres-vw) * 100 - 2rem);
}
.ceres-pill.worksite-banner .ceres-pill-title-row { flex: 0 0 2.25rem; min-height: 2.25rem; }
.ceres-pill.worksite-banner .worksite-banner-title { block-size: 2.25rem; overflow: hidden; white-space: nowrap; }
.ceres-pill.worksite-banner .worksite-banner-title > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ceres-pill.worksite-banner .ceres-pill-phase-mark { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 1.4rem; block-size: 1.4rem; }
.ceres-pill.worksite-banner .ceres-pill-phase-mark .spinner { width: 1.15rem; height: 1.15rem; }
.ceres-pill.worksite-banner .worksite-banner-detail { display: block; block-size: 1.35rem; line-height: 1.35rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ceres-pill-content { display: grid; flex: 1 1 auto; grid-template-rows: 1.35rem 2rem 1.45rem 2.5rem; gap: .25rem; min-width: 0; min-height: 0; overflow: hidden; }
.ceres-pill-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .35rem; min-width: 0; block-size: 2rem; }
.ceres-pill-fact { display: flex; flex-direction: column; justify-content: center; min-width: 0; overflow: hidden; padding: .15rem .4rem; }
.ceres-pill-label { overflow: hidden; font-size: .56rem; line-height: .65rem; white-space: nowrap; }
.ceres-pill-value { overflow: hidden; font-size: .75rem; line-height: .9rem; text-overflow: ellipsis; white-space: nowrap; }
.ceres-pill-transaction { display: flex; align-items: center; min-width: 0; min-height: 0; margin: 0; overflow: hidden; white-space: nowrap; }
.ceres-pill-transaction a { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ceres-pill-transaction.is-empty { visibility: hidden; }
.ceres-pill-action-row { display: grid; grid-template-columns: repeat(2, 7.5rem); justify-content: end; align-items: center; gap: .35rem; min-width: 0; min-height: 0; }
.ceres-pill-action-slot { display: flex; width: 7.5rem; min-width: 0; }
.ceres-pill-action-slot.is-empty { visibility: hidden; }
.ceres-pill-action-slot .ceres-pill-action { width: 100%; white-space: nowrap; }
.ceres-pill-action-row .ceres-pill-action { max-width: 100%; }
.ceres-pill.worksite-banner .ceres-pill-success-icon { flex-basis: 1.4rem; inline-size: 1.4rem; block-size: 1.4rem; }
.ceres-pill-progress { position: absolute; inset-inline: 0; bottom: 0; }
.ceres-pill-op-results { display: grid; gap: .5rem; font-size: .85rem; line-height: 1.45; overflow-wrap: anywhere; }
.ceres-pill-op-results > div { padding: .45rem .6rem; border-radius: var(--radius); background: color-mix(in srgb, var(--bg3) 70%, transparent); }
.ceres-pill-op-results > .is-confirmed > span:last-child { color: var(--ok); font-weight: 700; }
.ceres-pill-op-results > .is-failed > span:last-child { color: var(--danger); }
.ceres-pill.is-complete {
  border-color: var(--ok);
  background: color-mix(in srgb, var(--ok) 15%, var(--bg2));
}
.ceres-pill.is-complete .ceres-pill-title { color: var(--ok); }
.ceres-pill.is-complete .ceres-pill-fact,
.ceres-pill.is-complete .ceres-pill-op-results > div { background: color-mix(in srgb, var(--ok) 10%, var(--bg2)); }
.ceres-pill-success-icon { display: inline-grid; place-items: center; flex: 0 0 1.7rem; height: 1.7rem; border: 1px solid var(--ok); border-radius: 50%; color: var(--ok); background: color-mix(in srgb, var(--ok) 15%, transparent); font-family: var(--font-body); }
.ceres-pill.is-complete .ceres-pill-progress::before { background: var(--ok); }
.ceres-pill-batch li { flex-wrap: wrap; }

/* Matching companions use the same gold emphasis in both picker layouts. */
.candidate-bloodline-pair { display: inline-flex; align-items: center; justify-content: center; gap: .3rem; max-width: 100%; box-sizing: border-box; padding: .2rem .4rem; border: 1px solid var(--warn); border-radius: 6px; background: color-mix(in srgb, var(--warn) 15%, var(--bg3)); color: var(--warn); font-size: .75rem; font-weight: 700; line-height: 1.25; }
.candidate-bloodline-pair .ability-icon { width: 1.2rem; height: 1.2rem; flex: 0 0 auto; }
.candidate-grid-face > .candidate-bloodline-pair { align-self: center; }

:is(td.plot-cell, .csel-option-territory, .csel-trigger.region-filled).territory-land-of-prosperity { font-weight: 700; text-shadow: 0 1px 2px color-mix(in srgb, var(--shadow) 85%, transparent), 0 0 4px color-mix(in srgb, var(--shadow) 55%, transparent); }

/* S-16751 round 4 (S-16830 micro-fix, tuned in rig): the phone Total drops the desktop toggle column so its 2-column grid fills the box; at <=380px the amount spans the full badge width. */
@media (max-width: 767px) { #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total-ledger > .hm-batch-ledger-line:has(> .hm-batch-ledger-label[aria-hidden="true"]) { grid-template-columns: minmax(0,1fr); } #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-toggle-spacer { display: none; } }
@media (max-width: 380px) { #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-icon { grid-row: 1; } #harvest-modal-overlay .harvest-receipt-summary .harvest-receipt-ledger .hm-batch-total .hm-batch-number { grid-column: 1 / -1; } }
/* S-16844e: every setup control fills its grid cell, so the form reads as aligned columns; the "%" chip is part of
   its input (same 44px block, same border line and paint as .ceres-wide-input, primary edge once filled). */
.r3-edit .csel-wrap, .r3-edit .board-filter-field .csel-trigger { box-sizing:border-box; inline-size:100%; max-inline-size:none; }
.r3-edit .r3-percent-field > input { flex:1 1 auto; inline-size:auto; min-inline-size:0; }
.r3-edit .r3-percent-field > span { padding-inline:var(--ceres-control-inline-pad); border:1px solid color-mix(in srgb, var(--heading) 70%, var(--bg3)); border-inline-start:0; border-radius:0 8px 8px 0; background:var(--bg3); color:var(--heading); font-weight:600; }
.r3-edit .r3-percent-field:has(> input:not(:placeholder-shown)) > span { border-color:var(--primary); }
.r3-window .r3-stage-status:empty { display:none; }

/* S-16886: the warnings under an open plot (owner picked looks A + C of S-16881). A = an icon-only
   pill on the bottom edge of each affected card; C = the impact list below the plot, grouped by
   severity, one sentence per warning. Hovering a row lights its pills and the reverse
   (`.is-plot-warning-linked`, a class toggle -- nothing here transitions). Text keeps the proven
   `--bg3`/`--heading` pair; severity colour is only ever a border, icon or bar. */
:is(.plot-warning-pill, .plot-warning-row, .plot-warning-heading).is-danger { --plot-warning-tone: var(--danger); }
:is(.plot-warning-pill, .plot-warning-row, .plot-warning-heading).is-warn { --plot-warning-tone: var(--warn); }
:is(.plot-warning-pill, .plot-warning-row, .plot-warning-heading).is-info { --plot-warning-tone: var(--info); }
.eos-permanent-frame:has(> .plot-warning-pills) { position: relative; }
.plot-warning-pills { position: absolute; z-index: 7; left: 50%; bottom: 0; transform: translate(-50%, 50%); display: flex; gap: .25rem; pointer-events: auto; }
/* IR-9318: keep worker energy warnings centred on the card's bottom edge, including when the phone grid reserves crown clearance. */
.slot-tile-cell > .plot-warning-pills { top: 100%; bottom: auto; transform: translate(-50%, -50%); }
.plot-warning-pill { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; inline-size: 1.75rem; block-size: 1.75rem; border: 1px solid var(--plot-warning-tone); border-radius: 999px; background: color-mix(in srgb, var(--plot-warning-tone) 22%, var(--bg3)); box-shadow: 0 2px 6px color-mix(in srgb, var(--shadow) 35%, transparent); }
/* Owner, 2026-09-28: a pill means the card is being unstaged, so it overrides any tile badge or
   bottom plaque in that spot -- it sits on top and the covered badge is hidden, never peeking out. */
:is(.slot-tile-cell, .asset-slot, .eos-permanent-frame):has(> .plot-warning-pills) .slot-tile .slot-tags[data-fitted="1"] { visibility: hidden; }
.plot-warning-pill-icon { inline-size: 1.15rem; block-size: 1.15rem; object-fit: contain; }
.plot-warning-pill.is-plot-warning-linked { outline: 2px solid var(--plot-warning-tone); outline-offset: 2px; }
.plot-warning-svg { display: inline-flex; color: var(--plot-warning-tone, var(--info)); }
.plot-warning-svg svg { inline-size: 100%; block-size: 100%; }
.plot-warning-list { margin: .5rem .8rem .25rem; }
.plot-warning-list ul { display: grid; gap: .35rem; margin: 0; padding: 0; list-style: none; }
.plot-warning-heading { justify-self: start; margin-top: .25rem; padding-bottom: .1rem; border-bottom: 2px solid var(--plot-warning-tone); color: var(--heading); font-size: .7rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.plot-warning-row { display: flex; align-items: center; gap: .75rem; padding: .35rem .5rem; border: 1px solid var(--border); border-left: 3px solid var(--plot-warning-tone); border-radius: var(--radius); background: var(--bg3); color: var(--heading); font-size: .8rem; }
.plot-warning-row.is-plot-warning-linked { border-color: var(--plot-warning-tone); }
.plot-warning-thumbs { display: flex; flex: none; align-items: center; gap: .25rem; }
.plot-warning-thumb { display: inline-flex; align-items: center; justify-content: center; inline-size: 2rem; block-size: 2.8rem; overflow: hidden; border-radius: calc(var(--radius) - 3px); background: var(--bg2); }
.plot-warning-thumb img { inline-size: 100%; block-size: 100%; object-fit: contain; }
.plot-warning-kind { inline-size: 1.25rem; block-size: 1.25rem; flex: none; object-fit: contain; }
.plot-warning-text { display: flex; flex: 1 1 auto; flex-direction: column; gap: .1rem; min-width: 0; }
.plot-warning-text b { font-weight: 700; }
.plot-warning-action { flex: none; margin-inline-start: auto; }
.plot-warning-find-icon { inline-size: 1rem; block-size: 1rem; object-fit: contain; }

/* IR-8537 (owner, after a full Harvest all test): the routing window never scrolls and always shows what is happening.
   - The status row moved into the top summary row (it used to sit below the list and was covered by it).
   - The window body does not scroll; only the route list does, inside its own box, with its bar on the LEFT so it never
     covers the pointers that leave the list's right edge; routes are smaller so more fit; the list jumps to the active
     approval (routing-window.js scrollRouteIndexToActive); the approval box itself never scrolls. */
#harvest-modal-overlay .harvest-panel.is-r3 .hm-body:has(> .r3-window) { overflow: hidden; }
.r3-window { display: flex; flex-direction: column; flex: 1 1 auto; min-block-size: 0; gap: .6rem; }
.r3-window .r3-flow-layout { flex: 1 1 auto; min-block-size: 0; align-items: stretch; }
.r3-window .r3-route-index { min-block-size: 0; max-block-size: none; align-self: stretch; overflow-x: hidden; overflow-y: auto;
  overscroll-behavior: contain; direction: rtl; scrollbar-width: none; scrollbar-gutter: auto; row-gap: 4px; }
.r3-window .r3-route-index > * { direction: ltr; }
.r3-window .r3-flow-stage { min-block-size: 0; overflow: hidden; }
.r3-window .r3-route-chip { min-block-size: 42px; }
.r3-summary .r3-summary-status { display: inline-flex; align-items: center; gap: 10px; flex: 1 1 24rem; min-inline-size: 0; padding: 4px 10px;
  border: 1px solid color-mix(in srgb, var(--warn) 55%, var(--border)); border-radius: 8px; background: color-mix(in srgb, var(--warn) 8%, var(--bg3)); }
.r3-summary .r3-summary-status > span { white-space: nowrap; }
.r3-summary .r3-summary-status .r3-status-message { flex: 1 1 auto; min-inline-size: 0; }


/* IR-8541/IR-8544/IR-8548 (owner: "perfect alignment of all lines and boxes ... use the length of the longest resource text"): the route
   list is ONE grid and every chip (and its text) is a subgrid of it, so each column is exactly as wide as its widest entry -- badge,
   resource icon, resource name, arrow, destination, state -- and every row lines up with every other row, with no fixed widths and no
   gap after a short name. The state column takes the rest and is cut with an ellipsis when long (the full text is in the tooltip). */
.r3-route-index .r3-route-name { display: inline-flex; align-items: center; gap: .3rem; min-inline-size: 0; }
.r3-route-index .r3-route-name > .r3-to { display: inline-flex; align-items: center; gap: .3rem; }
.r3-route-index .r3-route-name > .r3-state { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.r3-route-index .r3-route-name > .r3-route-arrow { color: inherit; text-align: center; }
@media (min-width: 761px) {
  .r3-route-index { grid-template-columns: auto auto auto auto auto minmax(0, 1fr); column-gap: 7px; }
  .r3-route-index > .r3-route-chip { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: center; }
  .r3-route-index > .r3-route-chip > .r3-route-name { grid-column: 2 / -1; display: grid; grid-template-columns: subgrid; align-items: center; column-gap: 7px; }
  .r3-route-index > .r3-bundle-head { grid-column: 1 / -1; }
  .r3-route-index .r3-route-name > .r3-route-arrow { padding-inline: .2rem; }
}
/* IR-8543: the setup's flow preview uses the same two boxes as the approval window; its flowline is taller than the boxes, so equal
   height there is a shared minimum (the LP pool box has three rows). */
.r3-setup .r3-flowline > :is(.r3-pool, .r3-destination--two) { min-block-size: 80px; box-sizing: border-box; }

/* IR-8544 (owner: "find a fitting solution that does not cut space and make all look unaligned"): the route list has NO pill rows and no
   wrapper -- it is exactly as tall as the box beside it. The scroll cue costs no list space: (1) the summary row, which has free room
   right above the list, shows "showing 4-12 of 36" with two page buttons while the list overflows; (2) the list edge that has more
   routes fades softly (a mask, so nothing is painted or moved). */
.r3-scroll-cue { display: inline-flex; align-items: center; gap: 6px; }
.r3-scroll-cue[hidden] { display: none; }
.r3-scroll-cue .r3-scroll-cue-text { min-inline-size: 9ch; text-align: end; color: var(--meta); font-variant-numeric: tabular-nums; white-space: nowrap; }
.r3-scroll-cue .r3-more { min-inline-size: 0; padding-inline: .55rem; }
.r3-route-index.has-more-above { --r3-fade-top: 22px; }
.r3-route-index.has-more-below { --r3-fade-bottom: 22px; }
.r3-route-index.has-more-above, .r3-route-index.has-more-below {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, var(--bg) var(--r3-fade-top, 0px), var(--bg) calc(100% - var(--r3-fade-bottom, 0px)), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, var(--bg) var(--r3-fade-top, 0px), var(--bg) calc(100% - var(--r3-fade-bottom, 0px)), transparent 100%);
}
/* the summary row grows with its content (it must never be squeezed below it) and its status segment gives way before anything wraps */
.r3-window .r3-summary { flex: none; }
.r3-window .r3-summary .r3-summary-status { flex: 1 1 12rem; }
.r3-window .r3-summary .r3-summary-status .r3-status-message { overflow: hidden; text-overflow: ellipsis; }

/* IR-8545 (found while verifying IR-8544): at 761-1279 px wide the compact "page flow" rules (mobile-plot.css, S-16924) lifted every
   height limit from the harvest panel, so the routing window grew with its route list (2003 px tall at 36 routes) and the list never
   scrolled. From 761 px up the window is wide enough for the two-column layout, so it is height-limited again: the panel fills one
   screen, the window body does not scroll and only the route list scrolls. Phones (<= 760 px) keep the page flow on purpose. */
@media (min-width: 761px) {
  html.ceres-lw-le-1919.ceres-lw-le-1279 #harvest-modal-overlay:not([hidden]) > .harvest-panel.is-r3:has(.r3-window) { display: flex; flex-direction: column; block-size: calc(var(--ceres-vh, 1dvh) * 100); max-block-size: calc(var(--ceres-vh, 1dvh) * 100); overflow: hidden; }
  html.ceres-lw-le-1919.ceres-lw-le-1279 #harvest-modal-overlay .harvest-panel.is-r3 .hm-body:has(> .r3-window) { flex: 1 1 auto; min-block-size: 0; overflow: hidden; display: flex; flex-direction: column; }
}

/* IR-8546 (owner: "the new warning row placement breaks the spinner. make the row higher so it aligns with the posting key box on its
   right side"): the status segment in the summary row is as tall as the wallet chip beside it and gives the spinner room. */
.r3-summary .r3-summary-status { align-self: stretch; min-block-size: 48px; padding: 6px 14px 6px 12px; box-sizing: border-box; }
.r3-summary .r3-summary-status .r3-status-spinner { flex: none; align-self: center; margin-inline-start: auto; }

/* IR-8547: the percentage joins the amount in the flow box ("1,000.00 · 25%"); badge, icon, name and amount stay on ONE line for every
   resource (the box used to wrap the amount under the name when the name was wide). */
@media (min-width: 1280px) { .r3-flow-source, .r3-window .r3-flow-source { flex-wrap: nowrap; } }

/* IR-8550: the pre-harvest routing screen says what comes next and has one clear way forward. */
.r3-pre-harvest { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg2); color: var(--body); }

/* IR-8534 S10 round 2 / L4-06: a missing artwork image is hidden, not shown as a broken-image icon. */
img[data-art-missing] { visibility: hidden; }

/* IR-8553: the optional "Save across devices" control sits with the footer buttons (next to Add route and Sign) */
.r3-space-state { color: var(--muted); }
.r3-save-state { display: inline-flex; align-items: center; gap: .4rem; color: var(--warn); }
.r3-save-state::before { content: ''; flex: none; inline-size: .5rem; block-size: .5rem; border-radius: 50%; background: currentColor; }
.r3-save-state.is-ok { color: var(--ok); }
.r3-save-state.is-warn { color: var(--warn); }
.r3-save-state.is-bad { color: var(--danger); font-weight: 700; }
.r3-space-note { margin: 8px 0 0; color: var(--warn); }

/* IR-8553: the account menu (the wallet-logo button beside Switch player) in the glass design of the wallet window: the same translucent fill, blur and iris glow as .dec-drawer-panel (radius from the standard scale) */
.account-menu-btn.is-signed-in { border-color: var(--warn); }   /* signed-in marker: the global warn colour as the button's own border */
.account-menu-btn .wallet-logo { inline-size: var(--ceres-control-icon-size, 1.25em); block-size: var(--ceres-control-icon-size, 1.25em); flex-basis: auto; }
.account-menu { position: fixed; z-index: 120; box-sizing: border-box; inline-size: min(22rem, calc(100vw - 16px)); max-block-size: calc(100vh - 5rem); overflow-y: auto; display: grid; justify-items: start; gap: .65rem; padding: .75rem; border-radius: 8px; background: color-mix(in srgb, var(--shadow) 82%, transparent); backdrop-filter: saturate(130%) blur(4px); -webkit-backdrop-filter: saturate(130%) blur(4px); box-shadow: 0 0 18px -3px var(--primary); }
:is([data-theme="light"], [data-theme="wood"]) .account-menu { background: color-mix(in srgb, var(--bg2) 92%, transparent); }
.account-menu[hidden] { display: none; }
.acct-row { flex-wrap: nowrap; }
.acct-row .wallet-controls { inline-size: auto; }
.acct-note { margin: 0; max-inline-size: 22rem; color: var(--muted); }
.acct-warn { color: var(--warn); }
.acct-state { color: var(--ok); }
/* IR-8592: the account choice and HiveAuth request stay inside the one panel. */
.acct-wallet-choice { display: flex; flex-wrap: wrap; gap: .35rem; }
.acct-wallet-choice .ceres-btn { display: inline-flex; align-items: center; gap: .35rem; }
.acct-wallet-choice .ceres-btn[aria-pressed="true"] { border-color: var(--accent); }
.acct-wallet-choice .wallet-logo { inline-size: 1.25rem; block-size: 1.25rem; }
.acct-wait { display: flex; align-items: center; gap: .5rem; color: var(--warn); }
.acct-pairing { display: grid; justify-items: start; gap: .5rem; max-inline-size: 100%; }
.acct-pairing .wallet-qr { max-inline-size: min(15rem, 100%); }
.acct-pairing .wallet-qr svg { max-inline-size: 100%; block-size: auto; }
.acct-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }   /* IR-8599: Sign in / Sign out and Saved data options on one row */
.acct-actions .wallet-logo { inline-size: 1.25rem; block-size: 1.25rem; flex: none; }   /* IR-8600: the wallet icon of the current login on Sign out */
.acct-actions .ceres-btn { display: inline-flex; align-items: center; gap: .4rem; }
@media (max-width: 520px) { .acct-actions { gap: .35rem; } .acct-actions .ceres-btn { padding-inline: .55rem; } }
.acct-manage { display: grid; justify-items: start; gap: .5rem; inline-size: 100%; }
.acct-delete-actions { display: flex; flex-wrap: wrap; gap: .35rem; }
.acct-privacy { font-size: .75rem; }


/* IR-8558: a routing transaction whose result is not known yet, with its two ways out */

/* IR-8559: the counting column (last, right of the locks): muted numerals, not sortable */
table.plot-table :is(th.col-w-count, td.count-cell) { text-align: end; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.plot-table td.count-cell { color: var(--muted); }   /* only the row numerals are muted: the header cell keeps the global header colours */   /* right-aligned numerals: the header total and every row number share one right edge */
table.plot-table th.col-w-count { cursor: default; }
table.plot-table :is(col.col-w-count, th.col-w-count, td.count-cell) { inline-size: 1%; }   /* as narrow as its numerals */
/* IR-8559: the counting column is the last one, so it carries the badge-bar reserve like the cells below it. The generic last-child rule for the header loses on
   specificity to the shared padding rule, so the header gets the same end padding explicitly: its numeral and the row numerals share one centre line. */
html:not(.ceres-lw-le-1279) table.plot-table thead tr th.col-w-count { padding-inline-end: calc(var(--table-end-reserve) + clamp(.25rem, .3125cqi, .375rem)); }
table.plot-table :is(th.col-w-count, td.count-cell) { padding-inline-start: .35rem; }
/* IR-8579 (owner: the number counter column sits sticky-close to the badges although there is room): the numerals keep a clear gap from the badge strip. */
html:not(.ceres-lw-le-1279) table.plot-table :is(thead tr th.col-w-count, tbody tr.plot-row td.count-cell) { padding-inline-end: calc(var(--table-end-reserve) + clamp(.25rem, .3125cqi, .375rem) + 1.5rem); }

/* IR-8593 R1: four distinct steps; plan-only layout inside the existing windows. */
:root, [data-theme="alchemical"], [data-theme="dark"], [data-theme="light"] { --stage-planned: var(--gold); }
[data-theme="wood"] { --stage-planned: var(--gold-dim); }

/* IR-8593 R2: ready notification colour is defined per Iris theme before use. */
:root:not([data-theme]), [data-theme="alchemical"] { --ready-alert: color-mix(in srgb, var(--ok) 70%, var(--hi)); }
[data-theme="dark"] { --ready-alert: color-mix(in srgb, var(--ok) 70%, var(--hi)); }
[data-theme="light"], [data-theme="wood"] { --ready-alert: var(--ok); }

/* IR-8593 R2: the fixed Changes controls keep their geometry while readiness is visible. */
.ceres-btn.is-ready-alert {
  position: relative;
  isolation: isolate;
  overflow: visible;
  border-color: var(--ready-alert);
  box-shadow: inset 0 0 0 1px var(--ready-alert);
  background: color-mix(in srgb, var(--ready-alert) 20%, var(--bg3));
  transition: none;
}
.ceres-btn.is-ready-alert:focus-visible { box-shadow: 0 0 0 2px var(--bg3), inset 0 0 0 1px var(--ready-alert); }
.ceres-btn.is-ready-alert::after {
  position: absolute;
  z-index: -1;
  inset: -2px;
  border-radius: inherit;
  box-shadow: 0 0 14px 2px var(--ready-alert);
  content: "";
  opacity: .2;
  pointer-events: none;
  animation: changes-ready-pulse 1.6s ease-in-out infinite alternate;
}
.ceres-btn.is-ready-alert .checkout-count,
.ceres-btn.is-ready-alert #mobile-changes-count { background: var(--ready-alert); color: var(--bg); }
.ch-ready-now { border-color: var(--ready-alert); }
.ch-ready-now .ch-worksite-build-row { padding: .45rem .6rem; border-inline-start: 3px solid var(--ready-alert); background: color-mix(in srgb, var(--ready-alert) 10%, var(--bg2)); color: var(--ready-alert); }
@keyframes changes-ready-pulse { from { opacity: .2; } to { opacity: .85; } }
@media (prefers-reduced-motion: reduce) {
  .ceres-btn.is-ready-alert::after { animation: none; opacity: .6; }
}
.chain-editor, .chain-plans { display: grid; gap: 1rem; min-width: 0; }
.chain-hero { display: flex; align-items: center; gap: 1rem; min-width: 0; }
.chain-hero > :first-child:not(div) { width: 4rem; height: 5rem; object-fit: contain; flex: none; }
.chain-hero .editor-card-image, .chain-hero .card-image { width: 4rem; height: 5rem; object-fit: contain; }
.chain-hero h3, .chain-hero h4, .chain-hero p { margin: 0 0 .5rem; }
.chain-hero small, .chain-input small, .chain-copy small { display: block; color: var(--meta); }
.chain-levels { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.chain-summary { border-block: 1px solid var(--border); padding-block: .75rem; margin: 0; font-size: .9rem; }
.chain-copies { display: grid; gap: .25rem; }
.chain-copy { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: .75rem; align-items: center; border-left: 3px solid var(--border); padding: .5rem; background: var(--bg2); }
.chain-copy.is-survivor { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, var(--bg2)); }
.chain-copy.is-survivor .chain-copy-outcome { color: var(--ok); }
.chain-copy.is-consumed { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 8%, var(--bg2)); }
.chain-copy.is-consumed .chain-copy-outcome, .chain-problem { color: var(--danger); }
.chain-copy.is-muted { color: var(--meta); }
.chain-copy.is-unusable { opacity: .55; color: var(--meta); border-color: var(--bg2); }
.chain-check input { width: 1rem; height: 1rem; margin: 0; accent-color: var(--accent); }
.chain-check:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.chain-warn, .chain-copy small.chain-warn { color: var(--warn); }
.chain-loss { padding: .75rem; border: 1px solid var(--warn); border-radius: 8px; background: color-mix(in srgb, var(--warn) 8%, var(--bg2)); }
.chain-loss ul { margin-block: .5rem 0; padding-inline-start: 1.25rem; }
.chain-footer, .chain-card-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .75rem; }
.chain-footer { justify-content: flex-end; padding-top: .75rem; border-top: 1px solid var(--border); }
.chain-steps { display: grid; gap: .5rem; margin-top: .75rem; }
.chain-step { border-left: 4px solid var(--info); padding: .75rem; background: var(--bg2); }
/* IR-9174 (owner-approved state matrix, review page state-colours, 2026-10-14): a step's colour follows its outcome, not its
   number -- not started = --info (dashed), waiting on you / in progress = --warn, done = --ok, failed or refused = --danger.
   The step's number, icon and name stay its identity. */
.chain-step.is-waiting { border-left-color: var(--info); border-left-style: dashed; background: var(--bg2); }
.chain-step.is-active { border-left-color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, var(--bg2)); }
.chain-step.is-done { border-left-color: var(--ok); border-left-style: solid; background: color-mix(in srgb, var(--ok) 10%, var(--bg2)); }
.chain-step.is-problem { border-left-color: var(--danger); border-left-style: solid; background: color-mix(in srgb, var(--danger) 12%, var(--bg2)); }
.chain-step-details > summary { display: flex; align-items: center; gap: .5rem; color: var(--heading); font-size: .9rem; font-weight: 600; cursor: pointer; list-style: none; }
.chain-step-details > summary::-webkit-details-marker { display: none; }
.chain-step-state { margin-inline-start: auto; color: var(--meta); font-size: .75rem; font-weight: 400; }
.chain-step-content { padding-top: .5rem; }
.chain-step-number { display: inline-grid; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--info); color: var(--bg); }
.chain-step.is-waiting .chain-step-number { background: var(--info); }
.chain-step.is-active .chain-step-number { background: var(--warn); }
.chain-step.is-done .chain-step-number { background: var(--ok); }
.chain-step.is-problem .chain-step-number { background: var(--danger); }
.chain-input { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding-block: .25rem; }
.chain-input [data-countdown-until] { white-space: nowrap; }
.chain-result { margin: .5rem 0 0; padding-top: .5rem; border-top: 1px solid var(--border); } /* IR-9155: the combined card, CC / max CC and level */
.chain-phone { display: none; }
/* IR-8601 A14: the signing controls and hint use one logical width tier. */
html.ceres-lw-le-1279 .chain-phone { display: inline; }
@media (max-width: 1279px) {
  .chain-copy { grid-template-columns: auto minmax(0, 1fr); }
  .chain-copy-outcome { grid-column: 2; }
  .chain-input { flex-wrap: wrap; }
  .chain-hero { align-items: flex-start; }
  .chain-hero > div { min-width: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .combine-step.is-active .combine-step-dot { animation: none; }
}

/* IR-8593: the existing opacity-only step pulse, with a reduced-motion answer. */
.chain-step.is-active .chain-step-number { animation: combine-step-pulse 1.1s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .chain-step.is-active .chain-step-number { animation: none; }
}
/* IR-8593: keep long copy lists scrollable without changing the idle window. */
.is-plan-mode .combine-dialog-body { overflow-y: auto; min-height: 0; }
.is-plan-mode .combine-dialog-panel > header { justify-content: space-between; flex: none; }
.is-plan-mode .combine-dialog-panel > header h3 { color: var(--heading); }
.chain-editor { gap: .75rem; }
.chain-editor > h4 { margin: 0; }
.chain-editor .chain-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 0; }
.chain-editor .chain-step-3, .chain-editor .chain-step-4 { grid-column: 1 / -1; }
.chain-editor-how .chain-steps { margin-top: .5rem; }
.chain-editor .chain-footer { position: sticky; bottom: -1.5rem; background: var(--bg2); padding-block: .75rem; }
.chain-check.ceres-btn--choice > input[type="checkbox"] { position: static; opacity: 1; inline-size: 1rem; block-size: 1rem; }
@media (max-width: 1279px) {
  .is-plan-mode .combine-dialog-body { padding: 1rem; }
  .chain-editor .chain-steps { grid-template-columns: 1fr; }
  .chain-editor .chain-step-3, .chain-editor .chain-step-4 { grid-column: auto; }
  .chain-editor .chain-footer { bottom: -1rem; }
}
.chain-step-icon, .chain-step-number svg { display: inline-grid; place-items: center; width: 1.25rem; height: 1.25rem; }
.chain-step-icon :is(svg, img, .ceres-btn__icon) { width: 1.25rem; height: 1.25rem; object-fit: contain; }
.combine-dialog-overlay.is-plan-mode { z-index: 1350; align-items: center; padding: 1rem; }
.is-plan-mode .combine-dialog-panel { width: min(64rem, 96vw); min-width: 0; max-height: calc(100dvh - 2rem); }
.chain-card { font-size: .9rem; }
.chain-step p { margin: .25rem 0; }
.chain-plans > h3 { margin-block: .5rem; }
.chain-step-icon .hdr-ic { fill: var(--heading); }
.chain-step-icon .hdr-ic .d { fill: none; stroke: var(--bg2); }
.chain-step-icon .hdr-ic .h { fill: none; stroke: var(--heading); }
.chain-confirm-overlay { z-index: 1350; }
.candidate-grid-tools:has([data-upgrade-action]) .picker-combine { width: auto; flex: 1 1 auto; }

/* IR-8593 R3: expected cards use the existing Special gold and staged pulse. */
.slot-tile.is-stage-planned { --stage-tone: var(--stage-planned); outline: 2px solid var(--stage-tone); outline-offset: -2px; animation: stage-tile-outline 1.6s ease-in-out infinite; }
.slot-tile.is-stage-planned::after { content: ''; position: absolute; inset: 0; border-radius: inherit; z-index: 2; box-shadow: inset 0 0 calc(18 * var(--eos-effect-unit)) calc(5 * var(--eos-effect-unit)) var(--stage-tone), inset 0 0 0 calc(3 * var(--eos-effect-unit)) color-mix(in srgb, var(--stage-tone) 70%, transparent); opacity: .15; pointer-events: none; animation: stage-tile-glow 1.6s ease-in-out infinite; }
.candidate-card.is-planned { outline: 2px solid var(--stage-planned); outline-offset: -2px; }
.candidate-tag.is-planned, .planned-return-chip { color: var(--stage-planned); border: 1px solid var(--stage-planned); background: color-mix(in srgb, var(--stage-planned) 14%, var(--bg2)); }
.planned-card-copy { display: flex; flex-direction: column; align-items: center; gap: .25rem; font-size: .7rem; }
.planned-return, .ch-state.is-planned, .ch-status.is-planned, .ch-mrow.is-planned .ch-mrow-what { color: var(--stage-planned); }
.planned-return-chip { position: absolute; top: .25rem; left: .25rem; right: .25rem; z-index: 3; display: flex; align-items: center; gap: .25rem; border-radius: var(--radius); padding: .25rem; font-size: .7rem; line-height: 1.2; pointer-events: none; }
.planned-return-chip svg { width: 1rem; height: 1rem; flex-shrink: 0; }
.slot-tile.is-stage-planned .slot-flag-cooldown { display: none; }
.candidate-readiness-overlay.is-warn { color: var(--warn); border-color: var(--warn); background: color-mix(in srgb, var(--warn) 46%, transparent); }
.candidate-readiness-overlay.is-warn .candidate-readiness-badge { color: var(--warn); }
@media (prefers-reduced-motion: reduce) {
  .slot-tile.is-stage-planned, .slot-tile.is-stage-planned::after { animation: none; }
  .slot-tile.is-stage-planned::after { opacity: .5; }
}

/* IR-8593: keep both status lines inside the existing Changes cell. */
.changes-panel .ch-cells .ch-state.is-planned { border-color: var(--stage-planned); }
.changes-panel .ch-cells .ch-state.is-planned b { font-size: .7rem; white-space: normal; }
html.ceres-lw-le-1919.ceres-lw-le-1279 #changes-overlay .ch-mrow.is-planned { --ch-tone: var(--stage-planned); }
.candidate-row.is-planned .candidate-art { position: relative; }
.changes-panel .ch-cells .ch-state.is-planned b { letter-spacing: normal; font-weight: 600; }
.candidate-readiness-overlay.is-warn > span { background: var(--bg2); border-radius: var(--radius); padding: .25rem; }

/* IR-8593 R4: Review is desktop-only until IR-8596. */
html.ceres-lw-le-1279 .chain-desktop { display: none; }
/* IR-9106 (owner: "staged and waiting workers are visible in the staged tiles with waiting badges with timer"): a staged
   card that waits carries a Waiting badge with its countdown. IR-9108 (owner): full tile width, less rounded, centred on the tile. Gold = waiting (the
   Changes "Waiting" colour); orange on a move from another plot. Replaces the bare ⏳ flag on that tile. */
.slot-wait-badge { position: absolute; top: 50%; left: 0; right: 0; transform: none; z-index: 4; display: flex; flex-wrap: nowrap; align-items: center; justify-content: center; column-gap: .25rem; border-radius: 2px; border: 1px solid var(--gold); padding: .2rem .25rem; background: color-mix(in srgb, var(--gold) 14%, var(--bg2)); color: var(--gold); font-size: .65rem; font-weight: 600; line-height: 1.25; white-space: nowrap; pointer-events: auto; }
.slot-tile .slot-wait-badge .slot-wait-time.is-compact-hidden { display: none; }
.slot-tile .slot-wait-badge .slot-wait-clock { display: none; }
.slot-tile .slot-wait-badge.is-timer-compact .slot-wait-clock { display: inline; }
.slot-wait-badge.is-elsewhere { border-color: var(--warn); color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, var(--bg2)); }
.slot-wait-time { font-variant-numeric: tabular-nums; }
.slot-tile .slot-wait-badge :is(.slot-wait-label, .slot-wait-time) { flex: none; }
/* IR-9107: the red waiting box under a Changes tile holds the spinner and the live countdown only. */
.changes-panel .ch-cells .ch-state.ch-wait-timer { display: flex; align-items: center; justify-content: center; gap: .3rem; }
.changes-panel .ch-cells .ch-state.ch-wait-timer b { font-variant-numeric: tabular-nums; }
.changes-panel .ch-cells .ch-state.ch-wait-timer .spinner { flex-shrink: 0; width: 1.4em; height: 1.4em; }
/* IR-9110: a planned card's centred badge -- its level after the combine and the time until the combine can run. */
.slot-wait-badge.is-planned { border-color: var(--stage-planned); color: var(--stage-planned); background: color-mix(in srgb, var(--stage-planned) 14%, var(--bg2)); }
.slot-wait-badge svg { width: 1em; height: 1em; flex-shrink: 0; }
/* IR-9109: the combine menu's stage-back box and the planned picker card's original plot. */
.chain-stage-back { display: flex; align-items: center; gap: .5rem; margin-block: .5rem; text-align: start; white-space: normal; }
.chain-stage-back > input[type="checkbox"] { position: static; opacity: 1; inline-size: 1rem; block-size: 1rem; margin: 0; accent-color: var(--accent); }
.planned-origin { display: inline-flex; align-items: center; gap: .3rem; }
/* IR-9306 D2: disclosures stay in step order as one full-width timeline at every viewport. */
.chain-card .chain-steps { position: relative; grid-template-columns: minmax(0, 1fr); }
.chain-card .chain-steps::before { content: ''; position: absolute; z-index: 1; inset-block: .25rem .25rem; inset-inline-start: 3.25rem; border-inline-start: 2px solid var(--border); pointer-events: none; }
.chain-card .chain-step { position: relative; grid-column: 1; border-inline-start: 0; }
.chain-card .chain-step-details > summary, .chain-card .chain-step-content { position: relative; z-index: 2; }
.chain-card .chain-step-content { padding-inline-start: 3.5rem; }
/* IR-9116: the planned badge is two rows -- "Lv N after combine" over the time -- with no icon. */
.slot-wait-badge.is-planned { flex-direction: column; white-space: normal; text-align: center; }
/* IR-9118 / IR-9120 (owner: "dock it in the top right corner, same row as the title. make sure the text line breaks gracefully
   into a second row"): in step 4 the planned tile is docked top right beside the title; the text runs under the title and
   wraps in the column left of the tile. */
.chain-step-4:has(.chain-place) .chain-step-content { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: .75rem; align-items: start; }
.chain-step-4 .chain-place { display: contents; }
.chain-step-4 .chain-place-tile { grid-column: 2; grid-row: 1; align-self: start; margin: 0; padding: 0; overflow: visible; }
.chain-step-4 .chain-place-tile .ch-tiles { margin: 0; padding: 0; }
.chain-step-4 .chain-place > p { grid-column: 1; grid-row: 1; margin: 0; min-width: 0; overflow-wrap: anywhere; }
.slot-wait-badge.is-planned .slot-wait-label { white-space: nowrap; }
.fp-slot .slot-wait-badge.is-planned { font-size: .6rem; padding-inline: .1rem; letter-spacing: -.01em; }
/* IR-9121: the Cooldown step's timer -- spinner and the live time, large, in the step's colour; the ready time under it. */
.chain-timer { display: flex; align-items: center; gap: .5rem; margin: .1rem 0 .15rem; color: var(--warn); }
.chain-timer .spinner { width: 1.9rem; height: 1.9rem; flex: none; }
.chain-timer b { font-size: 1.5rem; font-weight: 800; line-height: 1.1; font-variant-numeric: tabular-nums; }
.chain-timer-when { margin: 0 0 .4rem; font-size: .8rem; color: var(--body); }

/* IR-9125 / IR-9126: a started transaction is colour-coded -- warning while Ceres waits for the wallet or the game, green once confirmed. */
.dec-receipt.is-waiting, .wallet-card:has(.wallet-wait) { border-color: var(--warn); background: linear-gradient(135deg, color-mix(in srgb, var(--warn) 10%, transparent), transparent 65%), var(--bg); }
.dec-receipt.is-waiting h3, .dec-receipt.is-waiting .dec-status { color: var(--warn); }
.wallet-swap-note.is-warn { color: var(--warn); }
.wallet-wait { display: flex; align-items: center; gap: .6rem; margin: .75rem 0 0; padding: .6rem .8rem; border: 1px solid var(--warn); border-radius: 4px; color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); font-weight: 600; }
.wallet-wait .wallet-logo { inline-size: 1.4rem; block-size: 1.4rem; flex: none; }
.wallet-wait .spinner { flex: none; }

/* IR-9128 (owner: "each inner box is using non-transparent backgrounds. Replace for a brighter glass design than the main wallet glass
   background"): inside the wallet drawer every box is a light, see-through glass layer over the panel's own glass -- no opaque fill and
   no second backdrop-filter (a nested filter repaints the panel). Status tints (waiting, confirmed, failed) sit on the same glass. */
.dec-drawer-content { --wallet-glass: linear-gradient(160deg, color-mix(in srgb, var(--text) 14%, transparent), color-mix(in srgb, var(--text) 6%, transparent)); --wallet-glass-edge: color-mix(in srgb, var(--text) 20%, transparent); }
.dec-drawer-content :is(.wallet-swap-box, .dec-amount input, .wallet-swap-amount) { background: color-mix(in srgb, var(--text) 6%, transparent); border-color: var(--wallet-glass-edge); }
.dec-drawer-content .dec-receipt.is-waiting:not(.is-confirmed), .dec-drawer-content .wallet-card:has(.wallet-wait):not(.is-confirmed) { border-color: var(--warn); background: linear-gradient(135deg, color-mix(in srgb, var(--warn) 16%, transparent), transparent 70%), var(--wallet-glass); }
.dec-drawer-content :is(.wallet-swap, .dec-receipt).is-confirmed { background: linear-gradient(135deg, color-mix(in srgb, var(--ok) 16%, transparent), transparent 70%), var(--wallet-glass); }
.dec-drawer-content .dec-receipt.is-failed { background: linear-gradient(135deg, color-mix(in srgb, var(--danger) 14%, transparent), transparent 70%), var(--wallet-glass); }

/* IR-9131: the SPS claim cooldown -- a warning-coloured live timer until the game allows the next claim. */
.wallet-claim-wait { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; margin: .5rem 0 .75rem; color: var(--text); }
.wallet-claim-wait b { font-size: 1.25rem; font-variant-numeric: tabular-nums; color: var(--primary); }
.wallet-claim-wait small { color: var(--text-dim); }
/* IR-9138 (owner): no warning colour while the claim is on cooldown; the warning colour marks a claim that is possible now. */
.dec-drawer-content .wallet-card.is-claim-ready, .dec-drawer-content .wallet-card.is-claim-ready:not(.is-confirmed):not(.is-failed) { border-color: var(--warn); border-block-start-color: var(--warn); }
.dec-drawer-content .wallet-card.is-claim-ready h3 { color: var(--warn); }
.wallet-card .wallet-claim-actions { margin-block: .75rem 0; }

/* IR-9140: a swap the game or the pool has not answered within an hour fails loudly. */
.dec-drawer-content :is(.wallet-swap, .dec-receipt).is-failed { border-color: var(--danger); }
.dec-drawer-content :is(.wallet-swap, .dec-receipt).is-failed h3 { color: var(--danger); }
.wallet-swap-note.is-danger { color: var(--danger); font-weight: 600; }
.wallet-swap-note .spinner { vertical-align: middle; margin-inline-end: .35rem; }

/* IR-9198 (owner: "use the landing page artwork for all pages backgrounds. remove the otating through the terrains"): every page
   paints the landing page's terrain wallpaper (S-16790's three sizes) instead of a random terrain per load. */
html { --hdr-scene: url('/static/assets/ui/ceres-terrain-wallpaper-4k.webp') !important; }
@media (max-width: 1000px) { html { --hdr-scene: url('/static/assets/ui/ceres-terrain-wallpaper-1000.webp') !important; } }
@media (max-width: 700px), (orientation: portrait) and (max-width: 1000px) { html { --hdr-scene: url('/static/assets/ui/ceres-terrain-wallpaper-phone.webp') !important; } }
/* IR-9198 (owner: "ALL pages, not just the board. the shop still shows the old backgrounds"): !important so a page that still
   sets a terrain inline (cards.html until its rotation script is removed) paints the wallpaper too. */

/* IR-9142 (owner-approved glass board, IR-9133 verdicts of 2026-10-14): on the desktop board the header's per-load terrain scene
   (--hdr-scene, index.html) is the artwork of the WHOLE page, fixed behind it (full-page parallax); header, filter row, head row
   and rows are glass over it. Wood and light as approved; dark and alchemical with a calmer, stronger veil ("too busy"). No
   backdrop-filter here (ui-standards Q-11 / M-4). The phone tier keeps its own design (S-16876). */
html:not(.ceres-lw-le-1279) { --glass-veil-top: 30%; --glass-veil-bottom: 72%; --glass-row: 52%; --glass-row-alt: 42%; --glass-cell: 74%; }
html:not(.ceres-lw-le-1279)[data-theme="dark"], html:not(.ceres-lw-le-1279)[data-theme="alchemical"] { --glass-veil-top: 50%; --glass-veil-bottom: 84%; --glass-row: 68%; --glass-row-alt: 60%; --glass-cell: 80%; }
/* IR-9146 (owner: "the table must not flow into the header but disappear behind it and not shine through"): the page, the sticky
   header and the sticky head row all paint the SAME viewport-fixed artwork and veil, so the art is seamless while the header and
   head row are opaque to the rows scrolling under them. */
html:not(.ceres-lw-le-1279) { --glass-art: var(--hdr-scene, url('/static/assets/ui/ceres-terrain-wallpaper-4k.webp')) center 40% / cover no-repeat fixed; --glass-veil: linear-gradient(180deg, color-mix(in srgb, var(--bg) var(--glass-veil-top), transparent) 0%, color-mix(in srgb, var(--bg) 60%, transparent) 50%, color-mix(in srgb, var(--bg) var(--glass-veil-bottom), transparent) 100%) center / 100% 100vh no-repeat fixed; }
html:not(.ceres-lw-le-1279) body:not(.start-page) { background: var(--glass-veil), var(--glass-art), var(--bg); }
html:not(.ceres-lw-le-1279) header.board-header { background: linear-gradient(90deg, color-mix(in srgb, var(--bg) 70%, transparent) 0%, color-mix(in srgb, var(--bg) 35%, transparent) 12%, transparent 22%), linear-gradient(270deg, color-mix(in srgb, var(--bg) 70%, transparent) 0%, color-mix(in srgb, var(--bg) 35%, transparent) 14%, transparent 26%), var(--glass-art), var(--bg); animation: none; border-bottom: 1px solid color-mix(in srgb, var(--text) 18%, transparent); }  /* IR-9171/IR-9172 (owner: "remove the glass from the header row entirely"; then "now the header is a non-transparent dark blue"): no glass tint at all -- the clear page artwork, still opaque to rows scrolling under it; the side fades keep the logo and buttons readable (IR-4000) */
html:not(.ceres-lw-le-1279) .controls-row { background: linear-gradient(color-mix(in srgb, var(--bg) 62%, transparent), color-mix(in srgb, var(--bg) 62%, transparent)), var(--glass-veil), var(--glass-art), var(--bg);  /* IR-9171: denser (darker) glass, still lighter than the solid header */ border-bottom-color: color-mix(in srgb, var(--text) 18%, transparent); }  /* IR-9146: sticky too, so opaque to the rows */
html:not(.ceres-lw-le-1279) main#board-main, html:not(.ceres-lw-le-1279) .table-scroll, html:not(.ceres-lw-le-1279) table.plot-table, html:not(.ceres-lw-le-1279) table.plot-table tbody { background: transparent; }
html:not(.ceres-lw-le-1279) table.plot-table th { background: var(--bg); color: var(--text); box-shadow: none; text-shadow: none; }  /* IR-9150 (owner: "Use the normal table background and table color for the header without shadow"): plain and opaque, no glass, no shadow */  /* IR-9150 (owner: "Use the normal table background and table color for the header without shadow"): plain and opaque, no glass, no shadow */
/* IR-9147 (owner: "give the table header a contrast shadow that also works in dark mode"): the head row casts a drop shadow onto the rows
   and its labels carry a text shadow, both from the theme's shadow token, so the edge reads on light art and on dark glass alike. */
/* (no rule on thead: a positioned thead breaks the sticky head cells -- IR-9148) */
html:not(.ceres-lw-le-1279) #plot-tbody tr.plot-row { background-color: color-mix(in srgb, var(--bg) var(--glass-row), transparent); }
html:not(.ceres-lw-le-1279) #plot-tbody tr.plot-row:nth-child(even) { background-color: color-mix(in srgb, var(--bg) var(--glass-row-alt), transparent); }
/* IR-9157 (owner: "the row hover should be darker glass than the table background"): the hovered row is a denser, shadow-tinted glass. */
html:not(.ceres-lw-le-1279) #plot-tbody tr.plot-row:hover { background-color: color-mix(in srgb, color-mix(in srgb, var(--bg) 45%, var(--shadow)) 94%, transparent); }
html:not(.ceres-lw-le-1279) #plot-tbody tr.plot-row > td { border-bottom: 1px solid color-mix(in srgb, var(--text) 10%, transparent); }
html:not(.ceres-lw-le-1279) td.plot-cell.territory-pristine-northwest { background: color-mix(in srgb, var(--territory-pristine-northwest) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.plot-cell.territory-broken-lands { background: color-mix(in srgb, var(--territory-broken-lands) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.plot-cell.territory-land-of-prosperity { background: color-mix(in srgb, var(--territory-land-of-prosperity) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.plot-cell.territory-central-fire { background: color-mix(in srgb, var(--territory-central-fire) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.plot-cell.territory-wild-northeast { background: color-mix(in srgb, var(--territory-wild-northeast) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.plot-cell.territory-great-lowlands { background: color-mix(in srgb, var(--territory-great-lowlands) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.plot-cell.territory-shimmering-coasts { background: color-mix(in srgb, var(--territory-shimmering-coasts) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-river { background: color-mix(in srgb, var(--terrain-river) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-lake { background: color-mix(in srgb, var(--terrain-lake) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-hills { background: color-mix(in srgb, var(--terrain-hills) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-forest { background: color-mix(in srgb, var(--terrain-forest) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-mountain { background: color-mix(in srgb, var(--terrain-mountain) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-jungle { background: color-mix(in srgb, var(--terrain-jungle) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-caldera { background: color-mix(in srgb, var(--terrain-caldera) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-plains { background: color-mix(in srgb, var(--terrain-plains) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-swamp { background: color-mix(in srgb, var(--terrain-swamp) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-canyon { background: color-mix(in srgb, var(--terrain-canyon) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-desert { background: color-mix(in srgb, var(--terrain-desert) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-tundra { background: color-mix(in srgb, var(--terrain-tundra) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-badlands { background: color-mix(in srgb, var(--terrain-badlands) var(--glass-cell), transparent); }
html:not(.ceres-lw-le-1279) td.terrain-cell.terrain-bog { background: color-mix(in srgb, var(--terrain-bog) var(--glass-cell), transparent); }

/* IR-9152: with the plain head row (IR-9150) the sort chevron must follow the head text colour, not the old --bg paint. */
html:not(.ceres-lw-le-1279) table.plot-table th[data-key] .sort-chevron, html:not(.ceres-lw-le-1279) table.plot-table .table-sort-control .sort-chevron { background: currentColor; }


/* IR-9214 (incident: same version, different code): the "+local.<hash>" marker shown only when the serving checkout's
   shipped files differ from the release. Approved amber/warn tone. Owner (2026-10-15): "place correctly in header right
   of logo" -- absolutely placed just right of the logo, vertically centred on it; the logo box reserves its width with
   a margin, so the header controls never sit under it and neither the header height nor the logo moves. Below 1280px
   it shrinks to a compact "Local" chip (full text in its tooltip / aria-label). Board and Cards page. */
.board-logo:has(> .ceres-local-marker) { margin-inline-end: 25rem; }
.board-logo > .ceres-local-marker { position: absolute; left: calc(100% + 0.6rem); top: 50%; transform: translateY(-50%); z-index: 2; display: inline-flex; align-items: center; gap: 0.3rem; white-space: nowrap; padding: 0.16rem 0.5rem; border: 1px solid var(--warn); border-radius: 999px; color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, var(--bg2)); font-size: 0.7rem; font-weight: 700; line-height: 1.2; letter-spacing: 0.01em; }
.ceres-local-marker-id { font-variant-numeric: tabular-nums; opacity: 0.85; }
.ceres-local-marker-short { display: none; }
@media (max-width: 1279px) {
  /* IR-9224 (owner chose "Dot on the logo"): on narrow screens the marker is a small amber dot on the logo's top-right
     corner; the full text stays in its tooltip / aria-label and the player field keeps its full width. */
  .board-logo:has(> .ceres-local-marker) { margin-inline-end: 0; }
  .board-logo > .ceres-local-marker { left: auto; right: 0.1rem; top: 0.45rem; transform: none; inline-size: 0.7rem; block-size: 0.7rem; padding: 0; gap: 0; border-width: 2px; background: var(--warn); box-shadow: 0 0 0 2px color-mix(in srgb, var(--bg) 70%, transparent); font-size: 0; }
  .ceres-local-marker-id, .ceres-local-marker-text, .ceres-local-marker-short { display: none; }
}
