:root {
  --sp-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --sp-sidebar-w: 15rem;

  /* One control height for the whole instrument. Every input, select,
     enhanced select and button is exactly this tall, so a filter strip
     reads as a single row of controls with its labels on one line —
     and it clears WCAG 2.5.8's 24px target floor with room to spare. */
  --sp-control-h: 2.125rem;
  --sp-control-pad: 0.6rem;
  /* The narrow-screen chrome bar's inline padding, floored by the
     safe-area insets so the bar clears a notch or a rounded corner. */
  --sp-chromebar-pad: 0.75rem;
}

/* Every control in this build states a height and a padding; border-box
   is what makes those two numbers add up to the measure rather than
   overflow it. Declared once here instead of per component. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

::selection {
  background: var(--accent-weak-2);
}

/* ── Links on the canvas ──────────────────────────────────────────────
   The chrome owns its own link colours; on the canvas a link is accent
   text with its underline held back for hover. Row-name links (the
   ledger idioms further down) override this with ink at 600 — an entity
   in a column is not a reference in prose. Written with :where() so the
   selector contributes no specificity: every component rule in this
   file and in tokens.css wins over it, and only the browser's default
   blue loses. */
:where(.sp-main) a {
  color: var(--accent);
  text-decoration: none;
}
:where(.sp-main) a:hover {
  text-decoration: underline;
}

/* Focus, same voice as the rst components. :where() keeps specificity
   at zero so any rule below can override. */
