/* Alfred — the canon, at the craft level of Apple, Linear and Stripe.
   See .impeccable/surfaces/backend-templates-web-base-html.md for the contract
   this implements, and PRODUCT.md for what may not change.

   Light-first, dark equal. The previous system was dark-committed on the
   grounds that reconciling happens at night; both are first-class now, which
   is Apple's own answer and what the iOS half of an adaptive platform needs
   anyway. Tokens are declared once, values swapped per appearance, so no rule
   below this block ever branches on the theme.

   Every pairing here was computed, not judged: scripts/palette_check.py walks
   each foreground against every ground it is painted on and fails under 4.5:1.
   It reports zero failures for both appearances. */

:root {
  color-scheme: light dark;

  /* ── surfaces ──────────────────────────────────────────────────────────
     Two levels and a hairline, not four. Depth is the grouped card lifting
     off the ground; anything more was carrying no information. */
  --ground: #f5f5f7;         /* the page */
  --surface: #ffffff;        /* a grouped list, a card, a table body */
  --surface-2: #f2f2f4;      /* table header, drawer, row hover */
  --surface-3: #f2f2f4;      /* pressed — same value, see the note below */
  --rule: #d8d8dc;           /* hairline between rows */
  --rule-strong: #c7c7cb;    /* the edge of a section */
  --rule-raised: #c7c7cb;
  --inset: #f5f5f7;          /* rail, sticky heads: chrome sits at ground */

  /* ── ink ───────────────────────────────────────────────────────────────
     Two readable weights, not three. On a near-white ground there is no room
     for a third grey that still clears 4.5:1 — `--ink-3` solved to the same
     value as `--ink-2` — so in light the ramp is two deep and the third level
     of hierarchy is carried by size and weight instead, which is better
     typography than a fourth grey would have been. Dark has the room and
     keeps three. */
  --ink: #1d1d1f;
  --ink-2: #6e6e72;
  --ink-3: #6e6e72;

  /* ── tint ──────────────────────────────────────────────────────────────
     One accent, and it is the only thing that changes between the three
     products. `--brand` is the fill a white label sits on; `--brand-ink` is
     the darker step for the accent as text, because the fill alone misses AA
     on the page ground. */
  --brand: #0071e3;
  --brand-ink: #006cda;
  /* And a third step, for the accent as text on a ground tinted with the
     accent. A quiet button is a 12% wash of the fill with the fill's own
     colour written on it, which is 3.98 light and 3.38 dark: the two are
     the same hue, so the tint that makes the button read as the accent is
     the same thing that takes the contrast away. */
  --brand-on-tint: #0060c4;
  --brand-press: #0062c4;
  --brand-on: #ffffff;
  --series-1: #0071e3;

  /* Status is separate from the tint and never borrows it. */
  --good: #247d52;
  --warn: #9f601a;
  --bad: #c63e42;
  /* The `-ink` steps, for the same reason `--brand-ink` exists: the fill
     misses AA as text, and misses it by more on a ground tinted with the fill
     itself. Measured on `color-mix(--bad 12%, --surface)`, which is what the
     refusal sits on. */
  --bad-ink: #b3282c;
  --good-ink: #1c6a44;

  --tier-1: #4aa87a;
  --tier-2: #247d52;
  --tier-3: #14603b;

  /* ── type ──────────────────────────────────────────────────────────────
     One humanist system face. On Mert's hardware this resolves to SF, which
     is the point: the interface is set in the same face as the operating
     system around it.

     `--mono` is deliberately the same stack. Money still has to align on the
     decimal, and it does — `body` turns on tabular figures globally, so every
     numeral in the product is fixed-width without a single mechanical
     letterform. That replaces IBM Plex Mono outright. */
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui,
          "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --narrow: var(--sans);
  --mono: var(--sans);

  --t-hero: 700 46px/1.05 var(--sans);
  --t-hero-track: -.036em;
  --t-title: 700 29px/1.1 var(--sans);
  --t-section: 600 19px/1.3 var(--sans);
  --t-figure: 700 25px/1.15 var(--sans);
  --t-body: 400 15px/1.5 var(--sans);
  --t-meta: 400 13px/1.45 var(--sans);
  --t-label: 600 11.5px/1.3 var(--sans);
  --t-label-track: .04em;

  /* ── radii ─────────────────────────────────────────────────────────────
     Grouped content is 12, controls are capsules. The old ramp treated
     sharpness as precision; this world reads precision off alignment and
     spacing instead. */
  --r-row: 0px;
  --r-control: 9px;
  --r-input: 9px;
  --r-button: 980px;
  --r-panel: 12px;
  --r-card-phone: 11px;
  --r-capsule: 26px;

  /* ── the functional layer ──────────────────────────────────────────────
     Bars and floating panels only. Glass cannot sample other glass, so it is
     never stacked and never applied to a card. */
  --glass-fill: rgba(255, 255, 255, .62);
  --glass-rim: rgba(255, 255, 255, .7);
  --glass-blur: blur(26px) saturate(1.9);
  --glass-shadow: 0 10px 34px rgba(0, 0, 0, .13),
                  inset 0 1px 0 rgba(255, 255, 255, .8);

  /* A floating layer needs separating from the page behind it, and the two
     appearances need opposite things: on white the shadow does it and a border
     is the redundant half of the generated-card signature; on black the shadow
     is invisible and the hairline is the only thing that works. So the rim is
     a token that goes transparent in light. */
  --float-rim: transparent;
  --shadow-pop: 0 10px 28px rgba(0, 0, 0, .13), 0 2px 6px rgba(0, 0, 0, .07);
  --shadow-float: 0 18px 44px rgba(0, 0, 0, .17), 0 3px 10px rgba(0, 0, 0, .09);

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

  --ease: cubic-bezier(.2, .6, .3, 1);
  --t-settle: 180ms;
  --t-fill: 240ms;
}

