/* Design Made Dashboard - Night Desk
   Dark-committed by decision (see DESIGN.md "Why dark"), so tokens are declared
   once on :root and every colour is painted explicitly. Nothing inherits the
   host ground. */

:root {
  color-scheme: dark;

  /* Surfaces are built from the Design Made navy. #102946 is the mark's own
     background, used here as the panel colour, so the brand is the substrate
     rather than a sticker on top of a neutral grey. */
  --ground: #0a1a2e;
  --surface: #102946;
  --surface-2: #16334f;
  --surface-3: #1d3d5c;
  --rule: #22405f;
  --rule-strong: #2c5075;
  /* The rule on a raised surface, which has to hold against a lighter fill
     than --rule sits on. */
  --rule-raised: #2a4a6b;
  /* Deeper than the ground: sidebars, sticky table heads, the threshold strip.
     Inset rather than raised, so the eye reads them as part of the chrome. */
  --inset: #081627;

  --ink: #eef3f8;
  --ink-2: #a8bdd2;
  --ink-3: #94abc2;          /* 4.73:1 on the darkest ground */

  /* Brand scope: Design Made. #2396d3 is lifted straight from the mark, and it
     happens to sit inside the dark chart lightness band, so one value serves
     as both the UI accent and a plotted series. --brand-ink is the lifted step
     used when the accent has to be text (#2396d3 alone misses AA on a raised
     row). --brand-on is the label colour on a brand fill. */
  --brand: #2396d3;
  --brand-ink: #57b2e2;
  --brand-press: #3ba6de;
  --brand-on: #08182a;
  --series-1: #2396d3;

  /* Semantic, shared by both brands and never used as an accent. */
  --good: #2ec687;
  --warn: #e8a33d;
  --bad: #e0604a;            /* marks */
  --bad-ink: #f28a74;        /* the same state as text */

  /* Plan tiers are ordered, so they take a one-hue ordinal ramp, not a
     categorical set. Validated light to dark against this surface. */
  --tier-1: #54dba4;
  --tier-2: #2ec687;
  --tier-3: #1caa72;

  --sans: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --narrow: "Archivo Narrow", "Archivo", ui-sans-serif, system-ui, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* ── type ──────────────────────────────────────────────────────────────
     Archivo for interface text, IBM Plex Mono for every number, order id and
     label. Tabular figures are on everywhere, so money columns align on the
     decimal and an order id reads character by character. */
  --t-hero: 600 60px/.95 var(--sans);
  --t-hero-track: -.035em;
  --t-title: 400 26px/1.2 var(--sans);
  --t-section: 600 19px/1.3 var(--sans);
  --t-figure: 600 28px/1.15 var(--mono);
  --t-body: 400 15px/1.5 var(--sans);
  --t-meta: 400 13px/1.45 var(--sans);
  --t-label: 400 11px/1.3 var(--mono);
  --t-label-track: .16em;

  /* ── radii ─────────────────────────────────────────────────────────────
     Concentric with the hardware the app runs on. */
  --r-row: 0px;
  --r-control: 8px;
  --r-input: 10px;
  --r-button: 12px;
  --r-panel: 20px;           /* card, desktop */
  --r-card-phone: 16px;
  --r-capsule: 30px;         /* sheets and the glass bar */

  /* ── the functional layer ──────────────────────────────────────────────
     Glass belongs to bars, controls and floating panels only. Never a card:
     glass cannot sample other glass and the hierarchy collapses into a blur
     pile. */
  --glass-fill: rgba(22, 51, 79, .55);
  --glass-rim: rgba(238, 243, 248, .18);
  --glass-blur: blur(26px) saturate(1.8);
  --glass-shadow: 0 18px 44px rgba(3, 10, 20, .6),
                  inset 0 1px 0 rgba(255, 255, 255, .22);

  --rail-w: 216px;
  --top-h: 52px;

  --ease: cubic-bezier(.2, .6, .3, 1);
  /* Motion explains, never decorates. A threshold drag re-ranks in 180ms
     because the movement is the explanation; a corrected field fills in 240ms
     because you are watching it become certain. */
  --t-settle: 180ms;
  --t-fill: 240ms;
}

/* Slashd is its own product with its own brand, so entering it re-themes the
   whole page, not just the accent: its mark sits on near-black, so the surfaces
   become a green-biased charcoal rather than the company navy. Every value below
   was re-validated against these darker grounds. */
:root[data-brand="slashd"] {
  --ground: #080f0d;
  --surface: #0e1714;
  --surface-2: #15211d;
  --surface-3: #1d2c27;
  --rule: #1f302a;
  --rule-strong: #2b403a;

  --ink: #eaf2ee;
  --ink-2: #a7bcb5;
  --ink-3: #8fa49d;          /* 5.52:1 on the darkest ground */

  --brand: #2ec687;
  --brand-ink: #46d69b;
  --brand-press: #46d69b;
  --brand-on: #08182a;
  --series-1: #1caa72;

  --rule-raised: #26382f;
  --inset: #050b09;

  --glass-fill: rgba(21, 33, 29, .55);
  --glass-rim: rgba(234, 242, 238, .18);
}

/* HQ — the personal half: the homelab, the machines, what is owed.

   A third palette because it is a third product in the switcher, and the rule
   here is that the furniture never changes and the colour always does. Slate
   and amber: cool ground so a rack of green "running" dots reads at a glance,
   and a warm accent that cannot be mistaken for Slashd's green or Design
   Made's navy when you are three screens deep and have forgotten where you
   are. Contrast ratios carried over from the other two rather than guessed. */
:root[data-brand="hq"] {
  --ground: #0b0e13;
  --surface: #141922;
  --surface-2: #1b212c;
  --surface-3: #242c39;
  --rule: #242c39;
  --rule-strong: #35404f;

  --ink: #eceff5;
  --ink-2: #b0b9c8;
  --ink-3: #97a1b2;          /* 5.61:1 on the darkest ground */

  --brand: #d98b3a;
  --brand-ink: #e9a25a;
  --brand-press: #e9a25a;
  --brand-on: #12161d;
  --series-1: #c2762c;

  --rule-raised: #2e3745;
  --inset: #070a0e;

  --glass-fill: rgba(27, 33, 44, .55);
  --glass-rim: rgba(236, 239, 245, .18);
}

/* Alfred — the platform itself, which is the sign-in and the portal.

   Those two pages belong to no product, and dressing them in Design Made's
   navy said they did: you signed in to one of the three and then discovered
   the other two. So the shell gets the fourth palette and the products keep
   theirs, which makes the switch of colour on entering one the thing that
   tells you you have arrived somewhere.

   Same rule as the navy: the mark is the substrate rather than a sticker on
   a neutral grey. Alfred's mark is a gold tile with a near-black A, and it is
   the A — #1a1206 — that becomes the panel, with the gold left to be the
   accent it already is. Warm black against HQ's cool slate, so the one other
   palette built on an amber accent still reads as a different place. */
:root[data-brand="alfred"] {
  --ground: #0f0b04;
  --surface: #1a1206;
  --surface-2: #241a0c;
  --surface-3: #2f2210;
  --rule: #2c2010;
  --rule-strong: #3d2d17;

  --ink: #f7f1e6;
  --ink-2: #cbb99c;
  --ink-3: #a08f75;          /* 4.92:1 on --surface-3, the worst case */

  /* Straight off the mark's own gradient: the mid stop carries the accent,
     the light stop is the lifted step for accent-as-text, and the label on a
     gold fill is the A's own colour at 8.5:1. */
  --brand: #e0a45b;
  --brand-ink: #f3ca86;
  --brand-press: #f3ca86;
  --brand-on: #1a1206;
  --series-1: #c07f38;

  --rule-raised: #38291a;
  --inset: #0a0703;

  --glass-fill: rgba(36, 26, 12, .55);
  --glass-rim: rgba(247, 241, 230, .18);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, p, figure, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--brand-ink);
  outline-offset: 2px;
  border-radius: var(--r-control);
}

::selection { background: color-mix(in srgb, var(--brand) 35%, transparent); color: var(--ink); }

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

.skip {
  position: absolute; left: 8px; top: -48px; z-index: 100;
  background: var(--brand); color: var(--brand-on); padding: 8px 12px;
  border-radius: var(--r-control); font-weight: 600; transition: top .12s var(--ease);
}
.skip:focus { top: 8px; }

/* ── the motif ─────────────────────────────────────────────────────────────
   One brand-coloured diagonal, which is also the Slashd mark. Reused as the
   active marker, the figure separator and the drawer tick. */
/* The motif, lifted from the mark: a marker for a section, never decoration.
   Brand blue informs, warn asks for attention, ink 3 is a neutral note. */
.brand__lockup { display: block; width: auto; flex: none; }

.slash {
  display: inline-block; width: 6px; height: 16px;
  background: var(--brand); transform: skewX(-14deg);
  border-radius: 2px; vertical-align: -.16em; flex: none;
}
.slash--warn { background: var(--warn); }
.slash--note { background: var(--ink-3); }
.slash--rule { background: var(--rule-strong); }

/* ── elevation: three levels, and no more ──────────────────────────────────
   1 flat, every card. 2 top-lit, the one hero figure per screen. 3 glass, the
   functional layer. A fourth would stop meaning anything. */
.lvl-1 {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-panel);
}
.lvl-2 {
  background: linear-gradient(160deg, var(--surface-2), var(--surface));
  border: 1px solid var(--rule-raised);
  border-radius: var(--r-panel);
  box-shadow: inset 0 1px 0 rgba(238, 243, 248, .07);
}
.glass {
  background: var(--glass-fill);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-rim);
  box-shadow: var(--glass-shadow);
  border-radius: var(--r-capsule);
}

/* Lists that re-rank under you settle rather than jump, so the movement reads
   as the same rows in a new order. */