:where(.sp-sidebar, .sp-main) :focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Screen-reader-only text (the accounts table's Pin column header). */
.sp-vh {
  block-size: 1px;
  clip-path: inset(50%);
  inline-size: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
}

/* The skip link: first tab stop, invisible until focused — thirteen
   sidebar stops otherwise stand between the keyboard and the page. */
.sp-skip {
  background: var(--accent);
  border-radius: var(--radius-ctl);
  color: var(--on-accent);
  inset-block-start: 0.5rem;
  inset-inline-start: 0.5rem;
  padding: 0.4rem 0.75rem;
  position: absolute;
  transform: translateY(-200%);
  /* Over the narrow-screen chrome bar, which is sticky at z-index 20. */
  z-index: 30;
}
.sp-skip:focus-visible {
  transform: none;
}

/* Line icons sit inside flex rows (nav items, buttons, row leads); the
   glyph is a fixed 16px measure, never the thing that gives way when
   the label beside it is long. */
.sp-icon {
  flex: none;
}

/* ── The money face ───────────────────────────────────────────────── */
.sp-money {
  font-family: var(--sp-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sp-money--pos {
  color: var(--ok-ink);
}
/* Tabular figures for things that are counted rather than paid: how many
   transactions, how many reconciled. They want the money face's column
   alignment but must NOT be .sp-money — the privacy lock blurs money,
   and a blurred "4227 of 4842" is not private, just broken. */
.sp-num {
  font-family: var(--sp-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Direction arrow ahead of a row's amount — the arrow carries the tone,
   so the debit figure itself stays neutral ink. */
.sp-dir {
  display: inline-flex;
  vertical-align: -0.09em;
  margin-inline-end: 0.35em;
}
.sp-dir--in {
  color: var(--ok-ink);
}
.sp-dir--out {
  color: var(--danger-ink);
}
/* Drilled figures keep the money voice, not the accent link voice —
   this component rule outranks the zero-specificity :where() default. */
.sp-money a {
  color: inherit;
  text-decoration: none;
}
.sp-money a:hover {
  text-decoration: underline;
}

/* Privacy lock: while the session is locked every amount is already a
   server-side placeholder; the blur is aesthetic, signalling "figures
   hidden" (privacy-lock spec §Redaction). */
.sp-locked .sp-money {
  filter: blur(3px);
  opacity: 0.75;
}
.sp-locked .sp-money--pos {
  color: inherit;
}
/* Direction is a figure too — hidden (not removed, spacing kept) while
   locked, matching the green-neutralising rule above. */
.sp-locked .sp-dir {
  visibility: hidden;
}

/* ── Motion ───────────────────────────────────────────────────────────
   The only movement in the build: the 120ms tint under a hover. */
@media (prefers-reduced-motion: no-preference) {
  a,
  button,
  .sp-chip,
  .sp-nav a,
  .rst-btn {
    transition: background-color .12s ease, border-color .12s ease, color .12s ease;
  }
}

/* ── The shell: sidebar rail + content column ─────────────────────────
   Light chrome in both themes now: the sidebar is the page's own
   --surface-2, a hairline apart from the canvas, so the console reads
   as one sheet of paper rather than an app frame bolted to a page.
   The active nav item is a tinted accent chip — no rake line, no
   constant-ink chrome. */
.sp-shell {
  display: grid;
  grid-template-columns: var(--sp-sidebar-w) minmax(0, 1fr);
  min-height: 100dvh;
}

.sp-sidebar {
  background: var(--surface-2);
  border-inline-end: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-height: 100dvh;
  overflow-y: auto;
  padding: 1rem 0.75rem;
  position: sticky;
  top: 0;
}

.sp-brand {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0.5rem 0.25rem;
}
.sp-brand a {
  color: var(--text);
  text-decoration: none;
}

.sp-nav {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 1.1rem;
}
.sp-nav__group--foot {
  margin-block-start: auto;
}
.sp-nav__label {
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  margin: 0 0.5rem 0.3rem;
  text-transform: uppercase;
}
.sp-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.sp-nav li + li {
  margin-block-start: 1px;
}

/* One rule for both shapes of nav item: links and the lock/sign-out
   forms' buttons. Icon then label, on one baseline-free flex line. */
.sp-nav a,
.sp-nav button {
  align-items: center;
  background: none;
  border: 0;
  border-radius: var(--radius-ctl);
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  font: 500 0.8125rem/1.3 inherit;
  gap: 0.55rem;
  inline-size: 100%;
  padding: 0.42rem 0.5rem;
  text-align: start;
  text-decoration: none;
}
.sp-nav a:hover,
.sp-nav button:hover {
  background: var(--neutral-weak);
  color: var(--text);
  text-decoration: none;
}
.sp-nav a[aria-current="page"] {
  background: var(--accent-weak);
  color: var(--accent-ink);
  font-weight: 600;
}
.sp-nav .sp-icon {
  color: var(--text-faint);
}
.sp-nav a:hover .sp-icon,
.sp-nav button:hover .sp-icon {
  color: inherit;
}
.sp-nav a[aria-current="page"] .sp-icon {
  color: var(--accent-ink);
}
.sp-nav form {
  margin: 0;
}

.sp-main {
  min-inline-size: 0;
}
/* The app canvas earns the console's breadth; bare pages (signin,
   locked) keep the narrow card the gate file dresses. */
.sp-shell .sp-main .rst-page {
  max-width: 76rem;
  padding: 1.25rem 2rem 3rem;
}
.sp-main--bare {
  display: grid;
  min-height: 100dvh;
  place-items: center;
}
.sp-main--bare .rst-page {
  inline-size: 100%;
  max-inline-size: 28rem;
}
/* A bare page carrying prose rather than one ceremony takes a reading
   measure — the gate card's 28rem is a column for a single act. */
.sp-main--bare .rst-page:has(.sp-doc) {
  max-inline-size: 44rem;
}

/* ── Top bar: the page's title and its actions ──────────────────────── */
.sp-topbar {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0 0 1.25rem;
}
.sp-topbar h1 {
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0;
}
.sp-topbar__actions {
  display: flex;
  gap: 0.5rem;
  margin: 0 0 0 auto;
}
/* While a pass a human asked for is running, the Sync now button is
   replaced by this status — same measure and box as the button it stands
   in for, so the topbar does not reflow when it swaps back. */
.sp-topbar__status {
  align-items: center;
  block-size: var(--sp-control-h);
  color: var(--text-muted);
  display: flex;
  font-size: 0.8125rem;
  gap: 0.35rem;
  margin: 0;
  padding-inline: 0.5rem;
}
/* The sync status stacks its line over a progress bar, so the block —
   not the line — is what stands in for the button. It keeps the
   button's block-size on the line itself (above) and lets the bar hang
   below it, because matching the button's total height would make the
   topbar taller for as long as a sync runs and shorter again after. */
.sp-sync {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.sp-sync__line {
  justify-content: flex-end;
}
/* The spinner. A wrapper, not the svg itself: the icon set draws on a
   16x16 grid with its own stroke geometry, and rotating the element
   around it leaves that untouched. */
.sp-sync__spin {
  animation: sp-sync-spin 1.4s linear infinite;
  display: inline-flex;
  transform-origin: 50% 50%;
}
@keyframes sp-sync-spin {
  to { transform: rotate(360deg); }
}
/* A spinner is the one thing on the screen that moves continuously, so
   it is exactly what this setting is for. The status line still says
   what is happening and the bar still advances — nothing is lost by
   holding it still. */
@media (prefers-reduced-motion: reduce) {
  .sp-sync__spin { animation: none; }
}
/* <progress> is styled per-engine: the element, then WebKit's two
   pseudo-elements, then Firefox's one. All three need the same colours
   or the bar reads as a different component in a different browser. */
.sp-sync__bar {
  appearance: none;
  background: var(--accent-weak);
  block-size: 0.25rem;
  border: 0;
  border-radius: 999px;
  inline-size: 100%;
  min-inline-size: 8rem;
  overflow: hidden;
}
.sp-sync__bar::-webkit-progress-bar {
  background: var(--accent-weak);
  border-radius: 999px;
}
.sp-sync__bar::-webkit-progress-value {
  background: var(--accent);
  border-radius: 999px;
  transition: inline-size 0.3s ease;
}
.sp-sync__bar::-moz-progress-bar {
  background: var(--accent);
  border-radius: 999px;
}
@media (prefers-reduced-motion: reduce) {
  .sp-sync__bar::-webkit-progress-value { transition: none; }
}

/* A screen that needs a line of orientation under its title stacks the
   two in a titles block, so the actions still ride the right edge. */
.sp-topbar__titles {
  min-inline-size: 0;
}
.sp-topbar__sub {
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin: 0.2rem 0 0;
  max-inline-size: 44rem;
}

/* Two page-header idioms ship (the app's own and the upstream rst
   partial); their h1s speak at one size so the seam never shows. */
.sp-main .rst-page-header h1 {
  font-size: 1.25rem;
}

/* ── The narrow-screen chrome bar and its kebab menu ─────────────────
   Below 48rem the sidebar is replaced, not reflowed (#60): thirteen
   links in a scrolling strip put the current page off-screen and hid
   Sign out behind a swipe. A sticky bar carries the wordmark and one
   control — a native <details>, so the disclosure itself needs no
   script; menu.js only adds close-on-outside-click. The panel is
   positioned off the bar, so opening it never shifts the page. */
.sp-chromebar {
  display: none;
}
.sp-chromebar__brand {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0;
}
.sp-chromebar__brand a {
  color: var(--text);
  text-decoration: none;
}
.sp-navmenu {
  margin-inline-start: auto;
}
.sp-navmenu__btn {
  align-items: center;
  block-size: var(--sp-control-h);
  border-radius: var(--radius-ctl);
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  inline-size: var(--sp-control-h);
  justify-content: center;
  list-style: none;
}
.sp-navmenu__btn::-webkit-details-marker {
  display: none;
}
.sp-navmenu__btn:hover {
  background: var(--neutral-weak);
  color: var(--text);
}
.sp-navmenu__icon {
  align-items: center;
  display: inline-flex;
}
.sp-navmenu__icon--open,
.sp-navmenu[open] .sp-navmenu__icon--closed {
  display: none;
}
.sp-navmenu[open] .sp-navmenu__icon--open {
  display: inline-flex;
}
/* Positioned against the sticky chrome bar — which, being positioned,
   is the containing block — so the panel spans the bar's full width and
   the bar's bottom hairline draws over the panel's own top edge as the
   seam between them. */
.sp-navmenu__panel {
  background: var(--surface-2);
  border-block-end: 1px solid var(--line);
  box-shadow: var(--shadow-1);
  inset-block-start: 100%;
  inset-inline: 0;
  max-block-size: calc(100dvh - 3.75rem);
  overflow-y: auto;
  /* The panel scrolls itself; the page underneath must not take over
     when it hits the end. */
  overscroll-behavior: contain;
  padding: 0.75rem;
  padding-block-end: max(0.75rem, env(safe-area-inset-bottom));
  position: absolute;
}

@media (max-width: 48rem) {
  html {
    /* In-page jumps must clear the sticky bar. */
    scroll-padding-block-start: 3.75rem;
  }
  .sp-shell {
    display: block;
  }
  /* The bar is the chrome now, so the skip link has to clear it and
     in-page jumps need the same room. */
  .sp-chromebar {
    align-items: center;
    background: var(--surface-2);
    border-block-end: 1px solid var(--line);
    display: flex;
    gap: 0.75rem;
    inset-block-start: 0;
    padding: 0.5rem var(--sp-chromebar-pad);
    padding-inline-start: max(var(--sp-chromebar-pad), env(safe-area-inset-left));
    padding-inline-end: max(var(--sp-chromebar-pad), env(safe-area-inset-right));
    position: sticky;
    z-index: 20;
  }
  .sp-sidebar {
    display: none;
  }
  /* Touch measure for the panel's rows; the label stays, because the
     grouping is what the strip destroyed. */
  .sp-navmenu__panel .sp-nav a,
  .sp-navmenu__panel .sp-nav button {
    font-size: 0.875rem;
    min-block-size: 2.75rem;
  }
  .sp-shell .sp-main .rst-page {
    padding-inline: 1rem;
  }
}

/* ── The gate: the bare-page ceremony card (signin, unlock, recover) ──
   No sidebar out here, so the card carries the wordmark itself. It is
   the panel recipe with the ceremony's own rhythm: one act, taking the
   card's full measure. */
.sp-gate {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-1);
  padding: 1.5rem;
}
.sp-gate__brand {
  margin: 0 0 1.5rem;
}
.sp-gate__brand strong {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.sp-gate h1 {
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.25;
  margin: 0 0 0.5rem;
}
.sp-gate p {
  margin: 0 0 0.75rem;
}
/* One ceremony, one act: gate buttons take the card's full measure.
   Height still comes from --sp-control-h like every control. */
.sp-gate form,
.sp-gate .sp-gate__act {
  margin: 1rem 0 0.75rem;
}
.sp-gate .rst-btn {
  inline-size: 100%;
  justify-content: center;
}
.sp-gate .sp-field {
  margin-block-end: 0.75rem;
}
.sp-gate .sp-input {
  inline-size: 100%;
}
.sp-gate > :last-child,
.sp-gate form:last-child {
  margin-block-end: 0;
}

/* ── The public documents (privacy, terms) ───────────────────────────
   Bare pages like the gate, but prose rather than one act: the same
   paper, a reading measure instead of the ceremony card's 28rem, and
   headings that step down from the page title. */
.sp-doc {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-1);
  padding: 1.75rem;
}
.sp-doc h1 {
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0 0 0.75rem;
}
.sp-doc h2 {
  font-size: 0.875rem;
  font-weight: 600;
  margin: 1.5rem 0 0.4rem;
}
.sp-doc p,
.sp-doc li {
  color: var(--text-muted);
  font-size: 0.875rem;
  line-height: 1.6;
}
.sp-doc p {
  margin: 0 0 0.75rem;
}
.sp-doc ul {
  margin: 0 0 0.75rem;
  padding-inline-start: 1.1rem;
}
.sp-doc > :last-child {
  margin-block-end: 0;
}

/* ── Controls ─────────────────────────────────────────────────────────
   One measure: every button, input, select and enhanced select is
   exactly --sp-control-h tall, so a filter strip reads as a single row
   of controls with its labels on one line — and it clears WCAG 2.5.8's
   24px target floor with room to spare. */
.rst-btn {
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-ctl);
  color: var(--text);
  cursor: pointer;
  display: inline-flex;
  font: 600 0.8125rem/1.2 inherit;
  gap: 0.45rem;
  min-block-size: var(--sp-control-h);
  padding: 0 0.75rem;
  text-decoration: none;
}
.rst-btn:hover {
  border-color: var(--accent);
  color: var(--accent-ink);
  text-decoration: none;
}
.rst-btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.rst-btn--primary:hover {
  background: var(--accent-ink);
  border-color: var(--accent-ink);
  color: var(--on-accent);
}

.sp-input {
  background: var(--surface);
  block-size: var(--sp-control-h);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-ctl);
  color: var(--text);
  font: 400 0.8125rem/1.2 inherit;
  padding: 0 var(--sp-control-pad);
}
/* A native select is the no-JS floor under every enhanced one, so it
   carries the same minimum width the enhanced control does. */
select.sp-input {
  min-inline-size: 9rem;
}
/* The browser draws its own button inside a file input; it needs the
   block padding back, and it is never a one-line-of-text control. */
input[type="file"].sp-input {
  block-size: auto;
  min-block-size: var(--sp-control-h);
  padding-block: 0.35rem;
}
/* A textarea is never a one-line-of-text control either — the code
   someone pastes here runs to several hundred characters, and the
   shared control measure would shrink it to a single unreadable line. */
textarea.sp-input {
  block-size: auto;
  min-block-size: calc(var(--sp-control-h) * 2);
  padding-block: 0.35rem;
  resize: vertical;
}
.sp-input::placeholder {
  color: var(--text-faint);
}
.sp-input:focus-visible {
  border-color: var(--accent);
}
.sp-input--ccy {
  inline-size: 5.5rem;
  text-transform: uppercase;
}

/* The browser's own tick would otherwise arrive in its default blue,
   the one colour on the canvas that belongs to no theme here. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--accent);
}

/* ── Fields and form furniture ───────────────────────────────────── */
.sp-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.sp-field > label {
  color: var(--text-muted);
  font-size: 0.71875rem;
  font-weight: 600;
}
/* The search field takes the slack in a strip, but only so much: past
   about 24rem it is a trough, and the controls beside it read as
   stranded. */
.sp-field--grow {
  flex: 1 1 17rem;
  max-inline-size: 24rem;
  min-inline-size: 17rem;
}
.sp-form-row {
  align-items: end;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0;
}
.sp-form-row + .sp-form-row,
.sp-kv + .sp-form-row,
p + .sp-form-row {
  margin-block-start: 0.75rem;
}
.sp-checkline {
  align-items: center;
  align-self: end;
  display: flex;
  font-size: 0.875rem;
  gap: 0.5rem;
  min-block-size: var(--sp-control-h);
  padding-block: 0;
}
/* A form's terminal actions sit apart from the fields they commit. */
.sp-form-actions {
  align-items: center;
  border-block-start: 1px solid var(--line);
  display: flex;
  gap: 0.75rem;
  margin-block-start: 1rem;
  padding-block-start: 0.75rem;
}

/* ── Chips: a fact about a list, not an action ───────────────────────
   One chip, two duties: the category list's static pills and the
   browser's removable filter links share this single definition —
   links pick up the hover. */
.sp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.75rem 0;
  padding: 0;
}
.sp-chip {
  align-items: center;
  background: var(--neutral-weak);
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--neutral-ink);
  display: inline-flex;
  font-size: 0.71875rem;
  font-weight: 600;
  gap: 0.35rem;
  list-style: none;
  padding: 0.2rem 0.65rem;
  text-decoration: none;
}
a.sp-chip:hover {
  background: var(--accent-weak);
  border-color: var(--accent);
  color: var(--accent-ink);
  text-decoration: none;
}

/* ── Pagination ──────────────────────────────────────────────────── */
.sp-pages {
  display: flex;
  gap: 0.5rem;
  margin-block-start: 0.75rem;
}
.sp-pages a {
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  color: var(--text-muted);
  font-size: 0.8125rem;
  padding: 0.3rem 0.7rem;
  text-decoration: none;
}
.sp-pages a:hover {
  background: var(--accent-weak);
  border-color: var(--accent);
  color: var(--accent-ink);
  text-decoration: none;
}

/* ── Lead circles ─────────────────────────────────────────────────────
   A person gets their initials in the accent tint; a connector or
   account gets the neutral variant, wearing its mark. */
.sp-avatar {
  align-items: center;
  background: var(--accent-weak);
  block-size: 2rem;
  border-radius: 50%;
  color: var(--accent-ink);
  display: inline-flex;
  flex: none;
  font-size: 0.71875rem;
  font-weight: 600;
  inline-size: 2rem;
  justify-content: center;
}
.sp-avatar--mark {
  background: var(--neutral-weak);
  color: var(--neutral-ink);
}

/* ── Choices.js dressed in the app's tokens (enhancement only) ─────── */
/* Stock choices.css leaves content-box sizing on its own subtree even
   though the base layer sets border-box; without this the inner box
   overflows its wrapper by its own padding (a clipped right edge plus a
   stray hairline). */
.choices,
.choices * {
  box-sizing: border-box;
}
.choices {
  margin-block-end: 0;
  /* The hidden original <select> no longer sizes the field, so the
     control needs its own floor. */
  min-inline-size: 9rem;
}
/* Stock choices stacks three paddings — the inner's, select-one's extra
   block-end, and the single-item list's — on top of a 44px floor, which
   is how the enhanced select ended up half a line taller than every
   field beside it. All three are cleared here so the control is exactly
   --sp-control-h, like a plain input, with the label centred in it and
   room at the end for the dropdown caret. */
.choices__inner {
  align-items: center;
  background: var(--surface);
  block-size: var(--sp-control-h);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-ctl);
  color: var(--text);
  display: flex;
  font-size: 0.8125rem;
  min-block-size: 0;
  overflow: hidden;
  padding: 0 1.75rem 0 var(--sp-control-pad);
}
.choices[data-type*="select-one"] .choices__inner {
  padding-block-end: 0;
}
/* Stock draws the caret in a hard-coded #333, which all but disappears
   on the night canvas. It takes the muted ink of both themes instead. */