/* The dark appearance. Only the values move; no rule below branches. */
@media (prefers-color-scheme: dark) {
  :root:not([data-appearance="light"]) {
    --ground: #000000;
    --surface: #1c1c1e;
    --surface-2: #161618;
    --surface-3: #262628;
    --rule: #38383a;
    --rule-strong: #48484a;
    --rule-raised: #48484a;
    --inset: #000000;

    --ink: #f5f5f7;
    --ink-2: #98989d;
    --ink-3: #909096;

    --brand: #0875e3;
    --brand-ink: #0a84ff;
    --brand-on-tint: #4da3ff;
    --brand-press: #3b9bff;
    --brand-on: #ffffff;
    --series-1: #0a84ff;

    --good: #30d158;
    --warn: #f0b849;
    --bad: #ff6961;
    /* Dark needs no darker step: the fills are already light against every
       ground here, tinted or not. Kept as their own names so a rule can say
       which it means without asking which appearance it is in. */
    --bad-ink: #ff6961;
    --good-ink: #30d158;

    --tier-1: #6fe08f;
    --tier-2: #30d158;
    --tier-3: #1f9e40;

    --float-rim: var(--rule-strong);
    --shadow-pop: 0 10px 28px rgba(0, 0, 0, .55);
    --shadow-float: 0 18px 44px rgba(0, 0, 0, .66);

    --glass-fill: rgba(28, 28, 30, .52);
    --glass-rim: rgba(255, 255, 255, .14);
    --glass-shadow: 0 10px 34px rgba(0, 0, 0, .46),
                    inset 0 1px 0 rgba(255, 255, 255, .16);
  }
}

/* ── product tint ──────────────────────────────────────────────────────────
   The whole of what separates the three products. It used to be three
   palettes; it is one palette and one value, which is a tenth of the system
   doing the same job — you still know which set of books you are in without
   reading the header.

   Set on the document element and again on the app shell, because a boosted
   navigation replaces the body's children and cannot touch an attribute on
   <html>. */
[data-brand="slashd"] {
  /* #12a150 is the mark's green, but a white label on it is 3.37:1. The fill
     is the darker step; --brand-ink keeps the lighter one for text. */
  --brand: #0f8843;
  --brand-ink: #0f7d3e;
  --brand-on-tint: #0a6b34;
  --brand-press: #0f8a44;
  --series-1: #12a150;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-appearance="light"]) [data-brand="slashd"] {
    --brand: #198841;
    --brand-ink: #30d158;
    --brand-on-tint: #4ade80;
    --brand-press: #3ad463;
    --series-1: #30d158;
  }
}

[data-brand="hq"] {
  --brand: #5e5ce6;
  --brand-ink: #4f4dd4;
  --brand-on-tint: #4340b8;
  --brand-press: #4b49c9;
  --series-1: #5e5ce6;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-appearance="light"]) [data-brand="hq"] {
    --brand: #5b59d6;
    --brand-ink: #7d7aff;
    --brand-on-tint: #9b99ff;
    --brand-press: #8f8cff;
    --series-1: #7d7aff;
  }
}

/* Alfred's own screens — sign-in and the portal — take the platform tint,
   which is the Design Made blue until Alfred has a colour of its own. */
[data-brand="alfred"] { }


* { 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: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  /* Every numeral in the product is fixed-width, which is the whole of what
     IBM Plex Mono was here for. Money aligns on the decimal and an order id
     reads character by character, with no mechanical letterform anywhere. */
  font-variant-numeric: tabular-nums;
}

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);
}
/* Flat. The gradient and inset highlight were doing the work a hairline does,
   and on a white ground they read as a smudge across the top of the card. */
.lvl-2 {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-panel);
}
.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) center, calc(100% - 9px) center;
  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(--float-rim); background: var(--surface);
  box-shadow: var(--shadow-pop);
}
.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 {
  /* Centred by margin rather than by `translateX(-50%)`, because the open
     state runs `settle`, and `settle` ends on `transform: none` with a fill
     mode of `both` — so the moment it finished it wiped the centring off and
     left the sheet sitting with its left edge on the middle of the screen,
     half its own width out. An animated transform replaces the element's own;
     there is no combining them. Auto margins cannot be animated away. */
  position: fixed; top: 22vh; left: 0; right: 0; margin-inline: auto;
  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: var(--shadow-float);
}
.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: color-mix(in srgb, var(--brand) 12%, transparent); }
.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: color-mix(in srgb, var(--warn) 16%, transparent); 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. */
/* Content height, not viewport height. The cards are three rows of text and
   they were being laid on a 100dvh grid whose 1px-gap separator colour then
   showed as four fifths of a bare page. The book's portal is the grid and
   nothing else, on the page's own ground. */
.split {
  position: relative; background-color: var(--rule);
  display: grid; gap: 1px; grid-template-columns: 1fr; align-content: start;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
@media (min-width: 620px) {
  /* `auto-fit`, so a fourth and a fifth product need no layout change. */
  .split { grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); }
}

.split__half {
  background: var(--surface); text-decoration: none; min-width: 0;
  padding: 30px 26px 34px;
  /* Three fixed rows — mark, name, description — top-aligned. The old version
     bottom-aligned a lockup carrying its own wordmark, so Design Made's tall
     one and Slashd's wide one landed at different heights and the marks did
     not line up across the row. */
  display: grid; grid-template-rows: auto auto 1fr; gap: 14px;
  align-content: start; min-height: 220px;
  transition: background-color .15s var(--ease);
}
.split__half:hover { background: var(--surface-2); }
.split__half:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; }
.split__name {
  font-size: 20px; font-weight: 600; letter-spacing: -.018em; color: var(--ink);
  align-self: center;
}
/* 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. */




