/* Reconciliation — deep-glass surfaces, state-driven hue, fixed layout.
 *
 * Two rules govern everything here:
 *
 * 1. STATE IS SHOWN AS HUE, NOT AS A PILL. Each panel carries a broad gradient
 *    bloom rising from its bottom centre — green when healthy, amber degraded,
 *    red failing. There are no coloured left borders anywhere on this page.
 *
 * 2. THE LAYOUT IS FIXED. Every panel reserves its final height before content
 *    arrives, so an htmx swap changes content but never geometry. Skeletons
 *    occupy the exact space the real rows will, numbers sit in tabular-figure
 *    slots, and an empty section keeps its height rather than collapsing.
 */

/* --- Glass foundation ------------------------------------------------------ */

.recon {
  /* Tuned per theme below. Declared here so every glass surface reads one set. */
  --glass-bg: rgba(255, 255, 255, 0.42);
  --glass-border: rgba(255, 255, 255, 0.75);
  --glass-blur: 20px;
  /* Tight contact shadow + wide ambient + hairline ring, rather than one diffuse
     blur — a single soft shadow doesn't lift the panel off a light ground. */
  --glass-shadow:
    0 0 0 1px rgba(20, 21, 31, 0.035),
    0 1px 2px rgba(20, 21, 31, 0.05),
    0 12px 28px -8px rgba(20, 21, 31, 0.1),
    0 32px 64px -24px rgba(20, 21, 31, 0.08);
  --glass-inner: inset 0 1px 0 rgba(255, 255, 255, 0.7);
  /* A specular must read as light, so it stays white-ish in both themes and
     varies only by alpha. Deriving it from --surface made it invisible in dark
     (surface is darker than the glass it sat on) and near-invisible in light. */
  --glass-specular: rgba(255, 255, 255, 0.55);
  /* The bloom colour; each panel overrides via a state class. */
  --bloom: var(--good);
  /* Asymmetric by necessity: a saturated colour at low alpha shifts luminance
     far more over a light ground than a dark one. Equal values gave ~25 ΔL in
     light and ~4 ΔL in dark, i.e. invisible in dark. */
  --bloom-strength: 0.16;
}

[data-theme="dark"] .recon {
  --glass-bg: rgba(30, 32, 41, 0.42);
  --glass-border: rgba(255, 255, 255, 0.09);
  --glass-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.045),
    0 2px 4px rgba(0, 0, 0, 0.4),
    0 16px 40px -12px rgba(0, 0, 0, 0.55);
  --glass-inner: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --glass-specular: rgba(255, 255, 255, 0.055);
  --bloom-strength: 0.55;
  /* Muted enough that light-mode's darker muted token isn't needed here. */
  --text-muted: #9799a8;
}

/* The bloom eats the contrast margin under small muted text (0.71rem labels sit
   well under the large-text threshold), so light mode gets a darker muted tone
   on this page only. */
.recon {
  --text-muted: #5c5e6d;
}

.recon-daybar,
.recon-account,
/* Composition sits outside .recon-groups (full width, above both columns), so
   it needs its own bottom margin rather than inheriting the columns' flex gap. */
#recon-group-composition {
  margin-bottom: 1rem;
}

/* Two independent columns, not a grid or CSS multi-column: each is its own
   explicit stack of groups from the backend (see overview.py::_LEFT_COLUMN /
   _RIGHT_COLUMN), so a column's height is whatever its own groups add up to —
   never rebalanced against the other column, and which group lands in which
   column never shifts as row counts change day to day. Composition sits above
   both, full width, since it's an inherently two-panel side-by-side comparison.
   Below the breakpoint the columns stack into one. */
.recon-groups {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
}

.recon-groups__col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Without backdrop-filter the panel is a plain translucent fill; at 0.42 that
   would be marginal for text contrast, so raise it to near-opaque. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .recon { --glass-bg: rgba(255, 255, 255, 0.92); }
  [data-theme="dark"] .recon { --glass-bg: rgba(30, 32, 41, 0.94); }
}

/* The glazed panel. `isolation` keeps the bloom's blend inside the panel, and
   the ::after highlight gives the top edge its wet, glassy catch of light. */