.choices[data-type*="select-one"]::after {
  border-color: var(--text-muted) transparent transparent;
}
.choices[data-type*="select-one"].is-open::after {
  border-color: transparent transparent var(--text-muted);
}
.choices__list--single {
  padding: 0;
}
.choices__list--single .choices__item {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Choices moves focus into its own inner element, so the base
   :focus-visible ring never lands on the control the eye reads. */
.is-focused .choices__inner,
.is-open .choices__inner {
  border-color: var(--accent);
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.choices__list--dropdown,
.choices__list[aria-expanded] {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-1);
}
.choices__list--dropdown .choices__item--selectable.is-highlighted,
.choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
  background: var(--accent-weak);
  color: var(--accent-ink);
}
.choices__list--dropdown .choices__item,
.choices__list[aria-expanded] .choices__item {
  color: var(--text);
  font-size: 0.8125rem;
  padding: 0.4rem var(--sp-control-pad);
}
.choices__input {
  background: var(--surface);
  border-block-end-color: var(--line);
  color: var(--text);
  font-size: 0.8125rem;
  margin-block-end: 0;
}
.choices[data-type*="select-one"] .choices__input {
  padding: 0.4rem var(--sp-control-pad);
}
.choices__placeholder {
  color: var(--text-faint);
  opacity: 1;
}

/* ── Panels: the card every screen is built out of ──────────────────── */
.sp-panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-1);
  margin: 0 0 1rem;
  padding: 1rem;
}
.sp-panel__title {
  align-items: center;
  color: var(--text-faint);
  display: flex;
  font-size: 0.71875rem;
  font-weight: 600;
  gap: 0.4rem;
  letter-spacing: 0.08em;
  margin: 0 0 0.6rem;
  text-transform: uppercase;
}
/* A second section opening inside one panel needs the air a panel edge
   would otherwise have given it. */
.sp-panel__title--mid {
  margin-block-start: 1.5rem;
}
.sp-panel__more {
  font-size: 0.8125rem;
  margin: 0.75rem 0 0;
}
.sp-panel > p {
  margin: 0 0 0.5rem;
}
.sp-panel > p:last-child {
  margin-block-end: 0;
}

/* ── Panel arrangements ───────────────────────────────────────────────
   The board is the dashboard's hero beside its accounts rail; the grid
   is any run of same-kind panels that belong side by side while there
   is room for both (reconcile's two loose lists). */
.sp-board {
  display: grid;
  gap: 1rem;
  grid-template-columns: minmax(0, 1fr) minmax(17rem, 24rem);
  margin: 0 0 1rem;
}
.sp-board > .sp-panel {
  margin: 0;
}
/* A phone has no room for a hero beside a rail: the board becomes one
   column. This override lives here, not in the shell's narrow block —
   a media query buys no specificity, so it has to sit after the rule it
   overrides in bundleOrder (see TestMediaOverridesAreNotOutranked). */
@media (max-width: 48rem) {
  .sp-board {
    grid-template-columns: minmax(0, 1fr);
  }
}
.sp-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  margin-block-end: 1rem;
}
.sp-grid > .sp-panel {
  margin-block-end: 0;
}

/* ── Banners and the recovery-key ceremony ───────────────────────── */
.sp-banner {
  background: var(--warn-weak);
  border-radius: var(--radius-ctl);
  color: var(--warn-ink);
  font-size: 0.8125rem;
  margin: 0 0 0.75rem;
  padding: 0.5rem 0.75rem;
}
.sp-banner a {
  color: inherit;
}
/* The recovery key is an accent moment, not a warning. */
.sp-recovery-key {
  background: var(--accent-weak);
  color: var(--text);
  padding: 0.75rem 1rem;
}
.sp-recovery-key h3 {
  font-size: 0.875rem;
  margin: 0 0 0.5rem;
}
.sp-recovery-key code {
  font-family: var(--sp-mono);
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}

/* ── Quiet prose inside a panel ──────────────────────────────────── */
.sp-note {
  color: var(--text-muted);
  font-size: 0.8125rem;
}
.sp-back {
  font-size: 0.8125rem;
  margin: 1rem 0 0;
}
.sp-back a {
  color: var(--text-muted);
  text-decoration: none;
}
.sp-back a:hover {
  color: var(--accent-ink);
}