/* 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; }

/* 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 {
  width: 48px; height: 48px; border-radius: 12px; display: block; object-fit: cover;
}
/* 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. */

/* 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;
  /* A top padding, not zero. At 390 the row sat hard against the line above
     it, which the detector reads as cramped and a thumb reads as one target. */
  padding: 10px 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; }

/* Describing the stock a sale came out of, for anything bought before this
   existed. Its own line inside the row rather than a column beside it: it is a
   six-field form, and `.salerow` is a flex row that would otherwise squeeze it
   against the picker. Folded away, because it is the answer for a backlog and
   the ordinary move on this row is still the picker. */
/* `0 0 100%` and not `1 1 100%`: shrinkable, it squeezed onto the end of the
   row beside "Not a sale" whenever the summary was short enough to fit, so the
   same control sat on its own line in one row and inline in the next. */
.backfill { flex: 0 0 100%; border-top: 1px solid var(--surface-2); margin-top: 4px; }
/* Collapse the form explicitly rather than trusting the user agent to do it.
   As a flex item the <details> box stayed 42px tall while its form still laid
   out at 327px and spilled over the rows beneath — the detector caught it as
   a label sitting 100% behind an opaque row. Stating it removes the
   dependency on how any particular browser implements the details slot. */
.backfill:not([open]) > form { display: none; }
.backfill .collapsed { padding: 12px 0; }
.backfill form { max-width: 620px; padding-bottom: 4px; }
.backfill .field { min-width: 0; width: 100%; }

.split__what { font-size: 13.5px; line-height: 1.45; color: var(--ink-2); }

/* 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-radius: var(--r-panel, 10px); background: var(--surface-2);
  font-size: 13px;
}
.outcome--good .outcome__what { color: var(--good); }
.outcome--warn .outcome__what { color: var(--warn); }
.outcome--bad .outcome__what { color: var(--bad); }
.outcome--note .outcome__what { color: var(--brand-ink); }
.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); }

/* Under the strip, not over it. Absolutely positioned at the top right it
   landed on top of the third card, which is a real problem the moment a
   product's description runs to two lines. */
.split__bar {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 16px; padding: 18px 30px;
}
.split__link {
  font-family: var(--mono); font-size: 12px; font-weight: 400; color: var(--ink-3);
  background: none; border: 0; padding: 0; cursor: pointer; text-decoration: none;
}
.split__link:hover { color: var(--ink); }

/* A phone gets them stacked, each still its own world. */
@media (max-width: 760px) {
  .split__half { padding: 22px 20px 24px; min-height: 0; }
  .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: flex-end; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
}
/* A name over a sentence. It was a row with a hairline between the two, which
   put the sentence beside the word naming the page instead of under it. */
.vhead__left { min-width: 0; }
.vhead h1 {
  /* Full ink. It was `--ink-2`, which made the one word naming the page
     quieter than the rows under it. */
  font: var(--t-title); letter-spacing: -.015em; color: var(--ink);
  margin: 0;
}
.vhead__sub { margin: 3px 0 0; font-size: 13px; color: var(--ink-2); }
.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 ─────────────────────────────────────────────────────────────── */
/* Capsules, and intrinsic height. The fixed 30px box with no vertical padding
   gave a thumb nothing to aim at above or below the label; padding does the
   sizing now and the phone override only has to raise the minimum. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 32px; padding: 7px 14px; border-radius: var(--r-button);
  border: 1px solid var(--rule-strong); background: var(--surface);
  color: var(--ink); cursor: pointer; white-space: nowrap;
  font-size: 13.5px; font-weight: 500; text-decoration: none; line-height: 1.2;
  transition: background .1s var(--ease), border-color .1s var(--ease);
}
.btn:hover { background: var(--surface-2); border-color: var(--rule-raised); }
.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); }
/* The quiet button is the tint at low weight rather than a grey outline: it is
   still an action, and a bordered grey pill beside a filled one reads as two
   ranks of the same thing. */
.btn--quiet {
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  border-color: transparent; color: var(--brand-on-tint); font-weight: 500;
}
.btn--quiet:hover { background: color-mix(in srgb, var(--brand) 20%, transparent); }
.btn--danger { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 42%, transparent); }
.btn--danger:hover { background: color-mix(in srgb, var(--bad) 12%, transparent);
  border-color: color-mix(in srgb, var(--bad) 58%, transparent); }
.btn[disabled] { opacity: .42; cursor: not-allowed; }
.btn[disabled]:hover { background: var(--surface); 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);
}

/* Intrinsic height with real vertical padding, rather than a fixed 30px box
   with none. The old shape read as cramped at 16px on a phone and gave a
   thumb nothing to aim at above or below the text. */
.field {
  min-height: 34px; padding: 7px 9px; border-radius: var(--r-input);
  border: 1px solid var(--rule-strong); background: var(--surface);
  color: var(--ink); font-size: 14px; 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) center, calc(100% - 9px) center;
  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; }

/* 11.5px, not 10.5px. The old value sat under the 11px floor for functional
   text, which is the one detector rule this project deliberately leaves on and
   the P1 the audit raised. Sentence case rather than uppercase: a field label
   is read, not scanned. */