.glass {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border: 1px solid var(--glass-border);
  border-radius: 16px;
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)) saturate(180%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(180%);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  /* Matches the bloom's own 0.5s so a panel that gains a state ring when its
     fragment lands settles at the same rate as the bloom behind it. */
  transition: border-color 0.5s ease, box-shadow 0.5s ease;
}

/* The bloom: a wide, soft ellipse of state colour rising from bottom centre.
   Sits behind content (z-index -1) so text never fights it. */
/* A wide, shallow lens concentrated at the bottom centre. The earlier near-circular
   wash tinted the whole lower half, which read as "coloured card" rather than a
   bloom of light. */
.glass::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -45%;
  z-index: -1;
  width: 130%;
  height: 90%;
  transform: translateX(-50%);
  background: radial-gradient(
    ellipse 60% 100% at 50% 100%,
    color-mix(in srgb, var(--bloom) calc(var(--bloom-strength) * 100%), transparent) 0%,
    color-mix(in srgb, var(--bloom) calc(var(--bloom-strength) * 32%), transparent) 30%,
    transparent 62%
  );
  pointer-events: none;
  transition: background 0.5s ease;
}

/* In dark mode the bloom must add light rather than tint, which is what the
   panel's `isolation: isolate` exists to contain. */
[data-theme="dark"] .glass::before {
  mix-blend-mode: screen;
}

/* Specular top edge — the "glazed" catch of light. */
.glass::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 38%;
  z-index: -1;
  background: linear-gradient(to bottom, var(--glass-specular), transparent 90%);
  pointer-events: none;
}

/* State drives hue only. */
.is-good { --bloom: var(--good); }
.is-warn { --bloom: var(--warn); }
.is-bad { --bloom: var(--bad); }
.is-idle { --bloom: var(--text-muted); --bloom-strength: 0.1; }

/* Request population, when both systems agree on the day's total.

   The panel is rendered by group_panel with a hardcoded .is-idle and its body
   is swapped by htmx, so the verdict arrives on the fragment INSIDE it — hence
   :has() rather than a state class on the panel. It restates the same tokens
   .is-good sets, overriding the .is-idle already on the element, and adds a
   ring so the agreement reads at a glance from across the page rather than
   only as a hue shift at the panel's foot. */
.recon-group:has(.recon-comp--match) {
  --bloom: var(--good);
  --bloom-strength: 0.22;
  border-color: color-mix(in srgb, var(--good) 45%, var(--glass-border));
  box-shadow:
    var(--glass-shadow),
    var(--glass-inner),
    0 0 0 1px color-mix(in srgb, var(--good) 35%, transparent),
    0 0 22px -4px color-mix(in srgb, var(--good) 30%, transparent);
}

[data-theme="dark"] .recon-group:has(.recon-comp--match) {
  --bloom-strength: 0.6;
}

/* --- Day bar --------------------------------------------------------------- */

.recon-daybar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  min-height: 62px;
  padding: 0.75rem 1rem;
}

.recon-daybar__nav {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

.recon-daystep {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 1px solid var(--glass-border);
  border-radius: 9px;
  background: color-mix(in srgb, var(--surface) 40%, transparent);
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.recon-daystep:hover {
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent);
}

.recon-daybar__current {
  display: flex;
  flex-direction: column;
  gap: 0.12rem;
  min-width: 11rem;
  padding: 0 0.4rem;
}

.recon-daybar__date {
  font-size: 1rem;
  font-weight: 660;
  letter-spacing: -0.015em;
}

.recon-daybar__note {
  font-size: 0.71rem;
  color: var(--text-muted);
}

.recon-daybar__form {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-left: auto;
}

/* Sized to match .btn beside it and the toolbar date fields elsewhere — it was
   noticeably shorter than its own Go button. The calendar indicator itself is
   styled app-wide in controls.css. */
.recon-daybar__form input[type="date"] {
  flex: 0 0 auto;
  width: 11.5rem;
  min-width: 0;
  height: 2.35rem;
  padding: 0.4rem 0.65rem;
  border: 1px solid var(--glass-border);
  border-radius: 9px;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  color: var(--text);
  font-size: 0.85rem;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.recon-daybar__form input[type="date"]:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface);
}

/* .btn is a shared auth-form style (width: 100%) — undo that here so it sits
   inline at its natural size instead of stretching across the day bar. */