/* ── The balance hero: headline total over its 30-day line ────────── */
.sp-tide {
  display: flex;
  flex-direction: column;
}
.sp-tide__head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  justify-content: space-between;
}
.sp-tide__total {
  font-family: var(--sp-mono);
  font-size: 2.1rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 0;
}
/* A phone is narrower than a seven-figure balance at the display step:
   2.1rem holds "1,234,567.89 EUR" at 312px against a 294px panel, which
   put the lock button off the screen entirely. The hero drops one step
   down the ramp (DESIGN.md's typography chart) rather than shrink-to-fit
   between steps. */
@media (max-width: 48rem) {
  .sp-tide__total {
    font-size: 1.4rem;
  }
}
/* Headline as lock control (headline-lock-toggle spec): the button
   locks instantly — a deliberate separate target, no confirm; the
   blurred headline itself is the unlock link. */
.sp-tide__totalrow {
  align-items: baseline;
  display: flex;
  /* The figure is nowrap and sets its own width; a headline long enough
     to fill the line drops the lock button to the next one rather than
     shoving it off the panel. */
  flex-wrap: wrap;
  gap: 0.5rem;
}
.sp-tide__lock button {
  background: none;
  border: 0;
  color: var(--text-faint);
  cursor: pointer;
  font: 500 0.8125rem/1 inherit;
  min-block-size: 1.5rem;
  padding: 0.25rem 0.4rem;
}
.sp-tide__lock button:hover {
  color: var(--accent-ink);
}
.sp-tide__total-link {
  color: inherit;
  cursor: pointer;
  text-decoration: none;
}
.sp-tide__note {
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin: 0.25rem 0 0;
}
/* A total fed by an account that isn't syncing is a claim, not a fact. */
.sp-tide__warn {
  color: var(--warn-ink);
  font-size: 0.8125rem;
  margin: 0.25rem 0 0;
}
.sp-tide__ccys {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0;
}
.sp-tide__ccys > div {
  align-items: baseline;
  display: flex;
  gap: 1rem;
  justify-content: space-between;
}
/* Full width, the currency lines are a two-column ledger; on a phone the
   list is narrow enough that spreading them just strands the amount. */
@media (max-width: 48rem) {
  .sp-tide__ccys > div {
    justify-content: flex-start;
  }
}
.sp-tide__ccys dt {
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}
.sp-tide__ccys dd {
  font-size: 0.8125rem;
  margin: 0;
  text-align: end;
}
.sp-tide__ccys .sp-sub {
  font-size: 0.71875rem;
}
/* The hero's own chart is the shared .sp-chart recipe — 60-charts.css. */

/* ── Transaction detail hero ─────────────────────────────────────── */
.sp-txn-hero {
  margin: 0 0 1rem;
}
.sp-txn-hero .sp-money {
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.sp-txn-hero__sub {
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin: 0.25rem 0 0;
}
/* A receipt's raw body is quoted text, not prose: it keeps its own
   line breaks and scrolls rather than reflowing. */
.sp-receipt-body {
  font-size: 0.8125rem;
  overflow-x: auto;
  white-space: pre-wrap;
}

/* ── Pairing cards ──────────────────────────────────────────────────
   One card per Xero account, so the control and the button that saves
   it are unmistakably part of the account they belong to. The previous
   layout ran every account down one column separated by hairlines, and
   a submit button sitting above the next account's name reads as that
   account's button. */
.sp-pair {
  display: flex;
  flex-direction: column;
  margin-block-end: 0;
}
.sp-pair__name {
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0 0 0.25rem;
}
.sp-pair__why {
  color: var(--text-faint);
  font-size: 0.8125rem;
  margin: 0 0 0.75rem;
}
.sp-pair form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-block-start: auto;
}
.sp-pair .sp-form-actions {
  margin: 0;
}
/* A settled pairing is a statement, not a control. It reads as done at a
   glance — the tick and the account it answers to — and only becomes a
   form when someone asks it to. */
.sp-pair__paired {
  align-items: center;
  display: flex;
  font-size: 0.875rem;
  gap: 0.35rem;
  margin: 0 0 0.25rem;
}
.sp-pair__tick {
  align-items: center;
  color: var(--ok-ink);
  display: inline-flex;
}
.sp-pair__edit {
  margin-block-start: auto;
}
.sp-pair__edit > summary {
  color: var(--accent-ink);
  cursor: pointer;
  font-size: 0.8125rem;
  padding-block: 0.25rem;
}
.sp-pair__edit[open] > summary {
  margin-block-end: 0.5rem;
}
/* A submit that has been pressed says so, and refuses a second press.
   Enhancement only: without the script the button is an ordinary
   submit and the browser's own navigation is the feedback. */
.rst-btn[aria-busy="true"] {
  cursor: progress;
  opacity: 0.65;
}

/* ── Stat cards: the summary trio ─────────────────────────────────────
   A stat card is a panel that answers one question in one figure — what
   payroll last cost, what is still loose, what is uncategorised — with
   the act that clears it on the line underneath. Label, value, delta:
   nothing else earns a place, or the trio becomes a dashboard sprawl. */
.sp-statgrid {
  display: grid;
  gap: 0.9rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  margin: 0 0 1rem;
}
/* Panels carry their own bottom margin for stacked screens; inside the
   grid the gap is the only spacing. */
.sp-statgrid > .sp-panel {
  margin: 0;
}

/* The label names the figure and may wear a pill (a payroll run's
   status) beside it — hence the flex line rather than a bare span. */
.sp-stat__label {
  align-items: center;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  font-size: 0.8125rem;
  font-weight: 500;
  gap: 0.4rem;
}
.sp-stat__value {
  font-size: 1.4rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  margin-block-start: 0.35rem;
}
/* The delta line: what the figure is made of, and the link that acts on
   it. data-tone colours only the <strong> inside, never the whole line,
   so the sentence stays readable and the tone stays a highlight. */
.sp-stat__delta {
  align-items: center;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  font-size: 0.8125rem;
  gap: 0.3rem;
  margin-block-start: 0.3rem;
}
.sp-stat__delta[data-tone="positive"] strong {
  color: var(--ok-ink);
  font-weight: 600;
}
.sp-stat__delta[data-tone="negative"] strong {
  color: var(--danger-ink);
  font-weight: 600;
}

/* ── Rows: the hairline-ruled lines inside a panel ────────────────────
   Every list of entities is a run of ruled rows, never a bullet list.
   The first row drops its rule (the panel edge is the rule) and the
   last drops its floor. */

/* Key/value: the ledger column, value right-aligned. */
.sp-kv {
  align-items: baseline;
  border-block-start: 1px solid var(--line);
  display: flex;
  gap: 0.75rem;
  justify-content: space-between;
  padding-block: 0.45rem;
}
.sp-kv:first-of-type {
  border-block-start: 0;
  padding-block-start: 0;
}
.sp-kv:last-of-type {
  padding-block-end: 0;
}
.sp-kv__k {
  min-inline-size: 0;
}
.sp-kv__v {
  text-align: end;
}

/* The second line of a row: what the entity above it is, in muted body
   weight. It sits inside names set at 600, so it states its own weight
   rather than inheriting the emphasis of the line it explains. */
.sp-sub {
  color: var(--text-muted);
  display: block;
  font-size: 0.71875rem;
  font-weight: 400;
}

/* The freshness stamp — an honesty feature, kept on stage. A balance
   more than a day old is a warning worn by the stamp itself. */
.sp-acct__fresh {
  color: var(--text-faint);
  font-size: 0.71875rem;
  white-space: nowrap;
}
.sp-fresh--stale {
  color: var(--warn-ink);
}

/* ── Transaction rows: ledger and dashboard recent activity ───────── */
.sp-txn {
  align-items: baseline;
  border-block-start: 1px solid var(--line);
  display: flex;
  gap: 0.75rem;
  padding: 0.75rem;
  position: relative;
}
.sp-txn:first-child {
  border-block-start: 0;
}
.sp-txn:hover {
  background: var(--surface-2);
}
.sp-txn__date {
  color: var(--text-faint);
  flex: none;
  font-family: var(--sp-mono);
  font-size: 0.71875rem;
  font-variant-numeric: tabular-nums;
  inline-size: 5.25rem;
}
/* The date column's fixed measure is a luxury of width; on a phone it
   shrinks to its text so the counterparty gets the room. */
@media (max-width: 48rem) {
  .sp-txn__date {
    inline-size: auto;
  }
}
/* A row led by an avatar (cards.html) aligns on the circle's middle
   rather than the text baseline — the same reasoning as .sp-acct--lead. */