.lbl {
  display: block; font: var(--t-label); text-transform: none;
  letter-spacing: 0; color: var(--ink-2); 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: color-mix(in srgb, var(--warn) 40%, transparent);
  background: color-mix(in srgb, var(--warn) 12%, transparent); 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 ───────────────────────────────────────────────────────────────── */
/* 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. */
.stack > .panel { flex: none; }

/* 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, .heroc {
  background: var(--surface); border-radius: 14px; padding: 18px 20px 14px;
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.hero__l, .heroc__lbl { font-size: 12.5px; font-weight: 600; color: var(--ink-2); margin: 0; }
.hero__f, .heroc__fig {
  font-size: 46px; font-weight: 700; letter-spacing: -.036em; line-height: 1.05;
  font-variant-numeric: tabular-nums; display: flex; align-items: baseline; gap: 1px;
}
.hero__f em, .heroc__cur {
  font-style: normal; font-size: 28px; font-weight: 600; color: var(--ink-2);
}
.hero__m, .heroc__meta {
  font-size: 12.5px; color: var(--ink-2);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.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; }

/* The comp's tile, and the `.tile__*` names the existing templates use are
   aliased onto it — six templates get the new shape without their markup
   changing, which is the point of aliasing rather than rewriting. */
.tile {
  background: var(--surface); border-radius: var(--r-panel); padding: 14px 16px;
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.tile b, .tile__lbl {
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--ink-2);
  text-transform: none; letter-spacing: 0;
}
.tile .v, .tile__val {
  font-size: 25px; font-weight: 700; letter-spacing: -.028em;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.tile .v u, .tile__unit {
  text-decoration: none; font-size: 15px; font-weight: 600; color: var(--ink-2);
}
.tile p, .tile__foot { margin: 0; font-size: 11.5px; color: var(--ink-2); }
.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. */
/* `stretch`, not `flex-end`. Each slot is a two-row grid split at the zero
   line, so it has to be given the full height to divide — ending it at the
   baseline collapsed every slot to nothing and the chart disappeared. The
   book's height and spacing, this file's alignment. */
.bars { display: flex; align-items: stretch; gap: 3px; height: 74px; margin-top: 13px; }
.bars__slot { flex: 1; display: grid; grid-template-rows: var(--zero, 100%) 1fr; min-width: 0; }
/* The book draws these as the tint at low opacity with the current week at
   full. Kept on this markup rather than the book's, because these bars also
   have to sit a week below the zero line and the book's never had to. */
.bars__bar { grid-row: 1; align-self: end; border-radius: 2px;
  background: var(--brand); opacity: .42; }
.bars__bar--down { grid-row: 2; align-self: start; background: var(--bad); opacity: .5; }
.bars__bar--now { opacity: 1; }
.axis { font-size: 11.5px; color: var(--ink-3); margin: 7px 0 0; }

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

.card { overflow: hidden; min-width: 0; }
.lvl-1.card, .lvl-2.card { background: var(--surface); border: 0; border-radius: var(--r-panel); }
.card__head {
  padding: 12px 16px 6px; 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-size: 12.5px; font-weight: 600; color: var(--ink-2); margin: 0; }
.card__note { font-size: 12.5px; color: var(--ink-3); }
.card__link { font-size: 13px; color: var(--brand-ink); text-decoration: none; }
.card__link:hover { text-decoration: underline; }

.need {
  padding: 11px 16px; border-bottom: 1px solid var(--rule);
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 5px 14px; align-items: center;
}
.need > .dot { display: none; }
.need:last-child { border-bottom: 0; }
.need__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.need__title { font-size: 15.5px; font-weight: 500; letter-spacing: -.008em; }
.need__why { font-size: 12.5px; color: var(--ink-2); }
.need__amt {
  font-size: 15.5px; font-weight: 500; color: var(--ink); text-align: right;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* 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 color-mix(in srgb, var(--ink) 8%, transparent);
}
.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. */
/* The bar, under the rule it gates. It used to be a strip across the top of
   the inbox at the full width of the page; here it is a control belonging to
   the row above it, so it is inset to the rule's text column and sized like
   part of that row rather than like a page header. */
.thresh {
  display: flex; flex-direction: column; gap: 8px; min-width: 0;
  max-width: 460px; margin: -4px 20px 14px 68px;
  padding: 12px 14px; border-radius: var(--r-panel);
  background: var(--surface-2);
}
.thresh__top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.thresh__val { font-size: 19px; font-weight: 600; letter-spacing: -.02em; }
.thresh__of { font-size: 12.5px; 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 {
  margin: 0; font-size: 12px; color: var(--ink-2);
  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: color-mix(in srgb, var(--brand) 35%, transparent); }
.thresh__range {
  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(--surface-2);
  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(--surface-2);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .5); cursor: inherit;
}
/* A track of its own. It had none because the histogram behind it was the
   thing you read the handle against; with the histogram gone the control was
   a handle floating on nothing. */
.thresh__range::-webkit-slider-runnable-track {
  height: 4px; border-radius: 2px; background: var(--rule-strong);
}
.thresh__range::-moz-range-track {
  height: 4px; border-radius: 2px; background: var(--rule-strong);
}


/* 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. */
/* The book's proportions: two groups of comparable width with the page's gap
   between them, not a fluid list against a fixed 560px column. At 1440 the
   fixed one left the parse narrow enough that an order number wrapped. */
.queue { display: grid; gap: 14px; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
.queue__list { min-width: 0; }
/* No left border. The tick at the end of the row carries the tone instead, so
   no information is lost and no row is enclosed by a coloured edge. */


/* Under 70 the row is tinted, not just marked: at that score the parse is
   likely wrong rather than merely unproven. */
/* 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. */
/* 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. */

/* 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. */
/* A note that spans the page under whatever is above it. The bottom margin is
   not optional: without it the band's own surface sits flush against the next
   card and the two read as one panel with a seam through it. */
.hint--band {
  margin: 0 calc(-1 * var(--page-pad)) 14px; 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 { max-width: 60ch; }

/* `.detail` is the parse group itself now. It used to be a padded column
   holding five blocks; the book has one group, so the only thing left to say
   about it is that it sits at the top of its column rather than stretching to
   the queue's height. */
.detail { align-self: flex-start; 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;
}


/* 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. */
/* 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. */

.verdicts { display: flex; gap: 9px; 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 ───────────────────────────────────────────────────────
   The queue on one side, the scan on the other. The tab strip, the per-field
   score cards and the proposal cards are gone: three components that existed
   nowhere else in the product, to answer one question the book answers with
   the rows it uses everywhere. */

.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, var(--surface-2) 0 6px,
    var(--surface-3) 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; }



/* ── 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, .tbl {
  display: block; min-width: 0; background: var(--surface);
  border-radius: var(--r-panel); overflow: hidden;
}
.gtable__head, .grow {
  display: grid; grid-template-columns: var(--cols);
  gap: 14px; align-items: center;
}
.gtable__head {
  padding: 10px 16px;
  background: var(--surface-2); border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; z-index: 3;
  font: var(--t-label); letter-spacing: var(--t-label-track); text-transform: uppercase;
  color: var(--ink-2);
}
/* A hairline between rows and nothing else. The zebra stripe it replaced was a
   hardcoded navy, so on a white ground it was a grey bar every other row. */
/* A hairline between rows and nothing else, and the row sits inside the card
   rather than bleeding past it — the negative margin that used to pull it to
   the page edge is what made this a ledger rather than a panel. */
.grow {
  padding: var(--row-pad) 16px; min-height: 54px;
  border-bottom: 1px solid var(--rule);
}
.grow--tap { cursor: pointer; }

.grow--tap:hover { background: var(--surface-2); }
.grow:last-child { border-bottom: 0; }
.grow:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

.glead { display: flex; align-items: center; gap: 12px; min-width: 0; }
.gname {
  display: block; font-size: 13.5px; font-weight: 500; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gsub {
  display: block; font-size: 11.5px; line-height: 1.3;
  color: var(--ink-2); 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); }

/* A dot and a word. Two channels, no container: a tinted capsule on every row
   of every table was the generated-UI tell, and it made a state that is merely
   true look urgent. The neutral state takes a hollow ring rather than a filled
   dot, so the shape carries meaning too and the row still reads with the
   colour removed. */
.gstate {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--ink-2); white-space: nowrap;
}
.gstate::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: currentColor;
}
.gstate:not([class*="gstate--"])::before,
.gstate--note::before { background: none; box-shadow: inset 0 0 0 1.5px currentColor; }
/* `::first-letter` applies to block containers only, and this is an inline
   flex box — it has never done anything. The chips are written capitalised
   instead, which is what the rule was pretending to do. */
/* 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 { position: static; }
.grow--inset { padding: 11px 16px; }
.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) 360px; gap: 18px;
  padding: 0 0 40px; }
.stmts { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.stmt { background: var(--surface); border-radius: var(--r-panel); overflow: hidden;
  min-width: 0; padding: 4px 0 6px; }
.stmt__h { padding: 12px 16px 4px; font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.stmt--tot { outline: 2px solid var(--brand); outline-offset: -2px; padding: 0; }
.plside { display: flex; flex-direction: column; gap: 12px; padding: 0 0 40px; }

.stmt__row {
  display: grid; grid-template-columns: minmax(0, 1fr) 118px 54px;
  gap: 12px; align-items: baseline; padding: 9px 16px; font-size: 14px;
}
.stmt__label { min-width: 0; }
.stmt__note { display: block; font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.stmt__amt { text-align: right; font-variant-numeric: tabular-nums; }
.stmt__pct { font-size: 12.5px; color: var(--ink-3); text-align: right;
  font-variant-numeric: tabular-nums; }

.stmt__row--child { padding-left: 28px; }
.stmt__row--line { border-top: 1px solid var(--rule); }
.stmt__row--subtotal {
  font-size: 15.5px; font-weight: 600; border-top: 1px solid var(--rule);
}
/* No separator under the last line: nothing follows it to be separated from. */
.stmt__row--grand {
  font-size: 18px; font-weight: 700; padding: 15px 16px;
  letter-spacing: -.015em;
}
.stmt__row--grand .stmt__amt { font-size: 18px; }
.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 { background: color-mix(in srgb, var(--warn) 12%, var(--surface)); }
.honest .gp__h { color: var(--warn); }
.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: var(--t-label-track); 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 ───────────────────────────────────────────────────────────────── */

@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: color-mix(in srgb, var(--bad) 42%, transparent); }
.errbox .panel__head { border-bottom-color: color-mix(in srgb, var(--bad) 30%, transparent); }

/* ── 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(--float-rim);
  box-shadow: var(--shadow-float);
  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);
}

/* The Edit sheet: the drawer given the book's shape. A centred panel with the
   email on one side and what it is teaching on the other, because pointing at
   an answer in a 460px column is pointing at a word wrap. `translate` is set
   as its own property so the centring and the entrance transform compose
   instead of the second one cancelling the first. */
.drawer--sheet {
  top: 50%; left: 50%; right: auto; bottom: auto;
  translate: -50% -50%;
  width: min(880px, calc(100vw - 32px));
  /* Tall enough for a long email and no taller. A fixed height left the pane
     beside a short one half empty. */
  max-height: min(86dvh, 760px);
  border: 1px solid var(--float-rim); border-left: 1px solid var(--float-rim);
  border-radius: 14px; overflow: hidden;
  transform: translateY(16px); opacity: 0;
  transition: transform .2s var(--ease), opacity .16s var(--ease);
}
.drawer--sheet[data-open="true"] { transform: none; opacity: 1; }
.sheet__panes {
  display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  flex: 1; min-height: 0;
}
.sheet__pane {
  padding: 16px 18px; display: flex; flex-direction: column; gap: 12px;
  overflow-y: auto; min-width: 0;
}
.sheet__pane--mail { border-right: 1px solid var(--rule); }
/* On a phone it is a sheet up from the bottom, one pane above the other,
   which is the phone frame in the book. */
@media (max-width: 760px) {
  .drawer--sheet {
    top: auto; bottom: 0; left: 0; translate: none;
    width: 100vw; max-height: 92dvh;
    border-radius: 16px 16px 0 0;
    transform: translateY(100%);
  }
  /* One scroll, not two. Side by side each pane owns its own overflow; stacked
     they are one column of content and two scrollers in a column is a thing
     you fight rather than read. */
  .sheet__panes { grid-template-columns: 1fr; overflow-y: auto; }
  .sheet__pane { overflow: visible; }
  .sheet__pane--mail { border-right: 0; border-bottom: 1px solid var(--rule); }
}

.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); border: 1px solid var(--float-rim);
  border-radius: var(--r-panel); padding: 9px 10px 9px 12px; font-size: 12.5px;
  box-shadow: var(--shadow-pop);
  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(--float-rim); border-radius: var(--r-panel);
  box-shadow: var(--shadow-float); 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); border: 1px solid var(--float-rim);
  border-radius: var(--r-control); padding: 7px 9px; font-size: 11.5px;
  box-shadow: var(--shadow-pop); 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: 11px 18px; }
  .seg button { min-height: 44px; padding: 0 14px; }
  .field { min-height: 44px; font-size: 16px; padding: 11px 12px; }
  .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); }