.recon-daybar__form .btn {
  width: auto;
  padding: 0.4rem 0.9rem;
  font-size: 0.82rem;
}

/* --- Headline account ------------------------------------------------------ */

/* Fixed height: the verdict, figures, bar and reading line all have reserved
   space, so the skeleton and the loaded state are exactly the same size. */
.recon-account {
  min-height: 216px;
  padding: 1.1rem 1.25rem;
}

.recon-account__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}

.recon-account__verdict {
  font-size: 1.05rem;
  font-weight: 680;
  letter-spacing: -0.015em;
}

.recon-account__stamp {
  font-size: 0.74rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Pinned to a fixed column count rather than auto-fit: auto-fit silently
   dropped to fewer columns below ~649px, which wrapped the row and grew the
   panel by ~63px. Values shrink instead, and the narrow breakpoint below
   switches deliberately. Default is 4 (coverage's panel); the summary panel
   carries 5 figures (it adds "Rolled") and gets its own column count via
   .recon-account__figures--5. */
.recon-account__figures {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.75rem 1rem;
  margin-bottom: 1.05rem;
}

.recon-account__figures--5 {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

.recon-figure {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  /* Reserved so the row never changes height between skeleton and data. */
  min-height: 46px;
}

.recon-figure__value {
  font-size: 1.6rem;
  font-weight: 690;
  line-height: 1;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
}

.recon-figure__value--bad { color: var(--bad); }
.recon-figure__value--good { color: var(--good); }


.recon-figure__label {
  font-size: 0.71rem;
  line-height: 1.3;
  color: var(--text-muted);
}

/* A figure that carries a title= explanation. `help` plus the dotted label is
   the discovery cue — without one, a native tooltip is invisible until someone
   happens to rest the pointer on it. The underline sits on the label rather
   than the value so it never competes with the state colour.

   MUST stay below .recon-figure__label: that rule sets `color` at the same
   specificity, so a hover colour declared above it silently loses. */
.recon-figure--explained {
  cursor: help;
}

.recon-figure--explained .recon-figure__label {
  text-decoration: underline dotted color-mix(in srgb, var(--text) 35%, transparent);
  text-underline-offset: 3px;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}

.recon-figure--explained:hover .recon-figure__label {
  color: var(--text);
  text-decoration-color: color-mix(in srgb, var(--text) 60%, transparent);
}

/* The day's account bar: each term of
   outcome = delivered + rolled + returned + pending
   drawn to scale, with any shortfall hatched so a gap reads as an absence. */
.recon-bar {
  display: flex;
  height: 10px;
  border-radius: 999px;
  overflow: hidden;
  background: color-mix(in srgb, var(--text) 9%, transparent);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12);
}

.recon-bar__seg {
  height: 100%;
  transition: width 0.4s ease;
}

/* OUTCOME palette. The hue carries the meaning, so it has to match what the
   state actually IS — an earlier version painted rolled drops with --bad, which
   is what made a normal day (~30% rolled) read as a catastrophe.

   delivered  green   — completed
   rolled     blue    — attempted, rolls to another day; STILL LIVE, so it is
                        deliberately NOT a warning or error hue. Neutral-
                        informational is the honest reading.
   returned   red     — the genuinely terminal failure, and the only bad one
   pending    amber   — in flight, no outcome yet
   unbatched  red     — never batched, so never attempted. Shares the bad hue
                        with returned because it is a real failure of dispatch,
                        but is separated from it by a dotted texture below. */
.recon-bar__seg--delivered { background: linear-gradient(180deg, color-mix(in srgb, var(--good) 82%, white), var(--good)); }
.recon-bar__seg--rolled { background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 78%, white), var(--accent)); }
.recon-bar__seg--returned { background: linear-gradient(180deg, color-mix(in srgb, var(--bad) 70%, white), var(--bad)); }
.recon-bar__seg--pending { background: linear-gradient(180deg, color-mix(in srgb, var(--warn) 80%, white), var(--warn)); }
.recon-bar__seg--unbatched { background: linear-gradient(180deg, color-mix(in srgb, var(--bad) 55%, white), color-mix(in srgb, var(--bad) 85%, black)); }

.recon-bar__seg--unaccounted {
  background: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--text-muted) 70%, transparent),
    color-mix(in srgb, var(--text-muted) 70%, transparent) 3px,
    transparent 3px,
    transparent 6px
  );
}