.sp-txn--lead {
  align-items: center;
}
.sp-txn__main {
  flex: 1;
  min-inline-size: 0;
}
.sp-txn__amt {
  flex: none;
  font-size: 0.875rem;
}
/* Panel-hosted txn rows (dashboard recent activity) sit flush. */
.sp-panel .sp-txn {
  padding-inline: 0;
}
.sp-panel .sp-txn:hover {
  background: none;
}

/* The entity-name link, wherever a row names something you can open:
   ink at 600, no underline until hover. It reads as the row's subject
   rather than as a reference in prose — which is why it steps out of
   the canvas's accent link voice. */
.sp-txn__main > a,
.sp-acct__name > a,
.sp-acctrow__title > a,
.sp-payrow__who a,
.sp-rows__who a,
.sp-table tbody a {
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
}
.sp-txn__main > a:hover,
.sp-acct__name > a:hover,
.sp-acctrow__title > a:hover,
.sp-payrow__who a:hover,
.sp-rows__who a:hover,
.sp-table tbody a:hover {
  text-decoration: underline;
}
/* The name link stretches across its row, so the whole line is
   clickable without nesting anchors. */
.sp-txn__main > a::after,
.sp-acct__name > a::after,
.sp-acctrow__title > a::after {
  content: "";
  inset: 0;
  position: absolute;
}

/* ── Account rows: name, connection state, balance ────────────────── */
.sp-acct {
  align-items: baseline;
  border-block-start: 1px solid var(--line);
  display: flex;
  gap: 0.75rem;
  justify-content: space-between;
  padding-block: 0.55rem;
  /* Whole row is the link (stretched overlay above); inline actions
     like Renew float over it. */
  position: relative;
}
.sp-acct:first-of-type {
  border-block-start: 0;
  padding-block-start: 0;
}
/* The dashboard rail leads each account with its mark, so those rows
   align on the circle's middle rather than on the name's baseline, and
   the balance is pushed right by its own margin (three children, so
   space-between would spread all three). */
.sp-acct--lead {
  align-items: center;
}
.sp-acct--lead .sp-acct__bal {
  margin-inline-start: auto;
}
.sp-acct__name {
  font-weight: 600;
  min-inline-size: 0;
}
.sp-acct__meta {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.5rem;
  margin-block-start: 0.2rem;
}
.sp-acct__bal {
  text-align: end;
}
/* Renewing access is an act, not a footnote: a compact button in the
   accent's outline voice, riding above the row's stretched link. */
.sp-acct__renew {
  block-size: 1.7rem;
  border: 1px solid var(--accent);
  border-radius: var(--radius-ctl);
  color: var(--accent-ink);
  display: inline-flex;
  align-items: center;
  font-size: 0.71875rem;
  font-weight: 600;
  padding: 0 0.55rem;
  position: relative;
  text-decoration: none;
  z-index: 1;
}
.sp-acct__renew:hover {
  background: var(--accent-weak);
  text-decoration: none;
}

/* ── The accounts table: aligned columns under a quiet header row ─── */
.sp-tblhead,
.sp-acctrow {
  align-items: center;
  display: grid;
  gap: 0.75rem;
  grid-template-columns: minmax(10rem, 1fr) minmax(9rem, 12rem) minmax(6rem, 8rem) 9.5rem 4.5rem;
}
.sp-tblhead {
  border-block-end: 1px solid var(--line);
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  padding-block: 0.5rem;
}
.sp-col--num {
  text-align: end;
}
.sp-acctrow {
  border-block-start: 1px solid var(--line);
  padding-block: 0.55rem;
  position: relative;
}
.sp-acctrow:first-child {
  border-block-start: 0;
}
.sp-acctrow:hover {
  background: var(--surface-2);
}
.sp-acctrow__name {
  align-items: center;
  display: flex;
  gap: 0.6rem;
}
.sp-acctrow__title {
  display: flex;
  flex-direction: column;
  font-weight: 600;
  min-inline-size: 0;
}
.sp-acctrow__pin {
  justify-self: end;
  position: relative;
  z-index: 1;
}
/* Mid-drag, the picked-up row reads as lifted off the page. */
.sp-dragging {
  opacity: 0.5;
}

/* ── Payroll rows: employee lines in a run or roster ──────────────── */
.sp-payrow {
  align-items: center;
  border-block-start: 1px solid var(--line);
  display: flex;
  gap: 0.75rem;
  padding-block: 0.5rem;
}
.sp-payrow:first-of-type {
  border-block-start: 0;
  padding-block-start: 0;
}
.sp-payrow:last-of-type {
  padding-block-end: 0;
}
.sp-payrow__who {
  align-items: center;
  display: flex;
  flex: 1;
  gap: 0.5rem;
  min-inline-size: 0;
}
.sp-payrow .sp-input {
  inline-size: 8rem;
  text-align: end;
}