/* A state is a dot and a word here too. The capsule these used to be is the
   thing the rest of the product stopped doing, and a page of them — six
   integrations, seven rules — was the clearest case of it. */
.statechip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--ink-2); white-space: nowrap;
}
.statechip::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: currentColor;
}
.statechip--ok, .statechip--good { color: var(--good); }
.statechip--wait, .statechip--warn { color: var(--warn); }
.statechip--bad { color: var(--bad); }
.statechip--note { color: var(--ink-2); }
/* Nothing set, and nothing wrong with that: a hollow ring rather than a filled
   dot, so the neutral state is a different shape as well as a different
   colour. */
.statechip--off { color: var(--ink-3); }
.statechip--off::before,
.statechip:not([class*="statechip--"])::before {
  background: none; box-shadow: inset 0 0 0 1.5px currentColor;
}

.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; }
}

/* Long enough to be seen, short enough not to be a thing that happens to you.
   The field may be scrolled out of sight above the email, which is why the
   strip says where it went as well. */
.field--took {
  border-color: var(--good);
  box-shadow: 0 0 0 1px var(--good);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}

/* ── 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; }


/* A grouped-list row that is also a control. The queue rows are buttons —
   picking one swaps the parse pane — and a <button> brings its own font,
   background and alignment that the row has to put back. */