@keyframes settle {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
.settle { animation: settle var(--t-settle) var(--ease) both; }

@media (prefers-reduced-motion: reduce) {
  .settle { animation: none; }
}

/* ── shell ──────────────────────────────────────────────────────────────────
   A sidebar and a column. The sidebar is inset rather than raised: it is
   chrome, and chrome sits behind the page rather than on top of it. */
.app {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  min-height: 100vh;
  min-height: 100dvh;
}

.rail {
  background: var(--inset);
  border-right: 1px solid var(--rule);
  padding: 22px 0;
  display: flex; flex-direction: column; gap: 22px;
  /* Sticky and full-height: the inset colour runs to the bottom of the window
     whatever the page is, rather than stopping where the nav items end and
     leaving a seam down the left. */
  position: sticky; top: 0; align-self: stretch; z-index: 20;
  height: 100dvh; overflow-y: auto;
}
.brand { padding: 0 18px; display: flex; align-items: center; }

/* ── the product switcher ───────────────────────────────────────────────────
   Design Made LTD and Slashd are two products under one account, not two
   halves of one nav. The sidebar names the one you are in and carries only its
   sections; this is how you leave. */
.brandswitch {
  display: flex; align-items: center; gap: 9px; width: 100%;
  margin: 0 12px; width: calc(100% - 24px);
  padding: 8px; border: 0; border-radius: var(--r-input);
  background: none; cursor: pointer; text-align: left;
  transition: background .1s var(--ease);
}
.brandswitch:hover { background: var(--surface); }
.brandswitch[aria-expanded="true"] { background: var(--surface-2); }
.brandswitch__tile { width: 26px; height: 26px; border-radius: 6px; flex: none; display: block; }
/* The same tile for a product with no icon: its initials on the brand colour,
   rather than another product's mark standing in for one it does not have. */
.brandswitch__tile--type {
  display: grid; place-items: center;
  background: var(--brand); color: var(--brand-on);
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .02em; text-transform: uppercase;
}
.brandswitch__name { min-width: 0; flex: 1; }
.brandswitch__product {
  display: block; font-size: 13px; font-weight: 600; color: var(--ink);
  line-height: 1.15; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brandswitch__group {
  display: block; font-family: var(--mono); font-size: 11px;
  letter-spacing: .04em; color: var(--ink-3); margin-top: 1px;
}
.brandswitch__chev { color: var(--ink-3); font-size: 13px; line-height: 1; }

/* A line on a row that is not an error but is not fine either: a unit whose
   script has been deleted, or a process running outside the systemd unit that
   is supposed to own it. Warm rather than red — nothing is broken yet, and
   colouring it like a failure would make the real failures quieter. */
.warn { color: var(--warn, #d9a441); }

/* The host chips and the path above a listing. A chip is a host you can be on;
   the one you are on is filled rather than outlined, which is the same rule
   the nav uses for the section you are in. */
.chip {
  display: inline-block; padding: 3px 10px; margin-right: 6px;
  border: 1px solid var(--rule-strong); border-radius: 999px;
  font-family: var(--mono); font-size: 11.5px; color: var(--ink-2);
  text-decoration: none;
}
.chip:hover { border-color: var(--brand); color: var(--ink); }
.chip--on { background: var(--brand); border-color: var(--brand); color: var(--brand-on); }

.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px;
  padding: 10px 20px 14px; font-family: var(--mono); font-size: 12px;
}
.crumb { color: var(--ink-2); text-decoration: none; padding: 1px 4px; border-radius: 4px; }
.crumb:hover { color: var(--ink); background: var(--surface-2); }
.crumb__sep { color: var(--ink-3); }

/* The tick on a row. Sized to the row rather than to the browser's default,
   which is small enough to be a nuisance on a phone. */
/* ── Controls ────────────────────────────────────────────────────────────

   Every one of these is drawn here rather than by the operating system, and
   every colour in them is a token — so a checkbox in HQ is amber and the same
   checkbox in Slashd is green without either being written twice. `accent-color`
   is what this replaces: it tints the platform's own control and leaves its
   shape, size and focus ring alone, which is three quarters of what makes a
   control look like it came from somewhere else.

   Nothing here needs JavaScript. They are real inputs with their own paint, so
   the keyboard, the label, the form and the screen reader all behave as they
   already did. */

.check {
  appearance: none; margin: 0; flex: none; cursor: pointer;
  width: 17px; height: 17px; border-radius: 5px;
  border: 1px solid var(--rule-strong); background: var(--ground);
  display: grid; place-content: center;
  transition: background-color .12s var(--ease), border-color .12s var(--ease);
}
.check:hover { border-color: var(--brand); }
/* The tick is drawn, not a glyph: a font's checkmark sits differently in every
   family and would have to be nudged per weight. */
.check::after {
  content: ""; width: 9px; height: 5px;
  border-left: 2px solid var(--brand-on); border-bottom: 2px solid var(--brand-on);
  border-radius: 1px; transform: rotate(-45deg) scale(.6); transform-origin: center;
  opacity: 0; transition: opacity .1s var(--ease), transform .12s var(--ease);
  margin-top: -2px;
}
.check:checked { background: var(--brand); border-color: var(--brand); }
.check:checked::after { opacity: 1; transform: rotate(-45deg) scale(1); }
/* Some ticked and some not: one control saying "a few of these", which is what
   a folder header means when half its rows are chosen. */
.check:indeterminate { background: var(--brand); border-color: var(--brand); }
.check:indeterminate::after {
  opacity: 1; width: 9px; height: 0; border-left: 0;
  border-bottom: 2px solid var(--brand-on); transform: none; margin-top: 0;
}
.check:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 2px; }
.check:disabled { opacity: .4; cursor: not-allowed; }

/* The same control, round, for a one-of-many choice. */
.radio { border-radius: 999px; }
.radio::after {
  width: 7px; height: 7px; border: 0; border-radius: 999px;
  background: var(--brand-on); transform: scale(.4); margin: 0;
}
.radio:checked::after { transform: scale(1); }

/* A row's tick, sized to the row rather than to the browser's default — which
   is small enough to be a nuisance on a phone. */
.pick { display: grid; place-items: center; padding: 0 2px; }
/* "All", beside the download button. A label so the word is part of the
   target, which on a phone is the difference between a tick and three tries. */
.allpick {
  display: inline-flex; align-items: center; gap: 7px; margin-right: 12px;
  font-family: var(--mono); font-size: 11.5px; color: var(--ink-2);
  cursor: pointer; user-select: none;
}
.allpick:hover { color: var(--ink); }

/* The dropdown, list and all.

   The native <select> is still there and still the thing that submits — this
   is drawn beside it and writes back to it. Hidden rather than removed, so
   the form is whole if the script never runs. */
.drop { position: relative; display: inline-block; }
.drop__native {
  /* Pinned to the wrapper's corner. With no offsets it keeps its static
     position, which is *after* the button that replaced it — so a 1px box at
     the right edge of the control still reaches past it, and a select will not
     render narrower than about 37px whatever it is told. */
  position: absolute; left: 0; top: 0;
  height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); border: 0;
  /* Important because the select it is applied to may carry its own inline
     sizing — set to shape the control back when it was the visible one, and
     inline styles outrank a class. The supplier filter kept `min-width:160px`
     that way and went on occupying 160px while invisible, pushing the page
     141px past the edge of a phone. Hidden has to mean no size at all. */
  width: 1px !important; min-width: 0 !important;
}
.drop__btn {
  display: inline-flex; align-items: center; justify-content: space-between;
  gap: 8px; width: 100%; text-align: left; cursor: pointer;
  /* The same arrow the styled native control drew, kept so the closed state
     did not change shape when the open one stopped being the OS's. */
  padding-right: 26px;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
                    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: calc(100% - 14px) 13px, calc(100% - 9px) 13px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.drop__btn[aria-expanded="true"] {
  border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand);
}
.drop__list {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0;
  min-width: 100%; max-height: 40vh; overflow: auto;
  padding: 4px; border-radius: var(--r-panel, 10px);
  border: 1px solid var(--rule-strong); background: var(--surface);
  box-shadow: 0 12px 28px rgba(0, 0, 0, .45);
}
.drop__opt {
  padding: 6px 10px; border-radius: 6px; cursor: pointer;
  font-size: 13px; color: var(--ink-2); white-space: nowrap;
}
.drop__opt:hover { background: var(--surface-2); color: var(--ink); }
.drop__opt:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: -2px; }
.drop__opt[aria-selected="true"] { color: var(--ink); background: var(--surface-2); }
.drop__opt[aria-selected="true"]::after {
  content: "·"; margin-left: 8px; color: var(--brand-ink);
}

/* A journal tail. Monospace, its own inset ground, and scrolling in both
   directions rather than wrapping: a wrapped log line loses the column
   alignment that is most of what makes one readable at a glance. */
.logs {
  margin: 10px 0 0; padding: 12px 14px;
  background: var(--inset); border: 1px solid var(--rule);
  border-radius: var(--r-panel, 10px);
  font-family: var(--mono); font-size: 11.5px; line-height: 1.55;
  color: var(--ink-2); white-space: pre; overflow: auto; max-height: 60vh;
  /* The newest lines are at the bottom, which is where journald puts them and
     where you look first. */
  overscroll-behavior: contain;
}

/* Centred, like the command palette: a short list of destinations rather than
   a menu of actions hanging off a corner. */
.sheet {
  position: fixed; top: 22vh; left: 50%; transform: translateX(-50%);
  width: min(420px, calc(100vw - 32px)); z-index: 60;
  border-radius: var(--r-panel); overflow: hidden;
  background: var(--surface); border: 1px solid var(--rule-raised);
  /* The handoff's own shadow for a floating layer, rather than the wider one
     I had reached for: a 1px border under a 60px blur is the shape of a
     generated card, and there was already a decided value to use. */
  box-shadow: 0 18px 44px rgba(3, 10, 20, .6);
}
.sheet[data-open="false"] { display: none; }
.sheet[data-open="true"] { animation: settle var(--t-settle) var(--ease) both; }
.sheet__head { padding: 12px 14px; border-bottom: 1px solid var(--rule); }
.sheet__title { font-size: 13.5px; font-weight: 600; }
.sheet__body { padding: 6px; display: flex; flex-direction: column; gap: 1px; }
/* A real element beside the sheet rather than a pseudo-element on the body:
   the two have to share a stacking context, or whichever paints later covers
   the other and eats its clicks. */
.scrim--sheet {
  position: fixed; inset: 0; z-index: 55;
  background: rgba(4, 12, 22, .55);
}
.scrim--sheet[data-open="false"] { display: none; }