/* Two rows reserved unconditionally: the number of key items varies by day
   (each is conditional on a non-zero value), so a single-row reservation made
   the panel a different height on different days at the same width.
   overflow:hidden guarantees a third row can never appear. */
.recon-key {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 0.85rem;
  margin-top: 0.55rem;
  min-height: 49.6px;
  overflow: hidden;
}

.recon-key__item {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.73rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.recon-key__swatch {
  width: 14px;
  height: 9px;
  border-radius: 3px;
  flex: none;
}

/* 3em is exactly two lines at line-height 1.5 — the previous 2.5em reserved only
   1.67 lines. Clamped as well as reserved, because the reading concatenates up to
   four clauses and ran to four lines on precisely the worst days. */
.recon-account__reading {
  margin: 0.85rem 0 0;
  font-size: 0.83rem;
  line-height: 1.5;
  color: var(--text-muted);
  min-height: 3em;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.recon-account__reading--error {
  color: var(--bad);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.76rem;
  -webkit-line-clamp: 3;
}

/* --- Groups ----------------------------------------------------------------- */

/* Every group is a fixed, always-open panel — never a collapsible section — so
   the header is just a static row, same height regardless of load state. */
.recon-group__head {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  width: 100%;
  min-height: 48px;
  padding: 0.55rem 0.9rem;
  text-align: left;
}

.recon-group__title {
  font-size: 0.86rem;
  font-weight: 660;
  letter-spacing: -0.01em;
}

/* Sits in the head's flex gap, so no margin of its own. Muted and slightly
   optically lowered so it reads as a marker beside the title, not a second
   emphasis competing with it. */
.recon-group__icon {
  flex: none;
  color: var(--text-muted);
  transform: translateY(0.5px);
}

.recon-group__figures {
  display: flex;
  align-items: baseline;
  gap: 1.1rem;
  margin-left: auto;
}

.recon-group__stat {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  font-variant-numeric: tabular-nums;
}

.recon-group__stat-value {
  font-size: 1.05rem;
  font-weight: 680;
  letter-spacing: -0.02em;
}

.recon-group__stat-label {
  font-size: 0.71rem;
  color: var(--text-muted);
}

.recon-group__body {
  padding: 0 0.9rem 0.85rem;
}

/* --- Rows ------------------------------------------------------------------ */

/* Fixed row height, so a list of skeletons is exactly as tall as the real list
   that replaces it. */
.recon-row {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto;
  align-items: center;
  gap: 1rem;
  min-height: 52px;
  padding: 0.6rem 0.75rem;
  border-radius: 11px;
  border: 1px solid transparent;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.recon-row + .recon-row {
  margin-top: 0.3rem;
}

.recon-row:hover {
  border-color: var(--glass-border);
  background: color-mix(in srgb, var(--surface) 40%, transparent);
}

.recon-row__primary {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.recon-row__code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recon-row__sub {
  font-size: 0.72rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recon-row__meta {
  font-size: 0.75rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* State marker: a small hue dot, not a pill.
   Colour alone is not sufficient — --good and --bad are near-identical under
   deuteranopia — so each state also carries a distinct SHAPE core, and every
   marker is paired with visually-hidden text in the template. */
.recon-dot {
  position: relative;
  width: 9px;
  height: 9px;
  border-radius: 999px;
  flex: none;
  background: var(--bloom);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--bloom) 18%, transparent);
}

/* Good keeps the plain disc. Warn gets a square core, bad a diamond. */
.is-warn .recon-dot::after,
.is-bad .recon-dot::after {
  content: "";
  position: absolute;
  inset: 2px;
  background: var(--glass-bg);
}

.is-bad .recon-dot::after {
  transform: rotate(45deg);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Skeleton primitives (.skel*) now live in skeleton.css, shared by every page. */

@media (prefers-reduced-motion: reduce) {
  .recon-bar__seg,
  .glass::before { transition: none; }
}

@media (max-width: 720px) {
  .recon-row {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .recon-row__meta {
    display: none;
  }

  .recon-group__figures {
    margin-left: 0;
  }
}

/* --- Composition (two systems, two partitions) ------------------------------ */

.recon-comp {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.4rem;
}

.recon-comp__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
}

.recon-comp__system {
  font-size: 0.85rem;
  font-weight: 650;
}

.recon-comp__total {
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

/* The column the partition is taken over — shown so the reader knows what they
   are looking at rather than trusting the labels. */
.recon-comp__dimension {
  display: block;
  margin-bottom: 0.7rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.68rem;
  color: var(--text-muted);
}

.recon-pot {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr);
  align-items: baseline;
  gap: 0.15rem 0.6rem;
  min-height: 40px;
  padding: 0.3rem 0;
}

.recon-pot__count {
  font-size: 1rem;
  font-weight: 680;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.recon-pot__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.recon-pot__label {
  font-size: 0.82rem;
  font-weight: 550;
}

.recon-pot__note {
  font-size: 0.71rem;
  line-height: 1.35;
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  overflow: hidden;
}

.recon-pot__rail {
  grid-column: 2;
  height: 3px;
  margin-top: 0.3rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
}

.recon-pot__fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 70%, transparent);
}

.recon-comp__empty {
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* A pot is a link to its own requests. Styled to stay a row, not become a
   button — the affordance is the hover lift and the count/label turning to the
   accent, matching how .recon-row behaves on the check panels. */
.recon-pot--link {
  color: inherit;
  text-decoration: none;
  border-radius: 8px;
  padding-inline: 0.4rem;
  margin-inline: -0.4rem;
  transition: background-color 120ms ease, color 120ms ease;
}

.recon-pot--link:hover,
.recon-pot--link:focus-visible {
  background: color-mix(in srgb, var(--text) 5%, transparent);
}

.recon-pot--link:hover .recon-pot__label,
.recon-pot--link:focus-visible .recon-pot__label,
.recon-pot--link:hover .recon-pot__count,
.recon-pot--link:focus-visible .recon-pot__count {
  color: var(--accent);
}


/* --- Check rows ------------------------------------------------------------ */

.recon-check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.6rem;
  min-height: 46px;
  padding: 0.45rem 0.3rem;
}

.recon-check + .recon-check {
  border-top: 1px solid color-mix(in srgb, var(--text) 7%, transparent);
}

.recon-check__marker {
  display: flex;
  align-items: center;
}

.recon-check__body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.recon-check__name {
  font-size: 0.85rem;
  font-weight: 620;
}

/* Two lines reserved and clamped — reasons quote live figures and vary in length
   between days, which would otherwise change the row height. */
.recon-check__reason {
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.recon-check__reason--error {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  color: var(--bad);
}

.recon-check__figure {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.recon-check__count {
  font-size: 1.2rem;
  font-weight: 690;
  letter-spacing: -0.02em;
  color: var(--bloom);
}

.recon-check__of {
  font-size: 0.78rem;
  color: var(--text-muted);
}

.recon-check__link {
  font-size: 0.78rem;
  font-weight: 560;
  color: var(--accent);
  white-space: nowrap;
  text-decoration: none;
}

.recon-check__link:hover {
  text-decoration: underline;
}

/* The hatch needs a ground to read against; the bar has one, a bare swatch does
   not. background-color must follow the shorthand, which clears it. */
.recon-key__swatch.recon-bar__seg--unaccounted {
  background-color: color-mix(in srgb, var(--text) 9%, transparent);
  background-size: 8.49px 8.49px;
}

/* --unbatched shares the bad hue with --returned (both are real dispatch
   failures), so it is separated by a dot texture rather than hue — the same
   never-colour-alone rule the pending hatch and unaccounted hatch follow. */
.recon-bar__seg--unbatched {
  background-image: radial-gradient(
    rgba(0, 0, 0, 0.34) 1px,
    transparent 1.2px
  );
  background-size: 4px 4px;
}

/* --returned (red) and --pending (amber) sit adjacent in the bar and converge
   under protanopia/deuteranopia, so pending is separated by texture rather than
   hue alone — same reasoning the unaccounted hatch uses. */
.recon-bar__seg--pending {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(0, 0, 0, 0.28),
    rgba(0, 0, 0, 0.28) 2px,
    transparent 2px,
    transparent 4px
  );
}

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

@media (max-width: 900px) {
  .recon-groups {
    flex-direction: column;
  }
}

@media (max-width: 720px) {
  .recon-account__figures {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .recon-account {
    min-height: 320px;
  }
}

/* --- Hub coverage page ------------------------------------------------------ */

/* The filters sit in their own card, loaded with the day's figures, because the
   hub dropdown's options are query results. The rows card below loads
   separately, so this carries its own padding rather than borrowing the rows
   card's. */
.recon-group--filters {
  padding: 1rem 1.5rem;
  margin-bottom: 1rem;
}

.coverage-filters {
  display: flex;
  align-items: flex-end;
  gap: 1rem;
  flex-wrap: wrap;
}

.coverage-filters__field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.75rem;
  color: var(--text-muted);
}

.coverage-filters__field select {
  min-width: 12rem;
}

.coverage-table {
  display: flex;
  flex-direction: column;
}

.coverage-row {
  grid-template-columns: minmax(0, 1.3fr) auto minmax(0, 1fr) auto;
}

.coverage-row__distance {
  font-variant-numeric: tabular-nums;
  font-size: 0.9rem;
  font-weight: 650;
  color: var(--bloom);
  white-space: nowrap;
}

.coverage-row__links {
  display: flex;
  gap: 0.75rem;
  font-size: 0.75rem;
  white-space: nowrap;
}

.coverage-row__links a {
  color: var(--accent);
  text-decoration: none;
}

.coverage-row__links a:hover {
  text-decoration: underline;
}

/* Detail page only. .main--fill makes <main> a flex column so the table area
   can take the leftover height and scroll its own body (as on requests/rolled),
   but .recon sits between the two — without passing the fill through, the table
   area's flex:1 resolves against a height-auto parent and the frame collapses
   to its content instead of reaching the bottom of the viewport. */
.main--fill > .recon {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ------------------------------------------- whole-batch failure grouping ---

   The "acknowledged but never batched" detail table groups drops by order,
   since 21 drops from one order is a single failed batch rather than 21 faults.
   Backgrounds are explicit: the dense table's :nth-child(even) zebra would
   otherwise paint over the group rows on alternating groups. */
.recon-batch-note {
  margin: 0 0 0.6rem;
  padding: 0.55rem 0.8rem;
  border-left: 3px solid var(--bad);
  border-radius: 0 4px 4px 0;
  background: color-mix(in srgb, var(--bad) 8%, transparent);
  color: var(--text);
  font-size: 0.85rem;
  line-height: 1.4;
}

.data-table--dense tbody tr.data-table__grouprow,
.data-table--dense tbody tr.data-table__grouprow:hover {
  background: color-mix(in srgb, var(--text) 6%, transparent);
}

.data-table--dense tbody tr.data-table__grouprow--fail,
.data-table--dense tbody tr.data-table__grouprow--fail:hover {
  background: color-mix(in srgb, var(--bad) 10%, transparent);
}

.data-table--dense tbody tr.data-table__grouprow td {
  padding: 0.45rem 0.75rem;
  border-top: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
}

.recon-grouprow__order {
  font-weight: 700;
  margin-right: 0.5rem;
}

.recon-grouprow__note {
  color: var(--text-muted);
  font-size: 0.78rem;
  margin-left: 0.5rem;
}

/* The group header names the order, so child rows lead with their drop instead
   of repeating it — the indent is what ties them to the header. */
.recon-drop-indent {
  padding-left: 1.75rem !important;
  color: var(--text-muted);
}


/* Batch IDs page — the day's tally, sitting directly on top of the table.
   Carries the top radius so it and .dense-table-wrap (which is squared off at
   the top) read as one surface rather than two stacked cards. */
.batch-count {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
}

.batch-count__total {
  font-size: 0.86rem;
  font-weight: 660;
  letter-spacing: -0.015em;
}

.batch-count__split {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-left: auto;
}

/* ── The day's batches panel (reconciliation/_batches.html) ─────────────────
   The markup predates any styling for it; these are the rules it always
   assumed. Built from the same tokens and surface idiom as the rest of this
   file so the panel reads as part of the page rather than a transplant. */
.recon-batch__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  list-style: none;
}

.recon-batch__head::-webkit-details-marker { display: none; }

.recon-batch__body {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

/* A 36-char UUID must not push the figures off the row. */
.recon-batch__id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.recon-batch__headline {
  font-size: 0.71rem;
  color: var(--text-muted);
}

.recon-batch__figures {
  display: flex;
  gap: 1rem;
  margin-left: auto;
}

.recon-batch__stat {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.1rem;
}

.recon-batch__stat-value {
  font-size: 0.95rem;
  font-weight: 660;
  font-variant-numeric: tabular-nums;
}

.recon-batch__stat-label {
  font-size: 0.66rem;
  color: var(--text-muted);
}

/* Rotates the chevron when the batch is open. */
.recon-batch .recon-group__chevron {
  flex: none;
  color: var(--text-muted);
  transition: transform 0.15s ease;
}

.recon-batch[open] .recon-group__chevron { transform: rotate(90deg); }

.recon-batch__detail {
  padding: 0 0.75rem 0.6rem;
  border-top: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
}

.recon-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  padding: 0.5rem 0;
}

.recon-fact {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.recon-fact__label {
  font-size: 0.66rem;
  color: var(--text-muted);
}

.recon-fact__value {
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

/* Coverage gap / malformed-id blocks below the batch list. */
.recon-gap {
  margin-top: 0.75rem;
  border: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
  border-radius: 4px;
  overflow: hidden;
}

.recon-gap__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  background: color-mix(in srgb, var(--surface) 72%, transparent);
}

.recon-gap__count {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* A figure that drills through. The whole figure is the target — value and
   label both — so it must not pick up the anchor's default colour or
   underline; only the label reacts on hover, matching .recon-figure--explained
   which sits directly above it in this file. */
.recon-figure--link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.recon-figure--link .recon-figure__label {
  transition: color 0.15s ease;
}

.recon-figure--link:hover .recon-figure__label,
.recon-figure--link:focus-visible .recon-figure__label {
  color: var(--accent);
}

/* Each batch is its own box. Without this .recon-batch had no rule at all, so
   the rows were borderless and .batch-count above them had nothing to sit
   flush against — the whole block read as shifted against the page. */
.recon-batch {
  border: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
  border-top: 0;
  background: color-mix(in srgb, var(--surface) 72%, transparent);
}

.recon-batch:last-of-type {
  border-radius: 0 0 4px 4px;
}

/* A batch that lost drops or was awarded late must not read like a healthy one.
   The tone classes previously set only --bloom, which nothing on this element
   consumed, so every row rendered identically. The left bar is the signal; the
   tint keeps it legible in both themes without shouting. */
.recon-batch.is-warn,
.recon-batch.is-bad {
  border-left: 3px solid var(--bloom);
  background: color-mix(in srgb, var(--bloom) 7%, var(--surface));
}

.recon-batch.is-bad .recon-batch__id,
.recon-batch.is-warn .recon-batch__id {
  font-weight: 620;
}

.recon-batch.is-bad .recon-batch__headline {
  color: var(--bad);
}

.recon-batch.is-warn .recon-batch__headline {
  color: var(--warn);
}

/* .batch-count sits directly on top of the first batch row, so it owns the top
   radius and lets the rows below close the box. */
.batch-count + .recon-batch {
  border-top: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
}

/* Gap blocks carry a tone. The is-* classes only set --bloom, which nothing on
   .recon-gap consumed, so a rejection rendered exactly like a soft warning. */
.recon-gap.is-warn,
.recon-gap.is-bad {
  border-left: 3px solid var(--bloom);
}

.recon-gap.is-bad .recon-gap__head {
  background: color-mix(in srgb, var(--bad) 9%, var(--surface));
}

.recon-gap.is-warn .recon-gap__head {
  background: color-mix(in srgb, var(--warn) 9%, var(--surface));
}

/* One row inside a gap block. Had no rule at all, so these rendered as an
   unstyled run-on line; the columns mirror .recon-row's proportions. */
.recon-drop {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.3fr) auto minmax(0, 0.7fr) minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
  padding: 0.4rem 0.75rem;
  font-size: 0.78rem;
  border-top: 1px solid color-mix(in srgb, var(--text) 6%, transparent);
}

.recon-drop__code { font-weight: 580; }

.recon-drop__where,
.recon-drop__window,
.recon-drop__state,
.recon-drop__payload {
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.recon-gap.is-bad .recon-drop__state {
  color: var(--bad);
  font-weight: 580;
}

@media (max-width: 900px) {
  .recon-drop {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}