.r--tap {
  width: 100%; text-align: left; border: 0; background: none;
  color: inherit; font: inherit; cursor: pointer;
}
.r--tap:hover { background: var(--surface-2); }
/* 8%, not 12%. The row's quiet sub-line is `--ink-2`, which lands at 4.31 on
   a 12% wash in the light appearance; this keeps the selection obvious and the
   line under it readable. */
.r--tap[aria-current="true"] { background: color-mix(in srgb, var(--brand) 8%, transparent); }
.r--tap:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/* ══ the comp's component set ═══════════════════════════════════════════════
   Ported from the direction book rather than re-derived, so a screen rebuilt
   onto these pieces cannot drift from the comp by transcription. The book's
   short token names are remapped onto this stylesheet's, so both appearances
   come for free.

   Only what this file did not already own. `.btn`, `.tile`, `.hero`, `.bars`
   and `.hint` already exist and were brought to the same shape in the
   component pass; the iOS glass bar and the inlined logo data URIs are not
   ported at all, because the web build has a solid bar and real static files.

   Nothing below is used by a template yet. The screens move onto it one at a
   time, which is why this is additive and why every screen still renders. */

/* ── grouped list (the core idiom) ── */
.gp {background:var(--surface);border-radius:12px;overflow:hidden}
.gp__h {padding:11px 16px 9px;font-size:12.5px;font-weight:600;color:var(--ink-2);
  letter-spacing:-.005em}
.r {display:grid;grid-template-columns:1fr auto;gap:5px 14px;align-items:center;
  padding:11px 16px;position:relative;min-width:0}
.r+.r::before, .gp__h+.r::before {content:"";position:absolute;left:16px;right:0;top:0;
  height:1px;background:var(--rule)}
.r__m {min-width:0;display:flex;flex-direction:column;gap:2px}
/* A group header that carries a state on its right. Three templates were
   writing the same three declarations inline. */
.gp__h--split {display:flex;justify-content:space-between;align-items:center;gap:12px}
/* Two columns of cards, each column a stack. The book uses it on Connections
   and the P&L; it was an inline style on one of them and absent on the other. */