/* ── Detail rows: a record's fields, term beside value ────────────── */
.sp-detail {
  margin: 0;
}
.sp-detail > div {
  border-block-start: 1px solid var(--line);
  display: grid;
  gap: 0.75rem;
  grid-template-columns: 8rem minmax(0, 1fr);
  padding-block: 0.45rem;
}
.sp-detail > div:first-of-type {
  border-block-start: 0;
  padding-block-start: 0;
}
.sp-detail dt {
  color: var(--text-muted);
  font-size: 0.8125rem;
}
.sp-detail dd {
  margin: 0;
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

/* ── Reconcile suggestions ────────────────────────────────────────────
   A match is a claim about two records: the receipt on top, the
   transaction it answers to indented beneath it. */
.sp-suggestion {
  border-block-start: 1px solid var(--line);
  padding-block: 0.75rem;
}
.sp-suggestion:first-of-type {
  border-block-start: 0;
  padding-block-start: 0;
}
.sp-suggestion p {
  margin: 0;
}
.sp-suggestion__match {
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin-block-start: 0.15rem;
}
.sp-suggestion__actions {
  display: flex;
  gap: 0.5rem;
  margin-block-start: 0.75rem;
}

/* A row's hover tint arrives, it doesn't snap. */
@media (prefers-reduced-motion: no-preference) {
  .sp-txn,
  .sp-acctrow {
    transition: background-color .12s ease;
  }
}

/* ── Narrow screens: the accounts table folds to two lines ────────── */
@media (max-width: 44rem) {
  .sp-tblhead {
    display: none;
  }
  .sp-acctrow {
    grid-template-columns: 1fr auto auto;
    grid-template-areas:
      "name bal pin"
      "meta meta meta";
  }
  .sp-acctrow__name { grid-area: name; }
  .sp-acctrow > .sp-col--num { grid-area: bal; }
  .sp-acctrow__pin { grid-area: pin; }
  .sp-acctrow > span:nth-child(3),
  .sp-acctrow > span:nth-child(4) {
    grid-area: meta;
  }
  .sp-acctrow > span:nth-child(4) {
    justify-self: end;
  }
}

/* ── The sort row ─────────────────────────────────────────────────────
   A quiet line of choices under the label that names them, not a
   paragraph of links. The chosen one is ink at 600 and wears its
   direction arrow, so the state never rests on colour alone. */
.sp-tablebar {
  align-items: baseline;
  border-block-end: 1px solid var(--line);
  color: var(--text-faint);
  display: flex;
  flex-wrap: wrap;
  font-size: 0.8125rem;
  gap: 0.5rem 0.75rem;
  margin-block-end: 0.75rem;
  padding-block-end: 0.5rem;
}
.sp-tablebar__label {
  font-size: 0.71875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.sp-tablebar a {
  color: var(--text-muted);
  text-decoration: none;
}
.sp-tablebar a:hover {
  color: var(--text);
  text-decoration: underline;
}
/* A tablebar can carry one trailing action (Pair accounts) — it rides
   the far edge, away from the sort links. */
.sp-tablebar__end {
  margin-inline-start: auto;
}
.sp-tablebar a.sp-sort--active {
  color: var(--text);
}
.sp-sort--active {
  font-weight: 600;
}
/* The sort row already closes with a hairline, so the row beneath it
   must not draw its own. */
.sp-tablebar + .sp-acct,
.sp-tablebar + .sp-txn,
.sp-tablebar + .sp-kv {
  border-block-start: 0;
}

/* Wide by nature — a table scrolls inside its panel rather than
   stretching the page. */
.sp-scroll,
.sp-tablewrap {
  overflow-x: auto;
  scrollbar-color: var(--line-strong) transparent;
}
.sp-scroll::-webkit-scrollbar,
.sp-tablewrap::-webkit-scrollbar {
  height: 8px;
}
.sp-scroll::-webkit-scrollbar-track,
.sp-tablewrap::-webkit-scrollbar-track {
  background: transparent;
}
.sp-scroll::-webkit-scrollbar-thumb,
.sp-tablewrap::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 999px;
}

/* ── Row tables: entities in columns ──────────────────────────────────
   Where a ruled row list (40-rows.css) carries one entity per line, a
   row table carries the same entity across named columns — the shape
   the dashboard's recent activity takes, so date, counterparty,
   category, account and amount all line up down the panel. Same
   hairlines and same hover as the row idioms; the columns are the only
   difference. */
.sp-rows {
  border-collapse: collapse;
  font-size: 0.8125rem;
  width: 100%;
}
.sp-rows th {
  border-block-end: 1px solid var(--line);
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  padding: 0.4rem 0.6rem;
  text-align: start;
}
.sp-rows td {
  border-block-end: 1px solid var(--line);
  padding: 0.55rem 0.6rem;
  vertical-align: middle;
}
/* The last row's floor is the panel's edge. */
.sp-rows tr:last-child td {
  border-block-end: 0;
}
.sp-rows tr:hover td {
  background: var(--surface-2);
}
/* The row's first and last cells sit flush with the panel's padding —
   the cell padding is for the gaps between columns, not the margins. */
.sp-rows th:first-child,
.sp-rows td:first-child {
  padding-inline-start: 0;
}
.sp-rows th:last-child,
.sp-rows td:last-child {
  padding-inline-end: 0;
}
.sp-rows th.sp-col--num {
  text-align: end;
}
/* The subject cell: lead circle beside the name and what it was for. */
.sp-rows__who {
  align-items: center;
  display: flex;
  gap: 0.6rem;
  min-inline-size: 0;
}
/* The transaction browser's table is the row table at its densest: the
   amount cell can carry a status pill in front of the figure, and the
   two belong to one line — the pill qualifies that amount, so it must
   never wrap away from it. */
.sp-rows--txn td:last-child {
  white-space: nowrap;
}
/* A cell with nothing to say says so, quietly — an absence is a state,
   not a blank. (Not .sp-sub: that is a row's second line, not a cell.) */
.sp-rows__none {
  color: var(--text-faint);
}
/* A date in the money face, so dates stack as evenly as figures do. */
.sp-date {
  color: var(--text-faint);
  font-family: var(--sp-mono);
  font-size: 0.71875rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

@media (prefers-reduced-motion: no-preference) {
  .sp-rows tr td {
    transition: background-color .12s ease;
  }
}

/* Narrow screens: the two columns that only qualify the row give way,
   leaving date, counterparty and amount — the line still reads. */
@media (max-width: 48rem) {
  .sp-rows__drop {
    display: none;
  }
  /* Even three columns don't fit a phone: the date holds a nowrap
     "01 Aug 11:00" at its full mono measure, and the amount — the point
     of the row — starts past the panel's edge, reachable only by
     scrolling .sp-tablewrap. So the date breaks at its space, holding
     the day and month together on the first line and dropping the clock
     to the second (it costs no height — the counterparty is already two
     lines), and the subject's lead circle, which is decoration the row
     can read without, gives its measure to the figure. */
  .sp-rows .sp-date {
    white-space: normal;
  }
  .sp-rows td:first-child {
    min-inline-size: 3.25rem;
  }
  .sp-rows__who .sp-avatar {
    display: none;
  }
  .sp-rows th,
  .sp-rows td {
    padding-inline: 0.3rem;
  }
  .sp-rows th:first-child,
  .sp-rows td:first-child {
    padding-inline-start: 0;
  }
  .sp-rows th:last-child,
  .sp-rows td:last-child {
    padding-inline-end: 0;
  }
}

/* The reports table (.sp-report) and the tab bar above it live in
   65-reports.css — the P&L is a screen with its own vocabulary, not
   another row table. */

/* ── The counterparties table: a dense numeric grid ───────────────── */
.sp-table {
  border-collapse: collapse;
  font-size: 0.8125rem;
  width: 100%;
}
.sp-table th,
.sp-table td {
  padding: 0.5rem 0.75rem;
  text-align: end;
  white-space: nowrap;
}
.sp-table th:first-child,
.sp-table td:first-child {
  text-align: start;
}
/* The counterparty name cell leads with its avatar, same idiom as the
   row lists (.sp-acct, .sp-rows__who) — an entity is always named the
   same way, table cell or not. */
.sp-table__name {
  align-items: center;
  display: flex;
  gap: 0.6rem;
  white-space: nowrap;
}
.sp-table thead th {
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.sp-table thead th a {
  color: inherit;
  text-decoration: none;
}
.sp-table thead th a:hover {
  text-decoration: underline;
}
.sp-table tbody tr {
  border-block-start: 1px solid var(--line);
}

/* ── Filters: the transactions toolbar ────────────────────────────────
   A strip of labelled fields ending in the button that commits them.
   Standing on its own it is a card in its own right; mounted at the top
   of a list card (.rst-list, the transaction browser) it becomes that
   card's toolbar and gives up its own edges. Fields align on their
   bottoms — which is why every control being one height matters: it
   puts the labels on one line too. */
.sp-filters {
  align-items: end;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 0.75rem;
  margin: 0 0 1rem;
  padding: 0.75rem;
}
.rst-list > .sp-filters {
  border-block-start: 0;
  border-inline: 0;
  border-radius: 0;
  margin-block-end: 0;
}
/* The bulk strip mounts under its filter toolbar; one hairline apart. */
.rst-list > .sp-filters--bulk {
  border-block-start: 1px solid var(--line);
}
/* The transaction browser brings its own list card with it. Mounted
   inside a panel — the account, card and counterparty screens — that
   would be a card inside a card, with rows overrunning the inner edge,
   so the list sheds its own frame and the toolbar goes back to being a
   filter card standing on the panel. */
.sp-panel > .rst-list {
  border: 0;
  border-radius: 0;
  overflow: visible;
}
.sp-panel > .rst-list > .sp-filters {
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  margin-block-end: 0.75rem;
}

/* Quick windows above the filter form: quiet links in the sort-row
   voice, under the label that names them. (The CSV export used to sit
   at this strip's far end; it is a page action now and rides the
   topbar.) */
.sp-presets {
  align-items: baseline;
  border-block-end: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  font-size: 0.8125rem;
  gap: 0.75rem;
  margin: 0;
  padding: 0.5rem 0.75rem;
}
.sp-presets__label {
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.sp-presets a {
  color: var(--text-muted);
}
.sp-presets a:hover {
  color: var(--accent-ink);
}

/* (The strip's "More filters" tier — the settled/transfers checkbox
   pair and the disclosure that held the quiet half of the filters — is
   gone: the transaction browser asks one dimension at a time now, in
   the filter menu of 55-menu.css. The .sp-filters strip itself stays:
   the index screens still wear it, and so does the bulk-categorise
   form under the browser.) */

/* What the list adds up to. The count sits under the list; the
   reconciliation proof — the figure a drilled-in amount lands on — sits
   above the rows on its own tint, so the claim is read before the
   evidence it sums. */
.sp-count {
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin: 0.75rem 0 0;
}
.sp-recon {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin: 0.75rem;
  padding: 0.5rem 0.75rem;
}
.sp-recon .sp-money {
  color: var(--text);
  font-weight: 600;
}

/* The browser's rows inside its list card: the card has no padding of
   its own, so the table brings the inset the ruled rows used to carry.
   Mounted in a panel the list has already shed its frame, so the panel's
   own padding is the inset and the table gives its own back. */
.rst-list > .sp-tablewrap {
  padding: 0 0.75rem 0.25rem;
}
.sp-panel > .rst-list > .sp-tablewrap {
  padding-inline: 0;
}
.sp-panel > .rst-list > .sp-recon {
  margin-inline: 0;
}

/* ── The filter menu: one question at a time ──────────────────────────
   The transaction browser used to wear every filter at once — a strip
   of eight controls, half of them behind a "More filters" disclosure.
   It is a toolbar now: the search box, a Filter button, and Clear. The
   button is a native <details>, so the popover opens with no script;
   inside it each dimension (date, account, category, person, card,
   currency, direction, settled/transfers) is its own <details> holding
   its own little GET form. Each of those forms carries every *other*
   live filter as hidden inputs (HiddenParamsExcept), so applying one
   dimension narrows the screen instead of resetting it.

   static/menu.js is enhancement only: it closes an open menu on an
   outside click or Escape. Without it the disclosure still toggles. */
.sp-toolbar {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
/* Mounted at the top of the browser's list card the toolbar is that
   card's own header row, ruled off from the rows below; inside a panel
   the list has already shed its frame, so the panel's padding is the
   inset and the toolbar gives its own back. */
.rst-list > .sp-toolbar {
  border-block-end: 1px solid var(--line);
  padding: 0.75rem;
}
.sp-panel > .rst-list > .sp-toolbar {
  padding-inline: 0;
}
/* The popover is an absolutely positioned child of the toolbar, and the
   list card clips to its own rounded corners — which would shave the
   menu off on any short list (the "nothing matches these filters"
   screen most of all, where the filters are exactly what you came to
   change). The browser's rows carry their own inset, so the card can
   give up the clipping box without its table touching the corners. */
.sp-list--menu {
  overflow: visible;
}

/* The search box takes the toolbar's slack, but only so much — past
   about 26rem it is a trough. Its submit is icon-only: the placeholder
   already says what the field is for, and the label is there for
   screen readers. */
.sp-toolbar__search {
  display: flex;
  flex: 1 1 16rem;
  gap: 0.4rem;
  max-inline-size: 26rem;
}
.sp-toolbar__search .sp-input {
  flex: 1 1 auto;
  min-inline-size: 0;
}

/* ── The menu itself ─────────────────────────────────────────────── */
.sp-menu {
  position: relative;
}
/* The summary *is* the button: the disclosure triangle would read as a
   second affordance beside the filter glyph. */
.sp-menu > summary {
  list-style: none;
}
.sp-menu > summary::-webkit-details-marker {
  display: none;
}
.sp-menu[open] > summary {
  border-color: var(--accent);
  color: var(--accent-ink);
}
/* How many dimensions are in force, said on the closed button — the
   count is the only way a folded-away filter can announce itself. */
.sp-menu__count {
  background: var(--accent);
  border-radius: 999px;
  color: var(--on-accent);
  font-size: 0.71875rem;
  font-weight: 700;
  min-inline-size: 1.15rem;
  padding: 0.05rem 0.35rem;
  text-align: center;
}
.sp-menu__pop {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-1);
  inset-block-start: 100%;
  inset-inline-start: 0;
  margin-block-start: 0.4rem;
  /* On a narrow screen the toolbar can put the Filter button anywhere
     along the row, so the popover's own left edge is not the
     viewport's — cap it the same way the date-range popover already
     does, or a wide dimension body pushes the box off the right edge. */
  inline-size: max-content;
  max-inline-size: min(calc(100vw - 2rem), 17rem);
  min-inline-size: min(17rem, calc(100vw - 2rem));
  padding: 0.35rem;
  position: absolute;
  z-index: 10;
}

/* One dimension: a quiet row that opens into its own form. A dimension
   whose filter is live is rendered already open, so the menu can never
   show a folded row hiding an active filter. */
.sp-menu__dim {
  border-radius: var(--radius-ctl);
}
.sp-menu__dim > summary {
  align-items: center;
  border-radius: var(--radius-ctl);
  color: var(--text);
  cursor: pointer;
  display: flex;
  font-size: 0.8125rem;
  font-weight: 500;
  gap: 0.55rem;
  list-style: none;
  padding: 0.45rem 0.55rem;
}
.sp-menu__dim > summary::-webkit-details-marker {
  display: none;
}
.sp-menu__dim > summary:hover {
  background: var(--accent-weak);
  color: var(--accent-ink);
}
.sp-menu__dim > summary .sp-icon {
  color: var(--text-faint);
  flex: none;
}
.sp-menu__chev {
  display: inline-flex;
  margin-inline-start: auto;
}
.sp-menu__dim[open] > summary .sp-menu__chev {
  transform: rotate(180deg);
}
/* The dimension's form, indented under its own glyph. */
.sp-menu__dimbody {
  display: grid;
  gap: 0.5rem;
  padding: 0.1rem 0.55rem 0.6rem 2rem;
}
.sp-menu__dimbody .sp-input,
.sp-menu__dimbody select.sp-input {
  inline-size: 100%;
  min-inline-size: 0;
}
.sp-menu__check {
  align-items: center;
  color: var(--text);
  display: flex;
  font-size: 0.8125rem;
  gap: 0.45rem;
}
/* The sp-form-actions idea at menu scale: the submit sits apart from
   the fields it commits, one hairline down. */
.sp-menu__apply {
  border-block-start: 1px solid var(--line);
  display: flex;
  padding-block-start: 0.5rem;
}

@media (prefers-reduced-motion: no-preference) {
  .sp-menu__chev {
    transition: transform .12s ease;
  }
}

/* ── Date-range popover (static/daterange.js) ────────────────────────
   Progressive enhancement over the Date dimension's two native date
   inputs: a "Choose range" button opens a click-click calendar built
   client-side. The popover only ever writes ISO values back into the
   native inputs — they stay visible, editable, and the form's source
   of truth; the popover never submits. Hover/focus transitions on its
   buttons come free from 00-base.css's reduced-motion-guarded rule for
   plain `button` elements — nothing extra to gate here. */
.sp-daterange {
  position: relative;
}
.sp-daterange__trigger {
  justify-self: start;
}
.sp-daterange__pop {
  display: grid;
  gap: 0.6rem;
  inline-size: max-content;
  max-inline-size: calc(100vw - 2rem);
  z-index: 20;
}
.sp-daterange__presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}
.sp-daterange__preset {
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  color: var(--text);
  font-size: 0.8125rem;
  padding: 0.25rem 0.55rem;
}
.sp-daterange__preset:hover {
  background: var(--accent-weak);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.sp-daterange__cal {
  border-block-start: 1px solid var(--line);
  display: flex;
  gap: 0.9rem;
  padding-block-start: 0.6rem;
}
.sp-daterange__month {
  display: grid;
  gap: 0.3rem;
}
.sp-daterange__monthhead {
  align-items: center;
  display: flex;
  font-size: 0.8125rem;
  font-weight: 600;
  justify-content: space-between;
}
.sp-daterange__monthname {
  flex: 1;
  text-align: center;
}
.sp-daterange__nav {
  background: none;
  border: none;
  border-radius: var(--radius-ctl);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.875rem;
  line-height: 1;
  min-block-size: 1.5rem;
  min-inline-size: 1.5rem;
}
.sp-daterange__nav:hover {
  background: var(--accent-weak);
  color: var(--accent-ink);
}
.sp-daterange__grid {
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(7, minmax(1.5rem, 1fr));
}
.sp-daterange__dow {
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  text-align: center;
}
.sp-daterange__day {
  background: none;
  border: none;
  border-radius: var(--radius-ctl);
  color: var(--text);
  cursor: pointer;
  font-size: 0.8125rem;
  min-block-size: 1.5rem;
  min-inline-size: 1.5rem;
  padding: 0.15rem 0;
}
.sp-daterange__day:hover {
  background: var(--accent-weak);
}
.sp-daterange__day--out {
  color: var(--text-faint);
}
/* The stretch between the two ends fills flat; the ends themselves take
   the solid accent so "from" and "to" always read as the two pins on
   the range, never lost among the fill. */
.sp-daterange__day--in {
  background: var(--accent-weak);
  border-radius: 0;
}
.sp-daterange__day--from,
.sp-daterange__day--to {
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 700;
}

/* ── The account card strip: the ledger's scope, wearing its balances ─
   A scrolling row of small cards above the transaction list — one per
   account, plus "All accounts" at the head. Each card is a link that
   re-runs the screen's current filter against a different account, so
   the strip is a scope switch that shows you what you are switching
   between: the name, its mark, and the money behind it.

   The card in force says so with aria-current, and the accent border it
   wears is the visual echo of that attribute — the state is never
   carried by colour alone. */

/* Wrapper for absolute positioning of scroll arrows. */
.sp-cardstrip-wrap {
  position: relative;
}

.sp-cardstrip {
  display: flex;
  gap: 0.75rem;
  margin-block-end: 1rem;
  overflow-x: auto;
  /* Room inside the scroller for the cards' shadow and focus ring — a
     clipping box would shave both off. */
  padding: 0.15rem 0.15rem 0.6rem;
  scrollbar-color: var(--line-strong) transparent;
}
.sp-cardstrip::-webkit-scrollbar {
  height: 8px;
}
.sp-cardstrip::-webkit-scrollbar-track {
  background: transparent;
}
.sp-cardstrip::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 999px;
}
.sp-cardstrip__card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-1);
  color: var(--text);
  display: block;
  flex: none;
  min-inline-size: 11rem;
  padding: 0.75rem 0.9rem;
  text-decoration: none;
}
.sp-cardstrip__card:hover {
  border-color: var(--line-strong);
  text-decoration: none;
}
.sp-cardstrip__card[aria-current="true"] {
  background: var(--accent-weak);
  border-color: var(--accent);
}
.sp-cardstrip__label {
  align-items: center;
  color: var(--text-muted);
  display: flex;
  font-size: 0.8125rem;
  font-weight: 600;
  gap: 0.45rem;
  white-space: nowrap;
}
/* The card's mark is a strip-sized circle, not a row's lead — the
   initials keep the avatar's own extra-small size inside it. */
.sp-cardstrip__label .sp-avatar {
  block-size: 1.4rem;
  inline-size: 1.4rem;
}
.sp-cardstrip__bal {
  display: block;
  font-size: 1.0625rem;
  font-weight: 650;
  margin-block-start: 0.4rem;
  white-space: nowrap;
}

/* Scroll arrows: created by scrollpos.js only when the strip overflows.
   Absolutely positioned at the strip's left and right edges, circular,
   with surface background, shadow, and border. */
.sp-cardstrip__arrow {
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 50%;
  box-shadow: var(--shadow-1);
  color: var(--text);
  cursor: pointer;
  display: flex;
  font-size: 1.25rem;
  height: 2rem;
  justify-content: center;
  line-height: 1;
  padding: 0;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 2rem;
  z-index: 1;
}
.sp-cardstrip__arrow:hover {
  border-color: var(--line-strong);
}
.sp-cardstrip__arrow--left {
  left: 0;
}
.sp-cardstrip__arrow--right {
  right: 0;
}

@media (prefers-reduced-motion: no-preference) {
  .sp-cardstrip__card {
    transition: background-color .12s ease, border-color .12s ease;
  }
}

/* ── Charts: pictures the server draws ────────────────────────────────
   Zero JS means every chart is SVG the action computed (app.TidePaths)
   and the template only places. The CSS here is presentation only — it
   never knows the geometry, so a chart can change its shape without
   touching this file. Colours come from the SVG's own token references
   (var(--accent), var(--line)); the axis is the chart's caption. */
.sp-chart {
  margin: 1rem 0 0;
}
.sp-chart svg {
  /* Bounded by viewport width so the line stays a glance, not a wall. */
  block-size: clamp(7rem, 18vw, 10rem);
  display: block;
  inline-size: 100%;
}
/* The two ends of the window, in the money face — a chart with no axis
   is a decoration, and this one is meant to be read. */
.sp-chart__axis {
  color: var(--text-faint);
  display: flex;
  font-family: var(--sp-mono);
  font-size: 0.71875rem;
  font-variant-numeric: tabular-nums;
  justify-content: space-between;
  margin-block-start: 0.25rem;
}

/* ── Reports: the tab bar and the breakdown table ─────────────────────
   Reports is two readings of the same money — cash flow and runway — so
   the screen keeps one title and switches beneath it. The tab bar is
   that switch: quiet muted labels, the current one in accent ink over a
   2px accent underline that sits ON the bar's own hairline, so the
   active tab reads as the sheet in front. */
.sp-tabbar {
  border-block-end: 1px solid var(--line);
  display: flex;
  gap: 1.1rem;
  margin-block-end: 1.1rem;
}
.sp-tabbar a {
  border-block-end: 2px solid transparent;
  color: var(--text-muted);
  font-size: 0.875rem;
  font-weight: 500;
  margin-block-end: -1px;
  padding: 0.45rem 0.15rem 0.55rem;
  text-decoration: none;
}
.sp-tabbar a:hover {
  color: var(--text);
  text-decoration: none;
}
/* State never rests on colour alone: the active tab is also 600 and the
   only one wearing an underline. */
.sp-tabbar a[aria-current="page"] {
  border-block-end-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
/* The continuity-checks link stands on the canvas between the tab bar
   and the cards, not at the foot of a panel; it needs its own floor. */
.sp-tabbar + .sp-panel__more {
  margin-block: -0.25rem 1rem;
}

/* ── The breakdown table ──────────────────────────────────────────────
   A P&L is a grid that outgrows its panel by design — a year of months
   plus a total — so it scrolls inside .sp-scroll and keeps the category
   column pinned to the left edge while the periods slide past it.
   (.sp-scroll--toend adds no styling: it is the hook scrollpos.js reads
   to open the table at the newest period. Without JS the table simply
   starts at the oldest, which is the correct no-JS reading.) */
.sp-report {
  border-collapse: collapse;
  font-size: 0.8125rem;
  min-inline-size: max-content;
  width: 100%;
}
.sp-report th,
.sp-report td {
  padding: 0.45rem 0.7rem;
  white-space: nowrap;
}
.sp-report thead th {
  border-block-end: 1px solid var(--line);
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  text-align: end;
}
.sp-report thead th:first-child,
.sp-report tbody th,
.sp-report tfoot th {
  text-align: start;
}
/* The category column is the table's spine: it stays put, carries the
   panel's own ground so figures never show through it, and closes with
   a hairline so the scroll reads as movement behind an edge. That edge
   is a box-shadow, not a border: a border would join the row rules into
   the cell's box and shift the column's width by a pixel, while the
   shadow is drawn outside the cell and can't disturb the grid. */
.sp-report thead th:first-child,
.sp-report tbody tr:not(.sp-report__group) th,
.sp-report tfoot th {
  background: var(--surface);
  box-shadow: 1px 0 0 var(--line);
  inset-inline-start: 0;
  position: sticky;
  z-index: 1;
}
.sp-report tbody th {
  font-weight: 500;
}
.sp-report__num {
  text-align: end;
}
/* The report table's drill link is the money element itself (no nested
   span), so the money-face link rule never matches it — same voice,
   own selector. */
.sp-report__num a {
  color: inherit;
  text-decoration: none;
}
.sp-report__num a:hover {
  text-decoration: underline;
}
.sp-report tbody tr + tr {
  border-block-start: 1px solid var(--line);
}
.sp-report tfoot tr {
  border-block-start: 2px solid var(--line-strong);
  font-weight: 600;
}
.sp-report__group th {
  color: var(--text-faint);
  font-size: 0.71875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  padding-block: 0.5rem 0.125rem;
  text-transform: uppercase;
}
.sp-report tbody + tbody .sp-report__group th {
  padding-block-start: 0.9rem;
}
.sp-report__subtotal th,
.sp-report__subtotal td {
  border-block-start: 1px solid var(--line-strong);
  font-weight: 600;
}

/* ── The forecast half of the breakdown ───────────────────────────────
   The runway table states recorded months and projected ones in one
   grid, which is only honest if the eye can tell them apart without
   reading the header: the projection is held back to faint ink, and the
   month the record stops is marked with an accent rule down the column
   — the same accent the chart's dashed line wears. */
.sp-report__fc {
  color: var(--text-faint);
}
.sp-report__fcdiv {
  border-inline-start: 2px solid var(--accent);
}

/* ── The burn meter ───────────────────────────────────────────────────
   One category's share of a month's spending, drawn once beside the
   figure it restates. The track is neutral and the fill is the accent:
   a share of spending is not a tone — green is reserved for credits,
   and red would make every expense an alarm. */
.sp-meter {
  background: var(--neutral-weak);
  block-size: 0.375rem;
  border-radius: 999px;
  display: inline-block;
  inline-size: 7rem;
  max-inline-size: 40%;
  overflow: hidden;
  vertical-align: middle;
}
.sp-meter i {
  background: var(--accent);
  border-radius: 999px;
  block-size: 100%;
  display: block;
}
/* A delta inside a numeric cell belongs against the figures' edge, not
   the cell's start — the trend column reads down its right side like
   every other number on the screen. */
.sp-stat__delta--end {
  justify-content: flex-end;
  margin-block-start: 0;
}

/* ── The money-movement key ───────────────────────────────────────────
   Two bars per period need naming once. Money in is the accent; money
   out is neutral ink held back — a debit is not an alarm, and green is
   reserved for credits, so neither bar is a tone. */
.sp-chartkey {
  color: var(--text-muted);
  display: flex;
  font-size: 0.71875rem;
  gap: 0.9rem;
}
/* It captions the chart, so it sits with the chart, not in the panel's
   paragraph rhythm. */
.sp-panel > .sp-chartkey {
  margin: 0;
}
.sp-chartkey + .sp-chart {
  margin-block-start: 0.4rem;
}
.sp-chartkey span::before {
  block-size: 0.5rem;
  border-radius: 50%;
  content: "";
  display: inline-block;
  inline-size: 0.5rem;
  margin-inline-end: 0.35rem;
}
.sp-chartkey__in::before {
  background: var(--accent);
}
.sp-chartkey__out::before {
  background: var(--neutral-ink);
  opacity: 0.55;
}
/* The runway chart's key names two halves of ONE line, so its markers
   are the line itself: a solid rule for the recorded months, the same
   rule dashed for the projection. A second colour would say they are
   different measurements. */
.sp-chartkey__rec::before,
.sp-chartkey__proj::before {
  background: var(--accent);
  block-size: 2px;
  border-radius: 0;
  inline-size: 1rem;
}
.sp-chartkey__proj::before {
  background: repeating-linear-gradient(
    90deg,
    var(--accent) 0 4px,
    transparent 4px 8px
  );
}