.switchrow {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: var(--r-control);
  color: var(--ink); text-decoration: none;
}
a.switchrow:hover { background: var(--surface-2); }
.switchrow--here { background: rgba(35, 150, 211, .12); }
.switchrow--soon { opacity: .5; }
.switchrow__tile { width: 24px; height: 24px; border-radius: 5px; flex: none; display: block; }
.switchrow__tile--blank { background: var(--surface-3); }
.switchrow__body { min-width: 0; flex: 1; }
.switchrow__name { display: block; font-size: 13.5px; font-weight: 500; }
.switchrow__what {
  display: block; font-size: 12px; color: var(--ink-3); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.switchrow__mark { font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
.switchrow--here .switchrow__mark { color: var(--brand-ink); }
/* Cropped to its own content edges, so its left edge lands on the layout
   margin rather than on the edge of a transparent box. */
.brand__lockup { display: block; width: auto; height: 32px; flex: none; }

.rail__nav { display: flex; flex-direction: column; }
.rail__foot { margin-top: auto; padding-top: 12px; }

.navitem {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 18px; border: 0; border-left: 2px solid transparent;
  background: none; width: 100%; text-align: left; cursor: pointer;
  color: var(--ink-2); font-size: 14px; font-family: var(--sans);
  text-decoration: none;
  transition: background .1s var(--ease), color .1s var(--ease);
}
.navitem:hover { background: var(--surface); color: var(--ink); }
.navitem[aria-current="page"] {
  border-left-color: var(--brand); background: var(--surface);
  color: var(--ink); font-weight: 600;
}
/* Only the inbox carries one: it is the only count that is a queue rather
   than a total, and a pill on every row would say nothing. */
.navitem__count {
  margin-left: auto; font-family: var(--mono); font-size: 12px;
  color: var(--ink-3);
}
.navitem__count--wait {
  padding: 2px 7px; border-radius: 6px;
  background: rgba(232, 163, 61, .16); color: var(--warn);
}

/* The other product, kept below a rule: the same operator, a different thing. */
.railsplit { border-top: 1px solid var(--rule); margin: 0 0 6px; }
.navitem--other { font-size: 13px; }
.navitem__icon { width: 22px; height: 22px; display: block; flex: none; border-radius: 5px; }
.navitem--quiet { font-size: 13px; color: var(--ink-3); padding-top: 7px; padding-bottom: 7px; }

/* The page's side padding, named once: every band that runs full width pulls
   itself out by exactly this much. Hardcoding 30px meant each of them
   overshot the edge of a phone, where the padding is 16. */
.main { --page-pad: 30px; min-width: 0; padding: 0 var(--page-pad) 40px; }
/* The portal is not inside a product, so it has no sidebar to sit beside and
   no page padding — the halves run to the edges of the window. */
.app--portal { grid-template-columns: minmax(0, 1fr); }
.app--portal .main { padding: 0; }

/* ── the portal ─────────────────────────────────────────────────────────────
   Split: each product owns half the screen in its own palette, so choosing one
   is the whole page rather than a row in a list. The marks carry the names —
   both lockups include their wordmark — so nothing is set beside them. */
.split {
  position: relative; min-height: 100dvh;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
}

.split__half {
  display: flex; align-items: flex-end;
  padding: 54px; min-width: 0; text-decoration: none;
  /* The hovered side lifts one surface step and nothing moves. An earlier
     version grew the hovered half by 14%, which shifted the other product's
     content while you were still deciding — and the system's own hover rule
     is that rows lift a step and nothing scales. */
  transition: background-color .2s var(--ease);
  flex: 1 1 0;
}
/* align-items, not the default stretch: a column flex container stretches its
   children across the cross axis, which for an image means the width. The PNG
   wordmark distorted to fill it and the SVG lockup letterboxed inside it and
   centred — so one looked squished, the other looked huge, and the lockup no
   longer lined up with the text under it. */
.split__body {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 20px; max-width: 34ch;
}

.split__half--designmade { background: #0a1a2e; border-right: 1px solid #22405f; }
.split__half--designmade .split__what { color: #a8bdd2; }
.split__half--designmade:hover { background: #0c2038; }
.split__half--designmade:focus-visible { outline: 2px solid #2396d3; outline-offset: -6px; }

.split__half--slashd { background: #080f0d; }
.split__half--slashd .split__what { color: #a7bcb5; }
.split__half--slashd:hover { background: #0b1512; }
.split__half--slashd:focus-visible { outline: 2px solid #2ec687; outline-offset: -6px; }

.split__half--hq { background: #0b0e13; border-left: 1px solid #242c39; }
.split__half--hq .split__what { color: #b0b9c8; }
.split__half--hq:hover { background: #10141b; }
.split__half--hq:focus-visible { outline: 2px solid #e9a25a; outline-offset: -6px; }

/* A lockup assembled from a tile and a name, for the product that has no
   lockup file. Deliberately the slashd.app arrangement — a small square
   followed by one line of type — because that is the half it sits next to, and
   two halves built the same way read as one set where a bare word did not.

   The type is matched to the cap height of the wordmarks beside it, not to
   their frames, which is the same rule that keeps those two from looking like
   different sizes. */
/* `.split__mark` below sets display:block on everything carrying that class,
   and it comes later in the file — so this needs the two classes together to
   outrank it, or the tile stacks above the name instead of sitting beside it. */
.split__mark.split__mark--built { display: flex; align-items: center; gap: 12px; }
.split__tile { display: block; width: 44px; height: 44px; border-radius: 10px; flex: none; }
.split__type {
  font-family: var(--display, var(--sans));
  font-size: 34px; font-weight: 700; letter-spacing: -.02em;
  color: #eceff5; line-height: 1;
}

/* Balanced on cap height, not on frame height: the Design Made lockup is
   2.29:1 and stacks its wordmark over two lines, the slashd.app wordmark is
   5.6:1 and is all type. Matching their heights would make one look twice the
   size of the other. */
.split__mark { display: block; width: auto; flex: none; }
/* Two different kinds of mark: the Design Made lockup stacks its wordmark over
   two lines beside a full-height icon, the slashd.app one is a small tile
   followed by a single line of type. Sized so the two badges are close and the
   two wordmarks read at the same size — matching either dimension alone makes
   one mark twice the other. */
.split__mark--designmade { height: 60px; }
.split__mark--slashd { height: 44px; }

/* The controls under one eBay line. Below the row rather than inside it,
   because the row is a grid of four columns and a lot picker is not a column —
   and because deciding this one needs the reason in front of you. */
.salerow {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 0 20px 14px 20px; border-bottom: 1px solid var(--surface-2);
}
.salerow__why { margin: 0; font-size: 12px; color: var(--ink-3); flex: 1 1 200px; }
.salerow__act { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.salerow__act .field { width: auto; min-width: 260px; }
/* Said where the picker would be, when there is no stock to pick from. */
.salerow__none { font-size: 12.5px; color: var(--warn); max-width: 46ch; }

.split__what { font-size: 15px; line-height: 1.6; text-wrap: pretty; }

/* A connection's fields, under the row they belong to.

   `.integration` and not `.conn` — that one is already the grid the old
   connection rows use, further down this file, and borrowing it laid the form
   out as a second column beside the row instead of under it.

   Almost nothing here styles an input. The drawer already has the form
   furniture — `formgrid`, `lbl`, `field`, `hint` — and this uses it, so a
   field on this page is the same object as a field in the drawer rather than
   a second one that merely resembles it. What is left is the container, and
   the one label annotation that is genuinely new. */
.integration__form {
  padding: 2px 20px 18px 42px; border-bottom: 1px solid var(--surface-2);
  display: grid; gap: 16px; max-width: 680px;
}
/* Indented to where the row's own text starts, so the form reads as part of
   that connection rather than as a panel that happens to follow it. */
.integration__form .formgrid { gap: 14px 16px; }
/* The consent row, above the ordinary save. Separated by a rule because
   pressing it leaves the site, which is a different kind of act from saving a
   field. */
.integration__oauth {
  display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
  margin: 0; padding-top: 14px; border-top: 1px solid var(--rule);
}
.integration__org { font-size: 12.5px; color: var(--ink-3); }
.integration__actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
/* What the save just did. Quiet: it confirms, it does not announce. */
.integration__said {
  margin: 0; font-size: 12.5px; color: var(--brand-ink);
}

/* Where a value came from, beside the label rather than under the field — it
   is a property of the field, and the space under it belongs to the sentence
   explaining what the field is for. Lowercase against the label's caps so it
   reads as an aside rather than as part of the name. */
.lbl__from {
  font-family: var(--mono); font-size: 10px; text-transform: none;
  letter-spacing: 0; color: var(--ink-3); margin-left: 8px;
}

/* A checkbox and its name, which is one control and should behave like one. */
.pick { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; }
.pick > span { font-size: 13px; color: var(--ink-2); }

/* The chip and the Edit button share the row's last cell. `.need` is a three
   column grid, so a fourth child wraps to a line of its own. */
.need__end { display: flex; align-items: center; gap: 10px; }

/* The outcome strip: what a decision did, and the way back from it.
   Not `.verdict` — that is already the class on the Add/Fix/Not-stock buttons
   further down this file, and sharing it restyled them into banners. Sits where
   the eye already is — between the threshold and the queue — rather than
   floating over a corner, because it has to survive being read. */
.outcome {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 10px 14px;
  border: 1px solid var(--rule); border-left: 3px solid var(--brand);
  border-radius: var(--r-panel, 10px); background: var(--surface-2);
  font-size: 13px;
}
.outcome--good { border-left-color: var(--good); }
.outcome--warn { border-left-color: var(--warn); }
.outcome--bad  { border-left-color: var(--bad); }
.outcome--note { border-left-color: var(--brand); }
.outcome__what { font-weight: 600; }
/* Takes the slack so Undo ends up at the right-hand end. `margin-left: auto`
   on the button did that too, until the text was long enough to wrap: an auto
   margin claims all the free space, which is itself enough to push the button
   onto a line of its own even when there was room beside it. */
.outcome__detail { color: var(--ink-3); flex: 1 1 auto; }
.outcome__undo {
  font-family: var(--mono); font-size: 12px;
  color: var(--brand-ink); background: none; border: 0; padding: 4px 6px;
  cursor: pointer; text-decoration: underline;
  text-decoration-color: var(--rule-strong);
}
.outcome__undo:hover { color: var(--ink); }

.split__bar {
  position: absolute; top: 0; right: 0;
  display: flex; align-items: center; gap: 16px; padding: 22px 30px;
}
.split__link {
  font-family: var(--mono); font-size: 12px; font-weight: 400; color: #94abc2;
  background: none; border: 0; padding: 0; cursor: pointer; text-decoration: none;
}
.split__link:hover { color: #eef3f8; }

/* A phone gets them stacked, each still its own world. */
@media (max-width: 760px) {
  .split { grid-auto-flow: row; grid-auto-columns: auto; }
  .split__half { padding: 34px 24px; align-items: center; min-height: 44dvh; }
  .split__half--designmade { border-right: 0; border-bottom: 1px solid #22405f; }
  .split__mark--designmade { height: 56px; }
  .split__mark--slashd { height: 32px; }
  .split__bar { position: static; justify-content: flex-end; }
}
.main:focus, .main:focus-visible { outline: none; }

/* ── the screen header ──────────────────────────────────────────────────────
   Brand and place in one line. The logo is not repeated here: the sidebar
   already carries it, and saying it twice on one screen says it once. */
.vhead {
  margin: 0 calc(-1 * var(--page-pad)) 22px;
  padding: 20px var(--page-pad);
  border-bottom: 1px solid var(--rule);
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
}
.vhead__left { display: flex; align-items: center; gap: 14px; min-width: 0; flex-wrap: wrap; }
.vhead h1 {
  font: var(--t-title); letter-spacing: -.015em; color: var(--ink-2);
  margin: 0;
}
.hair { width: 1px; height: 22px; background: var(--rule); flex: none; }
.vhead__meta {
  font-family: var(--mono); font-size: 12px; color: var(--ink-3);
}

/* ── waiting ────────────────────────────────────────────────────────────────
   A boosted navigation is a fetch, and a fetch can be slow. The layout is held
   and the figures dim rather than the page emptying: the handoff's rule is no
   spinners over tables and no skeleton shimmer, so this is the same screen
   arriving rather than a different screen pretending to load. */
/* Set only for a whole-page navigation, never for a targeted swap: a panel
   updating should look like a panel updating. */
.main { transition: opacity .12s var(--ease); }
.main.is-navigating { opacity: .45; }

/* A thin progress line at the very top, which is the only thing that moves.
   It only appears once a navigation has taken long enough to be worth
   admitting to. */
#nav-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 80;
  background: var(--brand); transform: scaleX(0); transform-origin: left;
  opacity: 0; pointer-events: none;
}
#nav-progress.on {
  opacity: 1; transform: scaleX(.75);
  transition: transform 1.4s cubic-bezier(.06, .7, .2, 1), opacity .1s;
}
#nav-progress.done {
  opacity: 0; transform: scaleX(1);
  transition: transform .14s var(--ease), opacity .22s .1s;
}

@media (prefers-reduced-motion: reduce) {
  .main, #nav-progress { transition: none; }
}

/* Where a screen genuinely has nothing to show yet, it shows its own shape
   rather than a spinner: the rows that are coming, at the size they will be. */
.skeleton { background: var(--surface-2); border-radius: var(--r-control); }
.skeleton--line { height: 11px; }
.skeleton--row { height: 44px; margin-bottom: 1px; }

/* ── controls ─────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 12px; border-radius: var(--r-control);
  border: 1px solid var(--rule-strong); background: var(--surface-2);
  color: var(--ink); cursor: pointer; white-space: nowrap;
  font-size: 12.5px; font-weight: 500; text-decoration: none;
  transition: background .1s var(--ease), border-color .1s var(--ease);
}
.btn:hover { background: var(--surface-3); border-color: #414757; }
.btn:active { transform: translateY(.5px); }
.btn--primary {
  background: var(--brand); border-color: var(--brand); color: var(--brand-on); font-weight: 600;
}
.btn--primary:hover { background: var(--brand-press); border-color: var(--brand-press); }
.btn--quiet { background: none; border-color: transparent; color: var(--ink-2); }
.btn--quiet:hover { background: var(--surface-3); color: var(--ink); }
.btn--danger { color: var(--bad-ink); border-color: #5a3630; }
.btn--danger:hover { background: #3a221e; border-color: #6d423a; }
.btn[disabled] { opacity: .42; cursor: not-allowed; }
.btn[disabled]:hover { background: var(--surface-2); border-color: var(--rule-strong); }

.seg { display: inline-flex; gap: 8px; }
.seg button {
  border: 1px solid transparent; background: none;
  padding: 7px 12px; border-radius: var(--r-control); cursor: pointer;
  color: var(--ink-3); font-size: 12px; font-family: var(--mono);
  transition: background .1s var(--ease), color .1s var(--ease);
}
.seg button:hover { color: var(--ink-2); }
.seg button[aria-pressed="true"] {
  background: var(--surface-2); border-color: var(--rule); color: var(--ink-2);
}

.field {
  height: 30px; padding: 0 9px; border-radius: var(--r-control);
  border: 1px solid var(--rule-strong); background: var(--ground);
  color: var(--ink); font-size: 13px; width: 100%;
  transition: border-color .1s var(--ease);
}
.field:hover { border-color: var(--rule-raised); }
.field:focus { border-color: var(--brand); outline: none; box-shadow: 0 0 0 1px var(--brand); }
/* The browser's own file control is a grey system button that belongs to no
   design system. The field keeps the shape of every other input and the button
   inside it is restyled to match the quiet buttons beside it. */
input[type="file"].field {
  padding: 0; height: auto; min-height: 32px; line-height: 1.6;
  display: flex; align-items: center; gap: 10px;
  color: var(--ink-3); font-family: var(--sans); font-size: 12.5px;
  cursor: pointer;
}
input[type="file"].field::file-selector-button {
  height: 30px; padding: 0 12px; margin: 0;
  border: 0; border-right: 1px solid var(--rule-strong);
  border-radius: var(--r-control) 0 0 var(--r-control);
  background: var(--surface-2); color: var(--ink);
  font-family: var(--sans); font-size: 12.5px; font-weight: 500;
  cursor: pointer; transition: background .1s var(--ease);
}
input[type="file"].field:hover::file-selector-button { background: var(--surface-3); }

.field--mono { font-family: var(--mono); }
.field--num { font-family: var(--mono); text-align: right; }
textarea.field { height: auto; padding: 7px 9px; resize: vertical; min-height: 58px; }
select.field { appearance: none; padding-right: 26px;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%), linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: calc(100% - 14px) 13px, calc(100% - 9px) 13px;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; cursor: pointer; }
.field--err { border-color: var(--bad); }
.field--err:focus { border-color: var(--bad); box-shadow: 0 0 0 1px var(--bad); }

.search { position: relative; flex: 0 1 240px; min-width: 120px; }
.search .field { padding-left: 28px; font-size: 12.5px; }
.search__icon { position: absolute; left: 9px; top: 0; bottom: 0; display: flex; align-items: center; color: var(--ink-3); pointer-events: none; }

.lbl {
  display: block; font-family: var(--narrow); font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3);
  margin-bottom: 5px;
}
.hint { font-size: 11.5px; color: var(--ink-3); margin-top: 4px; }
.hint--err { color: var(--bad); }

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  height: 22px; padding: 0 8px; border-radius: var(--r-control);
  background: var(--surface-3); border: 1px solid var(--rule-strong);
  font-family: var(--mono); font-size: 11px; color: var(--ink-2); white-space: nowrap;
}
.chip--demo { border-color: #5a4520; background: #2a2011; color: var(--warn); }
.dot { width: 8px; height: 8px; border-radius: 999px; flex: none; background: var(--ink-3); }
.dot--good { background: var(--good); }
.dot--bad { background: var(--bad); }
.dot--warn { background: var(--warn); }
.dot--ink2 { background: var(--ink-2); }

/* ── panels ───────────────────────────────────────────────────────────────── */
.panel {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--r-panel); min-width: 0;
  display: flex; flex-direction: column;
}
.panel__head { flex: none; }
.panel__head {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 14px; border-bottom: 1px solid var(--rule); min-height: 44px;
}
.panel__title { font-size: 13px; font-weight: 600; letter-spacing: -.005em; }
.panel__note { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-3); }
/* Paired panels stretch to the same height, so whatever inside them can grow
   takes the slack: a chart gets taller, a capped list shows more rows. */
.panel__body { padding: 14px; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.panel__body > .chart { flex: 1 1 auto; min-height: 168px; }
.stack > .panel { flex: none; }
.panel__body--flush { padding: 0; }

/* The old header, still used by screens not yet moved to .vhead. */
.viewhead {
  display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap;
  margin: 24px 0 16px;
}
.viewhead h1 { font-size: 22px; font-weight: 700; letter-spacing: -.02em; }
.viewhead p { color: var(--ink-3); font-size: 12.5px; max-width: 62ch; }

.grid { display: grid; gap: 12px; }
.grid--figs { grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }
.grid--2 { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); }
.grid--even { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.stack { display: flex; flex-direction: column; gap: 12px; }
/* The body of a screen, at the drawings' rhythm. */
.stack--wide { gap: 22px; }
.row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ── figures ──────────────────────────────────────────────────────────────── */
.hero {
  display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap;
  padding: 16px 18px;
}
.hero__val {
  font-family: var(--mono); font-size: 46px; font-weight: 500;
  letter-spacing: -.03em; line-height: 1; color: var(--ink);
}
.hero__meta { display: flex; align-items: center; gap: 10px; padding-bottom: 5px; }

.fig { padding: 12px 14px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.fig__lbl {
  font-family: var(--narrow); font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3);
}
.fig__val {
  font-family: var(--mono); font-size: 22px; font-weight: 500;
  letter-spacing: -.02em; line-height: 1.1; color: var(--ink);
}
.fig__val--sm { font-size: 17px; }
.fig__foot { display: flex; align-items: center; gap: 7px; min-height: 16px; }

/* ── today ──────────────────────────────────────────────────────────────────
   One hero figure at level 2, four flat tiles beside it, and two cards under
   it. The proportions are the drawings': the hero is wider than a tile because
   it carries a chart, not because it is more important. */
.figrow {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
}
.figcol { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

.tile {
  flex: 1; padding: 18px; min-width: 0;
  display: flex; flex-direction: column; gap: 8px;
}
.tile__lbl {
  font: var(--t-label); letter-spacing: var(--t-label-track);
  text-transform: uppercase; color: var(--ink-3);
}
.tile__val {
  font: var(--t-figure); letter-spacing: -.02em; color: var(--ink);
}
.tile__val--slashd { color: var(--good); }
.tile__unit { font-size: 19px; color: var(--ink-2); }
.tile__foot { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }

.heroc { padding: 22px; display: flex; flex-direction: column; gap: 12px; }
.heroc__lbl {
  font: var(--t-label); letter-spacing: var(--t-label-track);
  text-transform: uppercase; color: var(--ink-2);
}
.heroc__fig { display: flex; align-items: baseline; gap: 4px; }
.heroc__cur {
  font-family: var(--mono); font-size: 30px; font-weight: 500; color: var(--ink-2);
}
.heroc__val {
  font: var(--t-hero); font-family: var(--mono);
  letter-spacing: var(--t-hero-track); color: var(--ink);
}
.heroc__meta {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  row-gap: 5px; font-family: var(--mono); font-size: 13px;
}
.heroc__meta .hair { height: 12px; }
/* Two clauses on one line where they fit. Where they do not, they become two
   lines and the divider goes, because a rule at the end of a line divides
   nothing. */
.heroc { container-type: inline-size; }
@container (max-width: 540px) {
  .heroc__meta { gap: 0; }
  .heroc__meta .hair { display: none; }
  .heroc__meta > span { flex-basis: 100%; }
}

/* Twelve weeks, hanging from a zero line. A losing week points down, which is
   the only honest way to draw one. */
.bars { margin-top: 6px; height: 44px; display: flex; align-items: stretch; gap: 3px; }
.bars__slot { flex: 1; display: grid; grid-template-rows: var(--zero, 100%) 1fr; min-width: 0; }
.bars__bar { grid-row: 1; align-self: end; border-radius: 2px; background: var(--surface-3); }
.bars__bar--down { grid-row: 2; align-self: start; background: var(--bad); }
.bars__bar--now { background: var(--brand); }
.axis { font-family: var(--mono); font-size: 11px; color: var(--ink-3); }

.lowrow { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 16px; }

.card { overflow: hidden; min-width: 0; }
.card__head {
  padding: 16px 20px; border-bottom: 1px solid var(--rule);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.card__lead { display: flex; align-items: center; gap: 10px; min-width: 0; flex-wrap: wrap; }
.card__title { font: var(--t-section); margin: 0; }
.card__note { font-family: var(--mono); font-size: 12px; color: var(--ink-3); }
.card__link { font-family: var(--mono); font-size: 12px; color: var(--brand-ink); text-decoration: none; }
.card__link:hover { text-decoration: underline; }

.need {
  padding: 14px 20px; border-bottom: 1px solid var(--surface-2);
  display: grid; grid-template-columns: 8px minmax(0, 1fr) auto;
  gap: 14px; align-items: center;
}
.need:last-child { border-bottom: 0; }
.need__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.need__title { font-size: 15px; font-weight: 500; }
.need__why { font-size: 13px; color: var(--ink-2); }
.need__amt { font-family: var(--mono); font-size: 13px; color: var(--ink); text-align: right; }

/* The learning, as a finish line rather than a percentage: ten notches, one
   per clean parse still needed. */
.trust {
  padding: 14px 20px; border-bottom: 1px solid var(--surface-2);
  display: flex; flex-direction: column; gap: 9px;
}
.trust:last-child { border-bottom: 0; }
.trust__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.trust__name { font-size: 15px; font-weight: 500; }
.trust__tally { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
.notches { display: flex; gap: 3px; }
.notch { flex: 1; height: 6px; border-radius: 2px; background: var(--surface-3); }
.notch--on { background: var(--good); }
.trust__note { font-family: var(--mono); font-size: 12px; color: var(--ink-3); }
.trust__note--good { color: var(--good); }
.trust__note--warn { color: var(--warn); }


/* ── inbox ──────────────────────────────────────────────────────────────────
   The queue is work, so it is ordered by what needs doing and the weak field
   is a column rather than a detail. Nothing here shows a bare score without
   something next to it you can check. */

/* The stream switch, which is a pill group rather than the period segments:
   these two are the same kind of thing, and only one can be open. */
.seg--pill {
  gap: 0; padding: 3px; border-radius: var(--r-input);
  background: var(--surface); border: 1px solid var(--rule);
}
.seg--pill button {
  font-size: 13px; padding: 7px 14px; border-radius: var(--r-control);
  color: var(--ink-2);
}
.seg--pill button[aria-pressed="true"] {
  background: var(--surface-3); border-color: transparent; color: var(--ink);
  box-shadow: inset 0 1px 0 rgba(238, 243, 248, .08);
}
.seg__n { color: var(--ink-3); }
.seg--pill button[aria-pressed="true"] .seg__n { color: var(--ink-2); }

/* Full-bleed bands: the header rule, the threshold strip and the queue all
   run the width of the screen, so they escape the body padding. */
.bleed {
  margin-left: calc(-1 * var(--page-pad));
  margin-right: calc(-1 * var(--page-pad));
}

.strip {
  background: var(--inset); border-bottom: 1px solid var(--rule);
  padding: 22px var(--page-pad);
}
/* A measure for the control and its caption. Without one the histogram
   stretched across the whole window and the caption under it ran to 240
   characters a line. */
.thresh { max-width: 1080px; }
.thresh { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.thresh__top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.thresh__val { font-family: var(--mono); font-size: 26px; font-weight: 600; letter-spacing: -.02em; }
.thresh__of { font-family: var(--mono); font-size: 13px; color: var(--ink-2); }
/* Its own measure. The control above it wants the width — it is a scale
   across the whole score range — but a sentence set to the same width runs to
   180 characters a line and stops being a sentence you read. */
.thresh__cap {
  font-family: var(--mono); font-size: 12px; color: var(--ink-3);
  max-width: 62ch; line-height: 1.5;
}

/* The histogram sits behind the control, because the bar you are dragging
   through is the work you are deciding not to look at. */
.thresh__ctl { position: relative; height: 52px; }
.thresh__hist {
  position: absolute; inset: 0 0 18px 0;
  display: flex; align-items: flex-end; gap: 2px;
}
/* An empty bucket still draws, so the axis stays visible where the queue is
   empty — but dimmer than one holding something, because "nothing scored 83"
   and "one thing scored 83" must not look the same. */
.thresh__bar { flex: 1; border-radius: 1px; background: var(--rule); }
.thresh__bar--has { background: var(--surface-3); }
.thresh__bar--above.thresh__bar--has { background: var(--brand); }
.thresh__bar--above { background: rgba(35, 150, 211, .35); }
.thresh__range {
  position: absolute; left: 0; right: 0; bottom: 0; width: 100%;
  height: 22px; margin: 0; appearance: none; background: none;
  cursor: grab;
}
.thresh__range:active { cursor: grabbing; }
.thresh__range:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
/* The handle, drawn. `accent-color` tinted the platform's own, which is a
   different size and shape on every one of them — and this one sits on a bar
   chart whose columns it has to line up with.

   The two vendor selectors cannot be combined: a rule listing both is thrown
   out whole by each engine that does not recognise the other's. */
.thresh__range::-webkit-slider-thumb {
  appearance: none; width: 15px; height: 15px; border-radius: 999px;
  background: var(--brand-ink); border: 2px solid var(--ground);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .5); cursor: inherit;
}
.thresh__range::-moz-range-thumb {
  width: 15px; height: 15px; border-radius: 999px;
  background: var(--brand-ink); border: 2px solid var(--ground);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .5); cursor: inherit;
}
/* The bars behind it are the track, so the input's own is not drawn at all. */
.thresh__range::-webkit-slider-runnable-track { background: none; height: 22px; }
.thresh__range::-moz-range-track { background: none; height: 22px; }


/* Stretched rather than top-aligned, so the rule between the queue and the
   detail runs the height of the section instead of stopping at the last row. */
.queue { display: grid; grid-template-columns: minmax(0, 1fr) 560px; }
.queue__list { border-right: 1px solid var(--rule); min-width: 0; }
.qhead, .qrow {
  display: grid; grid-template-columns: minmax(0, 1fr) 92px 120px 86px;
  gap: 12px; align-items: center;
}
.qhead {
  padding: 9px 30px; border-bottom: 1px solid var(--rule);
  background: var(--inset);
  font: var(--t-label); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3); position: sticky; top: 0; z-index: 2;
}
.qrow {
  border: 0; border-bottom: 1px solid var(--surface-2);
  border-left: 3px solid var(--rule);
  padding: 13px 24px; width: 100%; text-align: left;
  background: none; cursor: pointer; color: inherit; font: inherit;
}
.qrow:hover { background: var(--surface); }
.qrow--good { border-left-color: var(--good); }
.qrow--warn { border-left-color: var(--warn); }
/* Under 70 the row is tinted, not just marked: at that score the parse is
   likely wrong rather than merely unproven. */
.qrow--bad { border-left-color: var(--bad); background: rgba(224, 96, 74, .06); }
.qrow[aria-current="true"] { background: var(--surface-2); }
.qrow__cell { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* Both lines truncate rather than wrap: a queue is scanned down the left edge,
   and a row that grows a second line breaks that edge. */
.qrow__item, .qrow__sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.qrow__item { font-size: 14px; font-weight: 500; }
.qrow__sub { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
.qrow__total { font-family: var(--mono); font-size: 13px; text-align: right; }
.qrow__weak { font-family: var(--mono); font-size: 13px; color: var(--warn); }
/* That column was sized for a field name — "Quantity", "Cost" — and a product
   title in it wrapped to three lines and looked broken. One line, clipped. */
.qrow__weak--name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--sans); color: var(--ink-2);
}
.qrow__weak--none { color: var(--ink-3); }
.qrow__score {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  font-family: var(--mono); font-size: 13px;
}
.qrow__tick { width: 3px; height: 14px; border-radius: 1px; background: var(--ink-3); }
.qrow__tick--good { background: var(--good); }
.qrow__tick--warn { background: var(--warn); }
.qrow__tick--bad { background: var(--bad); }

/* Work that happened without you is collapsed, never hidden. */
.collapsed {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 24px; font-family: var(--mono); font-size: 12px;
  color: var(--ink-3); background: none; border: 0; width: 100%;
  text-align: left; cursor: pointer;
}
.collapsed:hover { color: var(--ink-2); }
.collapsed::-webkit-details-marker { display: none; }
.autofiled { border-top: 1px solid var(--rule); }
.autofiled[open] .collapsed { color: var(--ink-2); }

/* A note that runs the width of the screen rather than sitting in a card —
   the band does, the sentence inside it does not. */
.hint--band {
  margin: 0 calc(-1 * var(--page-pad)); padding: 12px var(--page-pad);
  border-bottom: 1px solid var(--rule); background: var(--surface);
}
/* 60ch, not 78: `ch` is the width of a zero, which in a proportional face is
   wider than the average character — 78ch measured out at 89 characters a
   line. This lands around 72, inside the 80 a line should stay under. */
.hint--band, .pane__foot, .sidebody__why, .honest__body, .gap__body,
.state__body { max-width: 60ch; }

.detail { padding: 24px; display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.detail__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
.detail__name { font: var(--t-section); margin: 0 0 6px; }
.detail__meta { font-family: var(--mono); font-size: 13px; color: var(--ink-2); }
.detail__score { text-align: right; flex: none; }
.detail__n { font-family: var(--mono); font-size: 34px; font-weight: 600; letter-spacing: -.02em; line-height: 1; }
.detail__n--good { color: var(--good); }
.detail__n--warn { color: var(--warn); }
.detail__n--bad { color: var(--bad-ink); }
.detail__capped {
  font: var(--t-label); letter-spacing: var(--t-label-track);
  text-transform: uppercase; color: var(--ink-3); margin-top: 6px;
}

/* Why the score is what it is, said in a sentence rather than implied by a
   colour. */
/* Prose keeps its measure even when the pane stops being a 560px column: at
   1180 the queue stacks and this became a 1120px-wide paragraph. */
.because, .pfield__note { max-width: 60ch; }
.because {
  padding: 14px 16px; border-radius: var(--r-button);
  background: rgba(232, 163, 61, .10); border: 1px solid rgba(232, 163, 61, .36);
  font-size: 13px; line-height: 1.5; color: var(--ink);
}

.pfields { display: flex; flex-direction: column; gap: 8px; }
.pfield {
  display: grid; grid-template-columns: 118px minmax(0, 1fr) 58px;
  gap: 14px; align-items: center;
  padding: 14px; border-radius: var(--r-button);
  background: var(--surface); border: 1px solid var(--rule);
}
.pfield--weak {
  background: rgba(232, 163, 61, .09); border-color: rgba(232, 163, 61, .36);
}
.pfield__lbl {
  font: var(--t-label); letter-spacing: var(--t-label-track);
  text-transform: uppercase; color: var(--ink-3);
}
.pfield__val { font-family: var(--mono); font-size: 14px; }
.pfield--weak .pfield__val { color: var(--warn); }
.pfield__bar { height: 4px; border-radius: 2px; background: var(--surface-3); margin: 7px 0 6px; }
/* Scaled rather than resized. This bar is the one thing on the screen that
   animates — a corrected field fills over 240ms — and animating width relayouts
   the row on every frame. A transform does not touch layout at all. */
.pfield__fill {
  display: block; height: 100%; width: 100%; border-radius: 2px;
  background: var(--good);
  transform-origin: left;
  transform: scaleX(calc(var(--score, 0) / 100));
  transition: transform var(--t-fill) var(--ease);
}
.pfield__fill--warn { background: var(--warn); }
.pfield__fill--bad { background: var(--bad); }
.pfield__note { font-size: 13px; color: var(--ink-2); }
/* Below 70 a field is not weak, it is probably wrong, and it says so in the
   same colour the queue uses for the same band. */
.pfield--bad {
  background: rgba(224, 96, 74, .09); border-color: rgba(224, 96, 74, .36);
}
.pfield--bad .pfield__val, .pfield--bad .pfield__n { color: var(--bad-ink); }
.pfield__n { font-family: var(--mono); font-size: 19px; text-align: right; }
.pfield--weak .pfield__n { color: var(--warn); }

.verdicts { display: flex; gap: 10px; flex-wrap: wrap; }
.verdict {
  flex: 1 1 auto; min-height: 44px; padding: 0 16px; border-radius: var(--r-input);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 500; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--rule); background: none; color: var(--ink-2);
  font-family: var(--sans); text-decoration: none;
}
.verdict:hover { background: var(--surface); color: var(--ink); }
.verdict--yes {
  background: var(--brand); border-color: var(--brand); color: var(--brand-on);
  font-weight: 600;
}
.verdict--yes:hover { background: var(--brand-press); color: var(--brand-on); }
.verdict--fix { background: var(--surface-2); border-color: var(--rule-raised); color: var(--ink); }
.verdict--fix:hover { background: var(--surface-3); }


/* ── proof of postage ───────────────────────────────────────────────────────
   What was read on one side, what it could be proof of on the other. No score
   appears on the right-hand side at all: a percentage on a proposal is a
   number you cannot check, and the choice here is a person's. */
.picker {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px var(--page-pad); background: var(--inset);
  border-bottom: 1px solid var(--rule);
}
/* One row that scrolls, not a block that grows: a run to the Post Office can
   produce fifteen of these and they must not push the receipt off the screen. */
.picker__tabs {
  display: flex; gap: 8px; flex: 1 1 0; min-width: 0;
  overflow-x: auto; padding-bottom: 6px;
  scrollbar-width: thin; scrollbar-color: var(--rule-raised) transparent;
}
.picker__tabs::-webkit-scrollbar { height: 6px; }
.picker__tabs::-webkit-scrollbar-thumb { background: var(--rule-raised); border-radius: 3px; }
.picker__tabs::-webkit-scrollbar-track { background: none; }
/* A scroller ends where the window does, so the last chip is mid-clip — which
   reads as a broken edge rather than as "there is more this way". The fade
   says which it is, and the spacer keeps a chip off the very edge. */
.picker__tabs::after { content: ""; flex: none; width: 6px; }
.picker__tabs {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}
.ptab { flex: none; }
.picker__scan { display: flex; align-items: center; gap: 10px; flex: none; }
.picker__scan .field { width: 210px; }
.ptab {
  display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  padding: 7px 12px; border-radius: var(--r-control);
  border: 1px solid var(--rule); background: var(--surface);
  cursor: pointer; font-family: var(--mono); color: var(--ink-2);
}
.ptab:hover { background: var(--surface-2); color: var(--ink); }
.ptab[aria-selected="true"] {
  background: var(--surface-3); border-color: var(--rule-raised); color: var(--ink);
  box-shadow: inset 0 1px 0 rgba(238, 243, 248, .08);
}
.ptab__when { font-size: 12px; }
.ptab__cost { font-size: 13px; }

.proofs { display: grid; grid-template-columns: 1fr 1fr; }
.pane {
  padding: 24px var(--page-pad); display: flex; flex-direction: column; gap: 16px;
  min-width: 0;
}
.pane + .pane { border-left: 1px solid var(--rule); }
.pane__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
.pane__title { font: var(--t-section); margin: 0 0 6px; }
.pane__meta { font-family: var(--mono); font-size: 13px; color: var(--ink-2); }
.pane__acts { display: flex; gap: 10px; }
.pane__acts .verdict { flex: 0 0 auto; }
.pane__foot {
  margin-top: auto; padding-top: 6px;
  font-size: 13px; color: var(--ink-2); line-height: 1.5;
}

.rfields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.rfield {
  display: grid; grid-template-columns: minmax(0, 1fr) 44px; gap: 10px;
  align-items: center; padding: 14px; border-radius: var(--r-button);
  background: var(--surface); border: 1px solid var(--rule);
}
.rfield__lbl {
  display: block; font: var(--t-label); letter-spacing: var(--t-label-track);
  text-transform: uppercase; color: var(--ink-3);
}
.rfield__val { display: block; font-family: var(--mono); font-size: 14px; margin-top: 5px; }
.pfield--weak .rfield__val { color: var(--warn); }
.pfield--bad .rfield__val { color: var(--bad-ink); }
.rfield__n { font-family: var(--mono); font-size: 19px; text-align: right; }
.pfield--weak .rfield__n { color: var(--warn); }
.pfield--bad .rfield__n { color: var(--bad-ink); }
.min0 { min-width: 0; display: block; }

/* An honest gap, drawn as one. The dashes say the box is empty on purpose. */
.gap {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 16px;
  padding: 18px; border-radius: var(--r-panel);
  border: 1px dashed var(--rule-raised);
}
.gap__box {
  width: 66px; height: 86px; border-radius: 8px; display: block;
  border: 1px solid var(--rule);
  background: repeating-linear-gradient(135deg, #16334f 0 6px, #122c4b 6px 12px);
}
.gap__title { font-size: 15px; font-weight: 500; margin-bottom: 4px; }
.gap__body { font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.gap__form { display: flex; gap: 10px; align-items: center; margin-top: 12px; flex-wrap: wrap; }
.gap__form .field { width: 200px; }
.shot img { display: block; max-width: 100%; border-radius: var(--r-button); border: 1px solid var(--rule); }
.shot figcaption { margin-top: 8px; }

.prop {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px;
  align-items: center; padding: 14px 16px; border-radius: var(--r-button);
  background: var(--surface); border: 1px solid var(--rule);
}
.prop--best { border-color: var(--rule-raised); background: var(--surface-2); }
.prop__item { display: block; font-size: 15px; font-weight: 500; }
.prop__sub { display: block; font-family: var(--mono); font-size: 12px; color: var(--ink-2); margin-top: 3px; }
/* The reason, in the colour of its own confidence. Never a number. */
.prop__why { display: block; font-size: 13px; margin-top: 7px; }
.prop__why--good { color: var(--good); }
.prop__why--warn { color: var(--warn); }
.prop__side { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.prop__cost { font-family: var(--mono); font-size: 13px; color: var(--ink-2); }
.prop__side .verdict { flex: none; }

.state--page { padding: 60px 30px; }


/* ── dense tables ───────────────────────────────────────────────────────────
   Lots, sales, returns and costs are the same object: a sticky head, a spine
   that groups, zebra that holds the row together across eight columns, and a
   density that is decided rather than preferred — 8px compact, 11px default,
   16px roomy, and desktop does not trade density for air. Each table sets its
   own columns in `--cols`. */
:root { --row-pad: 11px; }

.filters {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px var(--page-pad); background: var(--inset);
  border-bottom: 1px solid var(--rule);
}

.gtable { display: block; min-width: 0; }
.gtable__head, .grow {
  display: grid; grid-template-columns: var(--cols);
  gap: 14px; align-items: center;
}
.gtable__head {
  padding: 9px var(--page-pad); margin: 0 calc(-1 * var(--page-pad));
  background: var(--inset); border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; z-index: 3;
  font: var(--t-label); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
}
.grow {
  padding: var(--row-pad) var(--page-pad); margin: 0 calc(-1 * var(--page-pad));
  border-bottom: 1px solid var(--surface-2);
}
.grow--tap { cursor: pointer; }
.grow:nth-child(even) { background: rgba(22, 51, 79, .32); }
.grow--tap:hover { background: var(--surface-2); }
.grow:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

.glead { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* The category, as a mark rather than a word: it is how the eye groups the
   table, and it does not have to be read to do that. */
.gspine { width: 3px; height: 22px; border-radius: 1px; background: var(--ink-3); flex: none; }
.gspine--tcg, .gspine--ebay { background: var(--brand); }
.gspine--sneakers, .gspine--tiktok { background: var(--warn); }
.gspine--tech, .gspine--vinted { background: var(--good); }
.gspine--collectibles, .gspine--discord { background: var(--ink-3); }
.gspine--music { background: var(--tier-2); }
.gspine--packaging { background: var(--brand); }
.gspine--shipping { background: var(--good); }
.gspine--utilities { background: var(--ink-3); }
.gspine--membership { background: var(--warn); }
.gspine--food { background: var(--tier-2); }
.gspine--other { background: var(--rule-raised); }
.gname {
  display: block; font-size: 14px; font-weight: 500; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gsub {
  display: block; font-family: var(--mono); font-size: 11px; line-height: 1.3;
  color: var(--ink-3); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gwhen { font-family: var(--mono); font-size: 13px; color: var(--ink-2); }
.gnum { font-family: var(--mono); font-size: 13px; }
.gnum--soft { color: var(--ink-2); }
/* One line, like .gname and .gsub above. A wrapping cell is taller than its
   neighbours, so one long plan name made its whole row taller than the rest of
   the table — the column is a fixed width and the text has to live inside it.
   Where it truncates, the full string is on the element's title. */
.gtext {
  font-size: 13px; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.ar { text-align: right; }

.gmargin { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.mbar { width: 44px; height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; flex: none; }
.mbar__fill { display: block; height: 100%; }
.mbar__fill--good { background: var(--good); }
.mbar__fill--fair { background: var(--ink-2); }
.mbar__fill--thin { background: var(--warn); }
/* Fixed width, so the percentages line up down the column whatever they say. */
.mpct { font-family: var(--mono); font-size: 13px; width: 52px; text-align: right; }
.mpct--good { color: var(--good); }
.mpct--fair { color: var(--ink-2); }
.mpct--thin { color: var(--warn); }

.gstate { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
/* A right-aligned figure sits flush against its column's right edge, so when a
   text column follows one, the only thing between the number and the word is
   the 14px grid gap — whatever the columns are widened to. Pressed that close
   they read as one field.
   Applied to the whole table rather than to the state cell, because the
   heading above it is not a state cell and the two would part company. */
.gtable--spaced .ar + * { padding-left: 18px; }
.gstate--good { color: var(--good); }
.gstate--warn { color: var(--warn); }
.gstate--bad { color: var(--bad-ink); }
.gstate--note { color: var(--ink-2); }

/* ── grid tables on a phone ───────────────────────────────────────────────
   Nine fixed columns come to about 900px, so at 390px the table ran off the
   side and took the page with it: everything scrolled sideways, and the parts
   that did fit were pushed out of reach along with it.

   Swiping it is not the answer either. Giving the lead column enough room to
   hold a product name puts the last column past 1100px, which is three
   screenfuls of sideways travel to read one row.

   So each row becomes a small record: the thing it is about across the top,
   then every column as a labelled pair. The labels are on the cells because
   the header row is gone here, and "−£3.20" under a product name is not worth
   showing if nothing says it is the postage. */
@media (max-width: 760px) {
  .gtable__head { display: none; }

  /* Five filters at 398px do not fit across 390. They wrap onto a second line
     rather than scrolling, because a filter you cannot see is one you will not
     know to look for. */
  .seg { flex-wrap: wrap; }

  .grow {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 11px 14px; align-items: start;
    padding: 13px var(--page-pad);
  }
  /* What the row is about, across the top. */
  .glead { grid-column: 1 / -1; }

  .grow > [data-label] { display: block; min-width: 0; }
  .grow > [data-label]::before {
    content: attr(data-label);
    display: block; margin-bottom: 3px;
    font: var(--t-label); letter-spacing: .12em; text-transform: uppercase;
    color: var(--ink-3);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* A column right-aligns to line up with the figures above and below it.
     Stacked under its own label there is nothing to line up with, and the
     ragged left edge is harder to read than the ragged right one. */
  .grow > .ar { text-align: left; }
  .gtable--spaced .ar + * { padding-left: 0; }

  /* The margin cell is a bar beside a percentage, so the label takes a line of
     its own and lets the two keep sitting together. */
  .grow > .gmargin { display: flex; flex-wrap: wrap; justify-content: flex-start; }
  .grow > .gmargin::before { flex: 1 0 100%; margin-bottom: 0; }

  /* Nothing is truncated in a card — there is a whole line for it. */
  .grow > .gtext { white-space: normal; overflow: visible; }

  /* The percentage is held at a fixed width so the column lines up down the
     table. In a card there is no column, and the fixed width only strands it
     away from the bar it belongs to. */
  .grow > .gmargin .mpct { width: auto; text-align: left; }

  /* ── rows with actions on them ──────────────────────────────────────────
     A marker, a description and two controls in one line leaves the middle
     about 120px, which turned every service name into three wrapped lines.
     The description takes the width and the controls go underneath.

     Two controls share the second row, one at each end — both inside the
     flexible column, so neither can be pushed off the side. A third would
     stack below rather than being squeezed in beside them. */
  .rule { grid-template-columns: auto minmax(0, 1fr); row-gap: 10px; }
  .rule > :nth-child(n+3) { grid-column: 2; justify-self: start; }
  .rule > :nth-child(3) { grid-row: 2; }
  .rule > :nth-child(4) { grid-row: 2; justify-self: end; }

  /* ── thumbs ─────────────────────────────────────────────────────────────
     44px is the smallest target Apple asks for, and a 13px "Zip" sitting on
     a row boundary is a coin toss between two files. Padding rather than
     size, so nothing looks inflated. */
  .rule a, .rule button, .card__link, .allpick, .split__link {
    display: inline-flex; align-items: center; gap: 8px;
    min-height: 44px; min-width: 44px;
  }
  /* "Zip" is four characters. Tall enough and still only 28px across is half
     a target, and the rows it sits between are other people's files. */
  .chip, .crumb { min-width: 44px; justify-content: center; }
  .checkrow, .rule input[type="checkbox"], .gtable input[type="checkbox"] {
    width: 24px; height: 24px;
  }
  /* A checkbox drawn big enough to hit with a thumb looks clumsy, so the box
     stays 24px and the label around it becomes the target — tapping a label
     toggles its own checkbox, so the whole 44px square works. The negative
     margin keeps the row the height it was. */
  .pick {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px; margin: -10px;
  }
  /* The machine chips and the breadcrumb are how you move around Files, and
     they were 22px and 19px. Padding rather than a bigger font: the row should
     get taller, not louder. */
  .chip, .crumb {
    display: inline-flex; align-items: center; min-height: 40px;
  }
  /* A slider is dragged, not tapped, and 22px of track is a thin thing to
     find with a thumb. */
  input[type="range"] { height: 32px; }
}

/* A table inside a card rather than running the width of the screen: the
   bleed and the sticky head both belong to the full-width kind. */
.gtable__head--inset { margin: 0; padding: 9px 20px; position: static; }
.grow--inset { margin: 0; padding: 11px 20px; }
.grow--inset:last-child { border-bottom: 0; }

.figrow--even { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* A rule: the switch, what it does, and the command it gates. The command is
   shown because a switch with nothing behind it would be a decoration. */
.rule {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 14px; align-items: center;
  padding: 14px 20px; border-bottom: 1px solid var(--surface-2);
}
.rule:last-child { border-bottom: 0; }
.rule__cmd {
  font-family: var(--mono); font-size: 12px; color: var(--ink-3);
  padding: 3px 8px; border-radius: 6px; background: var(--surface-2);
  white-space: nowrap;
}

/* A field and the button that commits it, on one line. This lives in a drawer
   and wraps there, so the gap has to be a row gap as well as a column one —
   without it the button sat directly on the input. */
.editrow {
  display: flex; align-items: center; gap: 10px; margin-top: 12px;
  flex-wrap: wrap;
}
.editrow .field { width: 120px; flex: none; }

.gfoot {
  padding: 14px 0 0; font-family: var(--mono); font-size: 12px; color: var(--ink-3);
}


/* ── the statement ──────────────────────────────────────────────────────────
   Weight carries the structure: a subtotal is heavier than what it sums, and
   the figure the whole page is about is the heaviest thing on it. */
.pl { display: grid; grid-template-columns: minmax(0, 1fr) 396px; }
.stmt { padding: 8px 30px 40px 0; min-width: 0; }
.plside {
  padding: 24px 0 40px 30px; border-left: 1px solid var(--rule);
  display: flex; flex-direction: column; gap: 16px;
  margin-right: calc(-1 * var(--page-pad)); padding-right: var(--page-pad);
}

.stmt__row {
  display: grid; grid-template-columns: minmax(0, 1fr) 132px 96px;
  gap: 14px; align-items: baseline;
}
.stmt__label { min-width: 0; }
.stmt__note { display: block; font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.stmt__amt { font-family: var(--mono); text-align: right; }
.stmt__pct { font-family: var(--mono); font-size: 12px; color: var(--ink-3); text-align: right; }

.stmt__row--head {
  font: var(--t-section); padding: 22px 0 10px;
  border-bottom: 1px solid var(--rule);
}
.stmt__row--head .stmt__note { font: var(--t-meta); color: var(--ink-3); }
.stmt__row--child {
  font-size: 15px; padding: 11px 0 11px 16px;
  border-bottom: 1px solid var(--surface-2);
}
.stmt__row--line {
  font-size: 15px; padding: 13px 0;
  border-bottom: 1px solid var(--surface-2);
}
.stmt__row--subtotal {
  font-size: 16px; font-weight: 600; padding: 13px 0;
  border-bottom: 1px solid var(--rule);
}
/* No separator under the last line: nothing follows it to be separated from. */
.stmt__row--grand {
  font-size: 24px; font-weight: 600; padding: 18px 0;
  letter-spacing: -.015em;
}
.stmt__row--grand .stmt__amt { font-size: 24px; }
.stmt__row--grand .stmt__pct { font-size: 13px; }

.sidebody { padding: 16px 20px; display: flex; flex-direction: column; gap: 14px; }
.sidebody__why { font-size: 13px; line-height: 1.5; color: var(--ink-2); }
.sidelist { display: grid; grid-template-columns: 1fr auto; gap: 9px 14px; }
.sidelist dt {
  font: var(--t-label); letter-spacing: var(--t-label-track);
  text-transform: uppercase; color: var(--ink-3); align-self: center;
}
.sidelist dd { font-family: var(--mono); font-size: 15px; text-align: right; }

/* An honest gap is a product state, so it is drawn as a considered thing
   rather than as an error. */
.honest {
  padding: 18px 20px; border-radius: var(--r-panel);
  background: rgba(232, 163, 61, .10);
  border: 1px solid rgba(232, 163, 61, .36);
}
.honest__title {
  font: var(--t-label); letter-spacing: var(--t-label-track);
  text-transform: uppercase; color: var(--warn); margin-bottom: 8px;
}
.honest__body { font-size: 13px; line-height: 1.55; color: var(--ink); }

/* The side hero is narrower than the one on Today, so its figure is too. */
.heroc__val--side { font-size: 50px; }


/* ── slashd ─────────────────────────────────────────────────────────────────
   The other product, in its own palette. Same structure, so it reads as this
   application rather than a different one. */
.vhead__wordmark { display: block; height: 42px; width: auto; flex: none; }
.chip--quiet {
  background: var(--surface-2); border-color: var(--rule);
  color: var(--ink-2); font-family: var(--mono); font-size: 12px;
}

/* A figure this screen cannot source, drawn as a considered gap rather than
   left as an empty box or filled with a number from nowhere. */

.subs__head, .subrow {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) 120px 110px 110px minmax(0, 1fr);
  gap: 14px; align-items: center;
}
.subs__head {
  padding: 9px 20px; background: var(--inset);
  border-bottom: 1px solid var(--rule);
  font: var(--t-label); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
}
.subrow { padding: 11px 20px; border-bottom: 1px solid var(--surface-2); }
.subrow:last-child { border-bottom: 0; }
.subrow:nth-child(even) { background: rgba(21, 33, 29, .5); }
.subrow__who { display: flex; align-items: center; gap: 12px; min-width: 0; }
.subrow__spine { width: 3px; height: 20px; border-radius: 1px; background: var(--ink-3); flex: none; }
.subrow__spine--active { background: var(--good); }
.subrow__spine--trialing { background: var(--warn); }
.subrow__spine--past_due { background: var(--bad); }
.subrow__spine--canceled { background: var(--rule-raised); }
.subrow__email { font-size: 14px; }
.subrow__plan { font-size: 14px; color: var(--ink-2); }
.subrow__num { font-family: var(--mono); font-size: 13px; }
.subrow__state { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
.subrow__state--active { color: var(--good); }
.subrow__state--trialing { color: var(--warn); }
.subrow__state--past_due { color: var(--bad-ink); }
.subrow__state--canceled { color: var(--ink-3); }


.delta {
  font-family: var(--mono); font-size: 11.5px; font-weight: 500;
  display: inline-flex; align-items: center; gap: 3px;
}
.delta--up { color: var(--good); }
.delta--down { color: var(--bad); }
.delta--flat { color: var(--ink-3); }
.delta__vs { color: var(--ink-3); font-size: 11px; }

.meter { display: block; height: 5px; background: var(--surface-3); border-radius: 999px; overflow: hidden; }
.meter__fill { display: block; height: 100%; background: var(--series-1); border-radius: 999px; }
.meter__fill--good { background: var(--good); }
.meter__fill--warn { background: var(--warn); }
.meter__fill--bad { background: var(--bad); }

/* ── ledger table ─────────────────────────────────────────────────────────── */
.tablewrap { overflow-x: auto; }
.ledger { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.ledger th {
  font-family: var(--narrow); font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3);
  text-align: left; padding: 8px 10px; background: var(--surface-2);
  border-bottom: 1px solid var(--rule); white-space: nowrap; position: sticky; top: 0; z-index: 1;
}
.ledger th button {
  border: 0; background: none; padding: 0; cursor: pointer; color: inherit;
  font: inherit; display: inline-flex; align-items: center; gap: 4px;
}
.ledger th button:hover { color: var(--ink); }
.ledger th[aria-sort] button { color: var(--brand-ink); }
.ledger td {
  padding: 8px 10px; border-bottom: 1px solid var(--rule);
  vertical-align: middle; color: var(--ink-2);
}
.ledger tbody tr { cursor: pointer; transition: background .08s var(--ease); }
.ledger tbody tr:hover { background: var(--surface-3); }
.ledger tbody tr:focus-visible { background: var(--surface-3); outline-offset: -2px; }
.ledger tbody tr[aria-selected="true"] { background: var(--surface-3); }
.num { font-family: var(--mono); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.strong { color: var(--ink); font-weight: 500; }
.pos { color: var(--good); }
.neg { color: var(--bad-ink); }
.muted { color: var(--ink-3); }
.muted2 { color: var(--ink-2); }
.trunc { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.sub { display: block; font-size: 11px; color: var(--ink-3); margin-top: 1px; }

.marginbar { display: inline-flex; align-items: center; gap: 6px; }
.marginbar__track { width: 34px; height: 4px; background: var(--surface-3); border-radius: 999px; overflow: hidden; flex: none; }
.marginbar__fill { display: block; height: 100%; border-radius: 999px; }

.tablefoot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 14px; border-top: 1px solid var(--rule); background: var(--surface-2);
  font-size: 11.5px; color: var(--ink-3);
}

/* ── feed ─────────────────────────────────────────────────────────────────── */
.feed { display: flex; flex-direction: column; }
.feed__item {
  display: grid; grid-template-columns: 52px 1fr auto; gap: 10px; align-items: baseline;
  padding: 9px 14px; border-bottom: 1px solid var(--rule);
}
.feed__item:last-child { border-bottom: 0; }
.feed__time { font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
.feed__body { min-width: 0; font-size: 12.5px; }
.feed__amt { font-family: var(--mono); font-size: 12px; white-space: nowrap; }
@keyframes arrive {
  from { background: rgba(46, 198, 135, .18); }
  to { background: transparent; }
}
.feed__item--new { animation: arrive 1.6s var(--ease) 1; }

/* ── states ───────────────────────────────────────────────────────────────── */
.state { padding: 44px 20px; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.state__title { font-size: 14px; font-weight: 600; color: var(--ink); }
.state__body { font-size: 12.5px; color: var(--ink-3); max-width: 46ch; }
.state__mark { width: 26px; height: 26px; border-radius: var(--r-control); border: 1px solid var(--rule-strong); display: grid; place-items: center; color: var(--ink-3); }

@keyframes shimmer { from { opacity: .5; } to { opacity: 1; } }
.skel { background: var(--surface-3); border-radius: var(--r-control); height: 11px; animation: shimmer 1s var(--ease) infinite alternate; }
.skelrow { display: flex; gap: 10px; padding: 9px 10px; border-bottom: 1px solid var(--rule); align-items: center; }

.errbox { border-color: #5a3630; }
.errbox .panel__head { border-bottom-color: #4a2e2a; }

/* ── drawer ───────────────────────────────────────────────────────────────── */
.scrim {
  position: fixed; inset: 0; background: rgba(6, 7, 10, .66);
  opacity: 0; pointer-events: none; transition: opacity .16s var(--ease); z-index: 40;
}
.scrim[data-open="true"] { opacity: 1; pointer-events: auto; }

.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(460px, 100vw);
  background: var(--surface); border-left: 1px solid var(--rule-strong);
  box-shadow: -18px 0 48px rgba(0, 0, 0, .5);
  transform: translateX(100%); transition: transform .2s var(--ease);
  z-index: 41; display: flex; flex-direction: column;
}
.drawer[data-open="true"] { transform: none; }
.drawer__head {
  display: flex; align-items: center; gap: 10px; padding: 12px 16px;
  border-bottom: 1px solid var(--rule); background: var(--surface-2);
}
.drawer__title { font-size: 14px; font-weight: 600; flex: 1; min-width: 0; }
.drawer__body { padding: 16px; overflow-y: auto; flex: 1; }
.drawer__foot {
  display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--rule);
  background: var(--surface-2);
}

.formgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.formgrid > .wide { grid-column: 1 / -1; }

.preview {
  border: 1px solid var(--rule-strong); border-radius: var(--r-panel);
  background: var(--ground); padding: 12px 14px; display: flex; flex-direction: column; gap: 7px;
}
.preview__row { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; }
.preview__row--total { border-top: 1px solid var(--rule); padding-top: 7px; margin-top: 2px; }
.preview__row span:last-child { font-family: var(--mono); font-variant-numeric: tabular-nums; }

.deflist { display: grid; grid-template-columns: auto 1fr; gap: 7px 16px; font-size: 12.5px; align-items: baseline; }
.deflist dt { color: var(--ink-3); font-family: var(--narrow); text-transform: uppercase; font-size: 10.5px; letter-spacing: .07em; font-weight: 600; }
.deflist dd { margin: 0; font-family: var(--mono); color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }
.deflist dd.txt { font-family: var(--sans); color: var(--ink-2); }

/* ── toast ────────────────────────────────────────────────────────────────── */
.toasts {
  position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
  z-index: 60; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none; width: min(420px, calc(100vw - 24px));
}
.toast {
  pointer-events: auto; display: flex; align-items: center; gap: 10px; width: 100%;
  background: var(--surface-2); border: 1px solid var(--rule-strong);
  border-radius: var(--r-panel); padding: 9px 10px 9px 12px; font-size: 12.5px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
  animation: toastin .18s var(--ease);
}
@keyframes toastin { from { opacity: 0; transform: translateY(8px); } }
.toast__msg { flex: 1; min-width: 0; }

/* ── command palette ──────────────────────────────────────────────────────── */
.cmdk {
  position: fixed; inset: 0; z-index: 70; display: none;
  padding: 12vh 16px 16px; justify-content: center; align-items: flex-start;
  background: rgba(6, 7, 10, .6);
}
.cmdk[data-open="true"] { display: flex; }
.cmdk__panel {
  width: min(520px, 100%); background: var(--surface);
  border: 1px solid var(--rule-strong); border-radius: var(--r-panel);
  box-shadow: 0 24px 64px rgba(0, 0, 0, .6); overflow: hidden;
}
.cmdk__input {
  width: 100%; border: 0; background: none; padding: 13px 15px; font-size: 14px;
  border-bottom: 1px solid var(--rule);
}
.cmdk__input:focus { outline: none; }
.cmdk__list { max-height: 320px; overflow-y: auto; padding: 5px; }
.cmdk__item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 8px 10px; border: 0; background: none; cursor: pointer;
  border-radius: var(--r-control); color: var(--ink-2); font-size: 13px;
}
.cmdk__item[data-active="true"] { background: var(--surface-3); color: var(--ink); }
.cmdk__kind {
  margin-left: auto; font-family: var(--narrow); font-size: 10px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--ink-3);
}
kbd {
  font-family: var(--mono); font-size: 10.5px; background: var(--surface-3);
  border: 1px solid var(--rule-strong); border-bottom-width: 2px;
  border-radius: 3px; padding: 1px 5px; color: var(--ink-2);
}

/* ── charts ───────────────────────────────────────────────────────────────── */
.chart { position: relative; }
.chart svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart__grid line { stroke: var(--rule); stroke-width: 1; }
.chart__axis { font-family: var(--mono); font-size: 10px; fill: var(--ink-3); }
.chart__line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart__endlbl { font-family: var(--mono); font-size: 11px; fill: var(--ink); }
.chart__hit { fill: transparent; cursor: crosshair; }
.chart__cross { stroke: var(--rule-strong); stroke-width: 1; }
.chart__bar { transition: opacity .1s var(--ease); }
.chart__bar:hover { opacity: .82; }

.tip {
  position: absolute; pointer-events: none; opacity: 0;
  background: var(--surface-2); border: 1px solid var(--rule-strong);
  border-radius: var(--r-control); padding: 7px 9px; font-size: 11.5px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .5); z-index: 5; white-space: nowrap;
  transform: translate(-50%, -100%); transition: opacity .1s var(--ease);
}
.tip[data-show="true"] { opacity: 1; }
.tip__k { color: var(--ink-3); font-family: var(--narrow); text-transform: uppercase; font-size: 10px; letter-spacing: .07em; }
.tip__v { font-family: var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }

.legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11.5px; color: var(--ink-2); }
.legend__key { display: inline-flex; align-items: center; gap: 6px; }
.legend__sw { width: 9px; height: 9px; border-radius: 2px; flex: none; }

.stackbar { display: flex; height: 26px; gap: 2px; border-radius: var(--r-control); overflow: hidden; }
.stackbar__seg { position: relative; display: grid; place-items: center; font-family: var(--mono); font-size: 10.5px; }

.sparkline { display: block; width: 100%; height: 24px; overflow: visible; }

/* ── mobile ───────────────────────────────────────────────────────────────── */
.tabbar { display: none; }

@media (max-width: 1180px) {
  /* The hero keeps its own row rather than being squeezed beside two tiles:
     a 60px figure and a 12-week chart need the width more than the tiles do. */
  .figrow { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .figrow > .heroc { grid-column: 1 / -1; }

  /* The two side-by-side reading layouts stack. A fixed 396px column beside a
     statement, or a 560px detail pane beside a queue, does not shrink — it
     overflows, and at phone width the side column sat on top of the figures
     it was meant to sit beside. */
  .pl, .queue, .proofs { grid-template-columns: minmax(0, 1fr); }
  .plside {
    border-left: 0; border-top: 1px solid var(--rule);
    margin-right: 0; padding: 22px 0 0;
  }
  .queue__list { border-right: 0; border-bottom: 1px solid var(--rule); }
  .pane + .pane { border-left: 0; border-top: 1px solid var(--rule); }
  .stmt { padding-right: 0; }
}

@media (max-width: 880px) {
  .grid--2 { grid-template-columns: minmax(0, 1fr); }
  .figrow, .lowrow { grid-template-columns: minmax(0, 1fr); }
  .figcol { flex-direction: row; }
}

@media (max-width: 760px) {
  /* The sidebar becomes the tab bar at the bottom, so the shell is one column
     and the screen header carries the place on its own. */
  .app { grid-template-columns: minmax(0, 1fr); }
  .rail { display: none; }
  .main { --page-pad: 16px; padding: 0 var(--page-pad) 84px; }
  .vhead { padding: 16px var(--page-pad); }
  .vhead h1 { font-size: 21px; }
  .heroc__val { font-size: 46px; }
  .heroc__cur { font-size: 24px; }
  .hero__val { font-size: 34px; }
  .viewhead h1 { font-size: 19px; }
  .figcol { flex-direction: column; }

  .tabbar {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 30;
    background: var(--surface); border-top: 1px solid var(--rule);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .tab {
    border: 0; background: none; cursor: pointer; color: var(--ink-3);
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    /* 11px, not 10: these are nav items you tap, and functional text below
       11px fails on a high-DPI phone whatever the ramp says. The app's own
       bar keeps 10pt because that is the iOS system metric for a tab label,
       rendered by the system at its own hinting. */
    padding: 8px 2px 7px; min-height: 52px; font-size: 11px;
    font-family: var(--narrow); text-transform: uppercase; letter-spacing: .04em; font-weight: 600;
  }
  .tab__slash { width: 14px; height: 2px; background: transparent; transform: skewX(-40deg); }
  .tab[aria-current="page"] { color: var(--brand-ink); }
  .tab[aria-current="page"] .tab__slash { background: var(--brand); }

  /* Ledgers become records: the four cells that matter, labelled, tap for the rest. */
  .ledger, .ledger tbody, .ledger tr, .ledger td { display: block; width: 100%; }
  .ledger thead { display: none; }
  .ledger tbody tr {
    padding: 10px 12px; border-bottom: 1px solid var(--rule);
    display: grid; grid-template-columns: 1fr auto; gap: 2px 10px;
  }
  .ledger td { border: 0; padding: 0; }
  .ledger td[data-label]::before {
    content: attr(data-label) " ";
    font-family: var(--narrow); font-size: 10px; text-transform: uppercase;
    letter-spacing: .06em; color: var(--ink-3); margin-right: 6px;
  }
  .ledger td.cell-lead { grid-column: 1; color: var(--ink); font-weight: 500; }
  .ledger td.cell-lead::before { content: none; }
  .ledger td.cell-fig { grid-column: 2; grid-row: 1; text-align: right; }
  .ledger td.cell-fig::before { content: none; }
  .ledger td.cell-meta { grid-column: 1 / -1; font-size: 11.5px; }
  .ledger td.cell-hide { display: none; }
  .num { text-align: left; }
  .ledger td.cell-fig.num { text-align: right; }
  .trunc { max-width: none; white-space: normal; }
  .formgrid { grid-template-columns: 1fr; }
  .drawer { width: 100vw; top: 8vh; border-left: 0; border-top: 1px solid var(--rule-strong); border-radius: var(--r-panel) var(--r-panel) 0 0; }
  .feed__item { grid-template-columns: 46px 1fr; }
  .feed__amt { grid-column: 2; }

  /* Touch targets: 44px minimum, and 16px inputs so iOS does not zoom on focus. */
  .btn { min-height: 44px; padding: 0 16px; }
  .seg button { min-height: 44px; padding: 0 14px; }
  .field { min-height: 44px; font-size: 16px; }
  select.field { background-position: calc(100% - 14px) 20px, calc(100% - 9px) 20px; }
  .search .field { font-size: 16px; padding-left: 32px; }
  .tab { min-height: 56px; }
  .navitem { min-height: 44px; }
  .ledger tbody tr { min-height: 44px; }
  .cmdk__item { min-height: 44px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ── brand marks ──────────────────────────────────────────────────────────── */
.logo { display: block; border-radius: 6px; flex: none; }
.logo--sm { width: 18px; height: 18px; border-radius: 4px; }

/* ── confidence ───────────────────────────────────────────────────────────
   A score is a judgement, so it is shown as a filled arc plus the number,
   never the number alone. The arc colour is the decision (auto / review /
   too low), which is why it uses status colours and not the accent. */
.conf { display: inline-flex; align-items: center; gap: 8px; }
.conf__dial { width: 30px; height: 30px; flex: none; transform: rotate(-90deg); }
.conf__track { fill: none; stroke: var(--surface-3); stroke-width: 3.5; }
.conf__arc { fill: none; stroke-width: 3.5; stroke-linecap: round; transition: stroke-dasharray .3s var(--ease); }
.conf__num { font-family: var(--mono); font-size: 13px; font-variant-numeric: tabular-nums; }
.conf__lbl { font-size: 11.5px; color: var(--ink-3); }

.candidate { border-bottom: 1px solid var(--rule); padding: 13px 14px; display: flex; flex-direction: column; gap: 10px; }
.candidate:last-child { border-bottom: 0; }
.candidate__top { display: flex; align-items: flex-start; gap: 12px; }
.candidate__who { min-width: 0; flex: 1; }
.candidate__merchant { font-weight: 600; font-size: 13px; }
.candidate__subject { font-size: 11.5px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.candidate__acts { display: flex; gap: 6px; flex-wrap: wrap; }

.parsed {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 1px;
  background: var(--rule); border: 1px solid var(--rule); border-radius: var(--r-control); overflow: hidden;
}
.parsed__cell { background: var(--surface-2); padding: 7px 9px; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.parsed__k {
  font-family: var(--narrow); font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-3); display: flex; align-items: center; gap: 5px;
}
.parsed__v { font-family: var(--mono); font-size: 12.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.parsed__v--weak { color: var(--warn); }
.parsed__bar { height: 2px; background: var(--surface-3); border-radius: 999px; overflow: hidden; }
.parsed__fill { display: block; height: 100%; border-radius: 999px; }

/* ── learned rules and connections ────────────────────────────────────────── */
.ruleitem, .conn {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 12px;
  align-items: start; padding: 11px 14px; border-bottom: 1px solid var(--rule);
}
.ruleitem > .dot, .conn > .dot { margin-top: 6px; }
.ruleitem > .switch { margin-top: 1px; }
.ruleitem > :last-child, .conn > :last-child { align-self: center; }
.ruleitem:last-child, .conn:last-child { border-bottom: 0; }
.ruleitem__name, .conn__name { display: block; font-weight: 600; font-size: 13px; }
.ruleitem__meta, .conn__meta { display: block; font-size: 11.5px; color: var(--ink-3); margin-top: 1px; }
.conn__what { display: block; font-size: 12px; color: var(--ink-2); margin-top: 1px; }

.switch {
  position: relative; width: 36px; height: 20px; border-radius: 999px; flex: none;
  background: var(--surface-3); border: 1px solid var(--rule-strong); cursor: pointer;
  transition: background .14s var(--ease), border-color .14s var(--ease); padding: 0;
}
.switch::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px;
  border-radius: 999px; background: var(--ink-3); transition: transform .14s var(--ease), background .14s var(--ease);
}
.switch[aria-checked="true"] { background: var(--brand); border-color: var(--brand); }
.switch[aria-checked="true"]::after { transform: translateX(16px); background: var(--brand-on); }

.statechip {
  display: inline-flex; align-items: center; gap: 5px; height: 21px; padding: 0 7px;
  border-radius: var(--r-control); border: 1px solid var(--rule-strong);
  background: var(--surface-3); font-size: 11px; color: var(--ink-2); white-space: nowrap;
}
/* Tinted status: the fill and border are the status colour at low opacity and
   the text stays at full strength, which is the only way it holds contrast. */
.statechip--ok, .statechip--good {
  border-color: rgba(46, 198, 135, .38); background: rgba(46, 198, 135, .12);
  color: var(--good);
}
.statechip--wait, .statechip--warn {
  border-color: rgba(232, 163, 61, .38); background: rgba(232, 163, 61, .12);
  color: var(--warn);
}
.statechip--bad {
  border-color: rgba(224, 96, 74, .38); background: rgba(224, 96, 74, .12);
  color: var(--bad-ink);
}
/* Neutral: not a state with a colour, just a state. */
.statechip--note { background: var(--surface-2); color: var(--ink-2); }
.statechip--off { color: var(--ink-3); }

.threshold {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 14px; border-bottom: 1px solid var(--rule); background: var(--surface-2);
}
.threshold__val { font-family: var(--mono); font-size: 15px; color: var(--ink); min-width: 46px; text-align: right; }

.trustbar { display: flex; align-items: center; gap: 8px; }
.trustbar__track { width: 92px; height: 5px; background: var(--surface-3); border-radius: 999px; overflow: hidden; flex: none; }
.trustbar__fill { display: block; height: 100%; border-radius: 999px; }
.trustbar__mark { position: relative; }
.trustbar__mark::after {
  content: ""; position: absolute; top: -3px; width: 1px; height: 11px; background: var(--ink-2);
}

@media (max-width: 760px) {
  .ruleitem, .conn { grid-template-columns: auto minmax(0, 1fr); row-gap: 8px; }
  .ruleitem > :last-child, .conn > :last-child { grid-column: 2; justify-self: start; }
  .candidate__acts .btn { flex: 1 1 auto; justify-content: center; }
}

/* ── deck line ────────────────────────────────────────────────────────────
   A one-line deck under a heading is scanned, not read, so it takes a wider
   measure than body copy would. */
.viewhead p { max-width: 92ch; }

/* ── capped lists ─────────────────────────────────────────────────────────
   Long tables and feeds show about ten rows and scroll in place, so a panel
   never pushes everything beneath it off the screen. The sticky header keeps
   the columns readable while scrolling. */
.tablewrap--cap, .feed--cap {
  flex: 1 1 auto; min-height: 180px; max-height: 448px; overflow-y: auto;
}
.tablewrap--cap:focus-visible, .feed--cap:focus-visible { outline-offset: -2px; }

.ledger--plain tbody tr { cursor: default; }
.ledger--plain tbody tr:hover { background: none; }

/* ── item photo ───────────────────────────────────────────────────────────
   Either a real photo you added, or an honest empty slot that says where one
   would have come from. Nothing here is a stand-in for a product shot. */
.itemphoto { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.itemphoto__img {
  display: block; width: 100%; max-height: 230px; object-fit: contain;
  background: var(--ground); border: 1px solid var(--rule);
  border-radius: var(--r-panel);
}
.itemphoto__empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  height: 132px; border: 1px dashed var(--rule-strong); border-radius: var(--r-panel);
  background: var(--ground); color: var(--ink-3); font-size: 12px;
}
.itemphoto__meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.itemphoto .hint { margin: 0; }

@media (max-width: 760px) {
  .tablewrap--cap, .feed--cap { max-height: 62vh; }
}

/* ── receipt matching ─────────────────────────────────────────────────────
   A proposed match is a claim about two records belonging together, so it
   states its reasoning rather than only its score. */
.matchrow {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 10px; align-items: start;
  padding: 10px 11px; border: 1px solid var(--rule-strong); border-radius: var(--r-control);
  background: var(--surface-2);
}
.matchrow > .dot { margin-top: 6px; }
.matchrow__lead { display: block; font-size: 12.5px; color: var(--ink); font-weight: 500; }
.matchrow__meta { display: block; font-size: 11.5px; color: var(--ink-3); margin-top: 1px; }

.receiptthumb {
  width: 62px; height: 82px; object-fit: cover; flex: none;
  border: 1px solid var(--rule); border-radius: var(--r-control); background: var(--ground);
}

/* ── server-rendered additions ─────────────────────────────────────────────
   The prototype's stylesheet, plus the few things a Django template needs that
   a single-page mock did not. */
a { color: var(--ink); text-decoration-color: var(--rule-strong); }
a.plain { text-decoration: none; color: inherit; }
.ledger tbody tr.linked:hover { background: var(--surface-3); }

.login {
  min-height: 100dvh; display: grid; place-items: center; padding: 24px;
  background: var(--ground);
}
.login__card {
  width: min(380px, 100%); background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--r-panel); padding: 26px;
}
/* The platform's own screen, so the mark is the thing you see first: centred,
   with the name under it, and the form left to behave like a form. No
   border-radius on the mark — the artwork carries its own corners, and a
   second radius over the top only crops them. */
.login__head { text-align: center; margin-bottom: 22px; }
.login__mark { width: 64px; height: 64px; display: block; margin: 0 auto 14px; }
.login h1 { font-size: 23px; font-weight: 700; letter-spacing: -.02em; margin-bottom: 5px; }
/* Scoped to the head rather than `.login p`, which would outrank `.errors` on
   specificity and quietly set the spacing of the sign-in failure too. */
.login__head p { font-size: 13.5px; color: var(--ink-3); margin-bottom: 0; }
.login .field { margin-bottom: 12px; }
/* Under the button, explaining what a passkey is to someone who has only ever
   typed a password here. Quiet enough not to compete with the one control. */
.login__aside {
  font-size: 12px; color: var(--ink-3); margin: 14px 0 0; text-align: center;
  text-wrap: pretty;
}
.login [data-passkey-error][hidden] { display: none; }
.errors { color: var(--bad-ink); font-size: 12.5px; margin-bottom: 12px; }

.bar { display: block; height: 10px; background: var(--surface-3); border-radius: 0 999px 999px 0; }
.bar span { display: block; height: 100%; background: var(--series-1); border-radius: 0 4px 4px 0; }
.catrow { display: grid; grid-template-columns: minmax(88px, 30%) 1fr auto; gap: 10px; align-items: center; }