.cols2 {
  display: grid; gap: 14px;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.colstack { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
@media (max-width: 980px) { .cols2 { grid-template-columns: minmax(0, 1fr); } }
/* A scheduled job has no switch, so its row starts at the text rather than
   leaving a 44px hole where every other rule keeps one. */
.rule--job { grid-template-columns: minmax(0, 1fr) auto; }
/* A process row: what it is, what state it is in, its log, and its switch. */
.rule--proc { grid-template-columns: minmax(0, 1fr) auto auto auto; }
/* A row opened inside a group, and the panel that grows under it. The book
   tints the open row and puts the form in the group's own rows beneath. */
.r--open { background: color-mix(in srgb, var(--brand) 8%, transparent); }
.r--sub { padding-top: 4px; padding-bottom: 12px; }
.r--sub .r__m { width: 100%; }
/* Both answers on one line: draw it from stock, or say it is not a sale.
   They are two forms, so the row that holds them is the flex container and
   each form lays out as its contents. */
.salepick { display: flex; gap: 9px; align-items: center; flex-wrap: wrap;
  min-width: 0; width: 100%; }
.salepick .field { width: auto; min-width: 240px; flex: 1 1 240px; }
/* Proof of postage: the queue, and the scan beside it. */
.proofs { display: grid; gap: 14px; grid-template-columns: minmax(0, 1fr) 300px;
  align-items: start; }
@media (max-width: 980px) { .proofs { grid-template-columns: minmax(0, 1fr); } }
.r__s--quiet { color: var(--ink-3); }
.scanform { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  margin-bottom: 14px; }
.scanform .field { width: auto; flex: 1 1 260px; min-width: 0; }
.scanshot { width: 100%; border-radius: 9px; display: block; }
.scanshot--none {
  aspect-ratio: 3 / 4; background: var(--ground); display: flex;
  flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; padding: 18px; text-align: center;
  font-size: 12px; color: var(--ink-3); line-height: 1.5;
}
.scanshot--none i { width: 30px; height: 30px; border-radius: 7px;
  border: 2px dashed var(--rule-strong); }
/* The power cell: two buttons side by side in the row's last column. */
.gpower { display: flex; gap: 6px; justify-content: flex-end; min-width: 0; }
/* A cell that carries a state and a date qualifying it. The two are separate
   things: the chip is what the subscription is, the quiet text is when. */
.gcell { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.gqual { font-size: 11.5px; color: var(--ink-3); white-space: nowrap; }

.need__why--quiet { color: var(--ink-3); font-size: 12px; margin-top: 2px; }
/* The empty state. Four templates have been writing `<div class="empty">` at
   it since the port and nothing was styling it, so it rendered as unpadded
   prose running off the right edge of its card on a phone. */
.empty {
  background: var(--surface); border-radius: 12px; padding: 38px 20px;
  text-align: center; display: flex; flex-direction: column; gap: 5px;
  align-items: center;
}
.empty i {
  width: 34px; height: 34px; border-radius: 50%;
  border: 2px dashed var(--rule-strong); margin-bottom: 5px;
}
.empty b { font-size: 15.5px; font-weight: 600; color: var(--ink); }
.empty p { margin: 0; font-size: 13px; color: var(--ink-2); max-width: 42ch; }
/* One full-width cell, for a row that is a note or a set of buttons rather
   than a thing with a figure beside it. */
.r--full {grid-template-columns:1fr}
/* A parsed value. Smaller than a row title because the label above it is
   already doing the naming. */
.r__t--val {font-size:14.5px}
.r__t {font-size:15.5px;font-weight:500;letter-spacing:-.008em;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.r__s {font-size:12.5px;color:var(--ink-2);display:flex;align-items:center;gap:6px;
  flex-wrap:wrap;min-width:0}
.r__e {display:flex;align-items:center;gap:12px}
.st {display:inline-flex;align-items:center;gap:5px}
.st i {width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.st--w {color:var(--warn)}
.st--g {color:var(--good)}
.st--b {color:var(--bad)}
.st--n {color:var(--ink-3)}
.money {font-size:15.5px;font-weight:500;font-variant-numeric:tabular-nums;
  letter-spacing:-.008em;white-space:nowrap}
.chev {color:var(--ink-3);font-size:17px;line-height:1}
.btn--q {background:color-mix(in srgb, var(--brand) 12%, transparent);color:var(--brand-on-tint)}
/* No wash behind it, so the ordinary text step is enough. */
.btn--x {background:none;color:var(--brand-ink);padding:8px 4px}
/* `--brand-ink`, not `--brand`: the fill is 4.2 as 14.5px text on a panel and
   4.0 in dark, which is a link that fails AA on every page carrying one. */
.lnk {background:none;border:0;color:var(--brand-ink);font:inherit;font-size:14.5px;cursor:pointer;padding:4px}
/* ── the table (columns that line up, not a card grid) ── */
.tbl {background:var(--surface);border-radius:12px;overflow:hidden;min-width:0}
.tbl__scroll {overflow-x:auto}
.tbl__g {display:grid;min-width:0}
.th, .td {padding:9px 12px;font-size:13px;min-width:0;display:flex;align-items:center;gap:7px}
.th {font-size:11.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink-2);background:var(--surface-2);position:sticky;top:0}
.td {border-top:1px solid var(--rule);min-height:54px;overflow:hidden}
.lead {flex-direction:column;align-items:flex-start;gap:1px;justify-content:center}
.lead b {font-size:13.5px;font-weight:500;max-width:100%;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.lead span {font-size:11.5px;color:var(--ink-2);max-width:100%;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.stt {display:inline-flex;align-items:center;gap:6px;font-size:13px;white-space:nowrap;
  color:var(--ink-3)}
.stt i {width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
.stt--g {color:var(--good)}
.stt--w {color:var(--warn)}
.stt--b {color:var(--bad)}
.stt--n {color:var(--ink-3)}
.stt--n i {background:none;border:1.5px solid currentColor}
.mb {width:42px;height:4px;border-radius:2px;background:var(--rule);overflow:hidden;flex:none}
.mb i {display:block;height:100%;background:var(--good)}
.mb i.w {background:var(--warn)}
.mb i.b {background:var(--bad)}
.foot {font-size:12.5px;color:var(--ink-2);padding:2px 4px}
.mut {color:var(--ink-3)}
.hero__l {font-size:12.5px;font-weight:600;color:var(--ink-2);margin:0}
.hero__f {font-size:46px;font-weight:700;letter-spacing:-.036em;line-height:1.05;
  font-variant-numeric:tabular-nums;display:flex;align-items:baseline;gap:1px}
.hero__f em {font-style:normal;font-size:28px;font-weight:600;color:var(--ink-2)}
.hero__m {font-size:12.5px;color:var(--ink-2);display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.neg {color:var(--bad);font-weight:500}
.bars i.now {opacity:1}
.bars i.dn {background:var(--bad);opacity:.5}
.tiles {display:grid;gap:12px;grid-template-columns:repeat(2,1fr)}
.tile .v {font-size:25px;font-weight:700;letter-spacing:-.028em;font-variant-numeric:tabular-nums}
.tile .v u {text-decoration:none;font-size:15px;font-weight:600;color:var(--ink-2)}
/* One column until there is room for two. The port dropped the media query
   around the second rule, so the hero and its four tiles sat side by side at
   390px: the tiles came out 64px wide and the per-cent sign on Gross Margin
   hung off the end of its card. */
.grid2 {display:grid;gap:14px;grid-template-columns:1fr}
@media (min-width: 760px) { .grid2 {grid-template-columns:1.45fr 1fr} }
.dropnote {display:flex;align-items:center;gap:9px;padding:13px 16px;font-size:13.5px;
  color:var(--brand);font-weight:500}
.xfer {display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}
.xbar {height:4px;border-radius:2px;background:var(--rule);overflow:hidden}
.xbar i {display:block;height:100%;background:var(--brand)}
/* ── the teach sheet ── */
/* Whitespace is kept, because `flatten` turned the merchant's table cells into
   tabs and its rows into newlines, and that is the document the parser
   matched against. Collapsing it here would put an answer and its label in
   different places from where the pattern will look for them. */
.mail {background:var(--ground);border-radius:10px;padding:14px 16px;font-size:13.5px;
  line-height:1.85;color:var(--ink-2);user-select:text;
  white-space:pre-wrap;word-break:break-word;tab-size:4;overflow:auto;min-height:0}
.mail b {color:var(--ink);font-weight:500}
/* The selection keeps its label, because the label is the half
   that makes the pattern findable next time. */
.tag {border-radius:4px;padding:1px 4px;color:var(--ink);box-decoration-break:clone}
.tag--g {background:color-mix(in srgb,var(--good) 26%,transparent)}
.tag--w {background:color-mix(in srgb,var(--warn) 30%,transparent)}
.tag--b {background:color-mix(in srgb,var(--bad) 24%,transparent)}
.tag--sel {background:color-mix(in srgb,var(--brand) 30%,transparent);outline:2px solid var(--brand)}
.ask {background:var(--surface);border-radius:11px;padding:13px 15px;display:flex;
  flex-direction:column;gap:10px}
.ask__q {font-size:14px;font-weight:600}
.ask__opts {display:flex;gap:7px;flex-wrap:wrap}
.opt {font:inherit;font-size:13px;padding:7px 12px;border-radius:980px;cursor:pointer;
  border:1px solid var(--rule-strong);background:var(--surface);color:var(--ink)}
.opt--on {background:var(--brand);border-color:var(--brand);color:#fff;font-weight:500}
/* The book greys the question while a refusal stands. Held rather than
   disabled: the buttons still work, because the way out of a refusal is to
   select better and press again, and a dead panel makes that look impossible. */
.ask--held {opacity:.55}
/* Body text takes `--ink` rather than `--ink-2` on both tinted panels: a
   ground mixed 11% toward the accent is light enough in the light appearance
   to drop the second ink weight to 4.3, and these two panels are the ones
   carrying the reason a decision was refused. */
.ask--good {background:color-mix(in srgb,var(--good) 11%,var(--surface));color:var(--ink)}
.ask--good .ask__q {color:var(--good-ink)}
/* A sentence, not a row of chips. `.r__s` is the row subtitle and is a flex
   container with a 6px gap, so every `<b>` in the middle of a sentence became
   its own flex item: the gap landed in front of the full stop and the line
   broke at each emphasis. Prose needs a block that wraps as text. */
.ask__said {font-size:12.5px;line-height:1.55;color:var(--ink);margin:0}
.ask__said b {font-weight:600}
.sheet__teach {display:flex;flex-direction:column;gap:12px;min-width:0}
/* The sheet is already `--surface`, so a group drawn on `--surface` vanishes
   into it. One step up, the way a card sits on a page. */
.sheet__pane .gp {background:var(--surface-2)}
.refuse {background:color-mix(in srgb,var(--bad) 12%,var(--surface));border-radius:11px;
  padding:13px 15px;font-size:13px;color:var(--ink);line-height:1.55}
.refuse b {color:var(--bad-ink);display:block;font-size:14px;margin-bottom:3px}
/* The selection to make instead, quoted inside the sentence rather than
   announced above it, so it reads as part of the instruction. */
.refuse__pick {color:var(--ink) !important;display:inline !important;font-size:13px;
  margin-bottom:0 !important}


/* ── page transitions ──────────────────────────────────────────────────────
   A cross-fade between pages, short enough to read as the same page changing
   rather than as a slide. Only the root: transitioning named elements across
   a full-body swap costs more than it returns when every page has a different
   shape.

   The View Transitions API does not consult Reduce Motion on its own, so the
   guard is explicit. `animation: none` rather than a shorter duration, because
   the point is to arrive, not to arrive faster. */
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .16s; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
