/*
 * money map — application shell styling
 * -------------------------------------
 * Builds the base layout ON TOP of tokens.css. No raw colours or sizes here:
 * everything references a --mm-* token so light/dark and the design scale stay
 * in one place. This file styles the base.html shell only (header, main,
 * footer, primitives); feature chunks add their own scoped styles.
 */

/* ---- Reset / base -------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

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

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: hsl(var(--mm-bg));
  color: hsl(var(--mm-text));
  font-family: var(--mm-font-sans);
  font-size: var(--mm-text-base);
  line-height: var(--mm-leading-normal);
  font-feature-settings: "cv11", "ss01", "kern";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3 {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-bold);
  line-height: var(--mm-leading-tight);
  letter-spacing: var(--mm-tracking-tight);
  margin: 0 0 var(--mm-space-4);
}

p {
  margin: 0 0 var(--mm-space-4);
  max-width: 65ch;
}

a {
  color: hsl(var(--mm-accent));
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* A link inside running prose, as opposed to the navigation chrome the base rule
   above is tuned for. Dropping the underline is fine for a nav item sitting on
   its own; inside a sentence it leaves `a:hover` as the entire affordance, and
   hover does not exist on touch. Three templates already asked for this class and
   nothing defined it, so they silently rendered as the base rule — the phantom
   looked styled in review and was not. */
.mm-link {
  color: hsl(var(--mm-accent));
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

/* ---- Focus visibility (a11y — never removed) ---------------------- */
:focus-visible {
  outline: 2px solid hsl(var(--mm-ring));
  outline-offset: 2px;
  border-radius: var(--mm-radius-sm);
}

/* ---- Layout scaffolding ------------------------------------------- */
.mm-shell-header,
.mm-shell-footer {
  border-color: hsl(var(--mm-border));
  background-color: hsl(var(--mm-surface) / 0.6);
  backdrop-filter: saturate(1.4) blur(8px);
}

.mm-shell-header {
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
  position: sticky;
  top: 0;
  z-index: 10;
}

.mm-shell-footer {
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
  margin-top: auto;
}

.mm-container {
  width: 100%;
  max-width: var(--mm-content-width);
  margin-inline: auto;
  padding-inline: var(--mm-space-5);
}

.mm-shell-header .mm-container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-4);
  min-height: 4rem;
}

main.mm-main {
  flex: 1;
  padding-block: var(--mm-space-8);
}

.mm-shell-footer .mm-container {
  padding-block: var(--mm-space-5);
  color: hsl(var(--mm-text-subtle));
  font-size: var(--mm-text-sm);
}

/* ---- Brand wordmark ----------------------------------------------- */
.mm-brand {
  display: inline-flex;
  /* `baseline` was right for a text glyph and is wrong for a box: an SVG's
     baseline is its bottom edge, so the mark hung below the wordmark's descender
     line. Centred, the two are aligned on the axis a reader actually sees. */
  align-items: center;
  gap: 0.38em;
  /* The wordmark is two words and was breaking across two lines as soon as the
     nav got tight, which makes the header a different height on narrow screens
     and reads as a layout bug rather than a brand. It never wraps; the nav beside
     it scrolls instead. */
  flex: none;
  white-space: nowrap;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-bold);
  font-size: var(--mm-text-lg);
  letter-spacing: var(--mm-tracking-tight);
  color: hsl(var(--mm-text));
}

.mm-brand:hover {
  text-decoration: none;
}

/* The mark is an inline <svg> now, not the character "◆". A glyph has no size of
   its own — it took the wordmark's font-size and sat on the text baseline, which
   is why it always looked slightly high and slightly small next to "money map". A
   box does: 1.15em keeps it optically level with the cap height beside it at any
   type size, and `align-items: center` on .mm-brand (below) does the rest. */
.mm-brand-mark {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  color: hsl(var(--mm-accent));
}

.mm-brand-word {
  /* The wordmark is set in the display face; the mark beside it is geometry, so
     it is unaffected by which face actually resolves. */
  font-family: var(--mm-font-display);
}

/* ---- Skip link (a11y — bypass the nav for keyboard/AT users) ------- */
.mm-skip-link {
  position: absolute;
  left: var(--mm-space-3);
  top: var(--mm-space-3);
  z-index: 20;
  padding: var(--mm-space-2) var(--mm-space-4);
  border-radius: var(--mm-radius-sm);
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  color: hsl(var(--mm-text));
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  text-decoration: none;
  /* Visually hidden until focused, without leaving the a11y tree. */
  transform: translateY(-150%);
  transition: transform var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-skip-link:focus-visible {
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .mm-skip-link {
    transition: none;
  }
}

/* ---- Primary navigation (app shell, RBAC-gated — C62) ------------- */
/* Sits between the brand and the header actions; grows to take the middle
   space on desktop and drops to its own scrollable row on narrow screens. */
.mm-nav {
  flex: 1 1 auto;
  min-width: 0; /* allow the scroll container to shrink below content width */
}

.mm-header-actions {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--mm-space-2);
  margin-left: auto;
}

.mm-nav-list {
  display: flex;
  align-items: center;
  gap: var(--mm-space-1);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none; /* Firefox — the row scrolls, chrome stays clean */
}

.mm-nav-list::-webkit-scrollbar {
  display: none;
}

.mm-nav-item {
  flex: 0 0 auto;
}

.mm-nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem; /* 44px touch target */
  padding: var(--mm-space-2) var(--mm-space-3);
  border-radius: var(--mm-radius-sm);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text-muted));
  white-space: nowrap;
  text-decoration: none;
  transition: color var(--mm-duration-fast) var(--mm-ease-out),
    background-color var(--mm-duration-fast) var(--mm-ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .mm-nav-link:not(.mm-nav-link--pending):hover {
    color: hsl(var(--mm-text));
    background-color: hsl(var(--mm-surface));
  }
}

/* The current page's marker used to be `border-bottom: 2px solid` on this element
   — which also carries `border-radius` for its hover pill, so the bottom border
   followed the corner curve and turned up at both ends. At 2px over a 6px radius
   the result is a shallow smile under the active item, and it reads as a
   rendering fault rather than as an indicator.

   A pseudo-element instead: a straight bar, inset to the link's padding so it
   underlines the *word* rather than the whole hit area, which is also what makes
   it look deliberate at a glance. */
.mm-nav-link--active {
  color: hsl(var(--mm-accent-text));
}

.mm-nav-link--active::after {
  content: "";
  position: absolute;
  left: var(--mm-space-3);
  right: var(--mm-space-3);
  bottom: 0.55rem;
  height: 2px;
  border-radius: 1px;
  background-color: hsl(var(--mm-accent));
  /* Drawn outward from the centre of the word it belongs to. One shot on load —
     this only changes when the page does. */
  transform-origin: center;
  animation: mm-fill var(--mm-duration-base) var(--mm-ease-out) both;
}

/* A planned Phase-7 surface not yet wired — reads as present-but-inactive,
   never a dead link. Muted, non-interactive, no pointer affordance. */
.mm-nav-link--pending {
  color: hsl(var(--mm-text-subtle));
  cursor: default;
}

/* Narrow viewports: wrap the nav to a full-width scrollable second row while
   the brand + actions stay on the first row. No JS drawer needed. */
@media (max-width: 60rem) {
  .mm-shell-header .mm-container {
    flex-wrap: wrap;
    row-gap: var(--mm-space-1);
  }
  .mm-nav {
    order: 3;
    flex-basis: 100%;
  }
}

/* ---- Buttons ------------------------------------------------------- */
.mm-btn {
  --_bg: var(--mm-accent);
  --_fg: var(--mm-accent-foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--mm-space-2);
  min-height: 2.75rem; /* 44px touch target */
  padding: var(--mm-space-2) var(--mm-space-5);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--_fg));
  background-color: hsl(var(--_bg));
  border: var(--mm-border-width) solid transparent;
  border-radius: var(--mm-radius-md);
  cursor: pointer;
  transition: background-color var(--mm-duration-fast) var(--mm-ease-out),
    transform var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-btn:hover {
  text-decoration: none;
}

.mm-btn:active {
  transform: scale(0.97);
}

.mm-btn--secondary {
  --_bg: var(--mm-surface-raised);
  --_fg: var(--mm-text);
  border-color: hsl(var(--mm-border-strong));
}

/* Destructive affordance — a restrained danger ghost (not a filled alarm on
   every row). Danger is a state colour here, never a second brand accent. */
.mm-btn--danger {
  --_fg: var(--mm-danger);
  background-color: transparent;
  border-color: hsl(var(--mm-danger) / 0.45);
}

@media (hover: hover) and (pointer: fine) {
  .mm-btn:hover {
    background-color: hsl(var(--mm-accent-hover));
  }
  .mm-btn--secondary:hover {
    background-color: hsl(var(--mm-surface));
  }
  .mm-btn--danger:hover {
    background-color: hsl(var(--mm-danger) / 0.1);
    border-color: hsl(var(--mm-danger) / 0.6);
  }
}

/* Disabled/busy — suppressed opacity + blocked cursor so the button reads as
   inactive during the multi-second WebAuthn ceremony. The JS also sets
   aria-busy=true and swaps the label text to "Waiting for your device…". */
.mm-btn:disabled,
.mm-btn[aria-busy="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.mm-btn:disabled:active,
.mm-btn[aria-busy="true"]:active {
  transform: none;
}

/* ---- Card ---------------------------------------------------------- */
.mm-card {
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-lg);
  padding: var(--mm-space-6);
  box-shadow: var(--mm-shadow-sm);
}

/* ---- Hero (the shell's one premium "moment") ---------------------- */
.mm-hero {
  display: grid;
  gap: var(--mm-space-6);
  padding-block: var(--mm-space-6) var(--mm-space-4);
}

.mm-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--mm-space-2);
  width: fit-content;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-semibold);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-muted));
}

.mm-eyebrow::before {
  content: "";
  width: 1.75rem;
  height: 2px;
  background-color: hsl(var(--mm-accent));
  border-radius: var(--mm-radius-full);
}

.mm-hero h1 {
  font-size: var(--mm-text-4xl);
  max-width: 16ch;
}

/* Editorial italic emphasis (à la the matador reference craft) */
.mm-hero h1 em {
  font-family: var(--mm-font-serif);
  font-style: italic;
  font-weight: var(--mm-weight-normal);
  letter-spacing: 0;
  color: hsl(var(--mm-accent));
}

.mm-hero .mm-lede {
  font-size: var(--mm-text-lg);
  color: hsl(var(--mm-text-muted));
  max-width: 52ch;
}

.mm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-3);
}

/* ---- HTMX demo panel ---------------------------------------------- */
.mm-demo {
  margin-top: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-4);
}

.mm-demo h2 {
  font-size: var(--mm-text-xl);
  margin: 0;
}

.mm-demo-result {
  min-height: 3rem;
  display: flex;
  align-items: center;
  color: hsl(var(--mm-text-muted));
  font-size: var(--mm-text-sm);
}

.mm-pong {
  display: inline-flex;
  align-items: center;
  gap: var(--mm-space-2);
  padding: var(--mm-space-2) var(--mm-space-4);
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-success) / 0.12);
  color: hsl(var(--mm-success));
  font-weight: var(--mm-weight-medium);
}

.mm-pong::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-success));
}

/* Enter animation — transform + opacity only (Emil) */
.mm-pong {
  animation: mm-fade-in var(--mm-duration-base) var(--mm-ease-out);
}

@keyframes mm-fade-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ---- Theme toggle -------------------------------------------------- */
.mm-theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem; /* 44px touch target */
  height: 2.75rem;
  padding: 0;
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-surface-raised));
  color: hsl(var(--mm-text-muted));
  /* No font-size here any more. The icons were the characters ☾ / ☀, whose size
     *was* their font-size, so this rule had to fight the user-agent's ~13px
     button default or draw a 10px crescent adrift in a 44px box —
     moneymap/tests/test_glyph_controls_are_sized.py existed for that and is
     deleted with it. An <svg> is sized by .mm-icon, in one place, for every icon
     in the app. */
  line-height: 1;
  cursor: pointer;
  transition:
    background-color var(--mm-duration-fast) var(--mm-ease-out),
    color var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-theme-toggle .mm-icon-dark {
  display: none;
}

.dark .mm-theme-toggle .mm-icon-light {
  display: none;
}

/* `block`, not `inline`: .mm-icon is an inline-block whose line box would add a
   descender gap inside a centred flex button and push the icon a pixel high. */
.dark .mm-theme-toggle .mm-icon-dark {
  display: block;
}

@media (hover: hover) and (pointer: fine) {
  .mm-theme-toggle:hover {
    background-color: hsl(var(--mm-surface));
    color: hsl(var(--mm-text));
  }
}

/* A control the finger lands on should acknowledge the press before the state it
   toggles has finished changing. 0.94 rather than something softer because the
   button is small: on a 44px box a 2% squash is invisible. */
.mm-theme-toggle:active {
  transform: scale(0.94);
}

/* ---- Responsive ---------------------------------------------------- */
@media (max-width: 640px) {
  .mm-hero h1 {
    font-size: var(--mm-text-3xl);
  }
}

/* ---- Buttons: full-width variant (auth forms) --------------------- */
.mm-btn--block {
  width: 100%;
}

/* ---- Auth pages (login / signup / verification / reset) [C11] ------ */
/* Structural + accessible defaults on the design tokens. A designer pass
   refines the expression; the markup underneath stays semantic. */
.mm-auth {
  width: 100%;
  max-width: 26rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
}

.mm-auth-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-auth-heading {
  font-size: var(--mm-text-2xl);
  margin: 0;
}

.mm-auth-sub {
  color: hsl(var(--mm-text-muted));
  margin: 0;
}

.mm-auth-alt,
.mm-auth-fineprint {
  margin: 0;
  font-size: var(--mm-text-sm);
}

.mm-auth-fineprint {
  color: hsl(var(--mm-text-subtle));
}

/* ---- Forms & fields ----------------------------------------------- */
.mm-form {
  display: grid;
  gap: var(--mm-space-4);
  margin: 0;
}

.mm-field {
  display: grid;
  gap: var(--mm-space-2);
}

/* `display: grid` above beats the user-agent's `[hidden] { display: none }`, because an
   author rule outranks a UA rule at every specificity. So `destination_create.html`'s
   script, which sets `wrapper.hidden` to hide the Portfolio field on a non-portfolio
   destination type, has never hidden anything — the field sat visible on every type since
   the day it was written, and nothing failed because `hidden` was set exactly as intended.
   Found by looking at the page, not by a test. Any `.mm-field` given `hidden` needs this. */
.mm-field[hidden] {
  display: none;
}

.mm-label {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-required {
  color: hsl(var(--mm-danger));
}

/* Style the widgets allauth renders (input / select) via the form scope.
   Exclude checkbox/radio so they keep native sizing; those get their own rule. */
.mm-form input:not([type=checkbox]):not([type=radio]),
.mm-form select,
.mm-form textarea {
  width: 100%;
  min-height: 2.75rem; /* 44px touch target */
  padding: var(--mm-space-2) var(--mm-space-3);
  font-family: var(--mm-font-sans);
  font-size: var(--mm-text-base);
  color: hsl(var(--mm-text));
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  border-radius: var(--mm-radius-md);
  transition: border-color var(--mm-duration-fast) var(--mm-ease-out),
    box-shadow var(--mm-duration-fast) var(--mm-ease-out);
}

/* Textareas grow vertically; keep a comfortable line rhythm and free resize. */
.mm-form textarea {
  min-height: 3.5rem;
  line-height: var(--mm-leading-snug);
  resize: vertical;
}

.mm-form input:not([type=checkbox]):not([type=radio])::placeholder,
.mm-form textarea::placeholder {
  color: hsl(var(--mm-text-subtle));
}

.mm-form input:not([type=checkbox]):not([type=radio]):focus-visible,
.mm-form select:focus-visible,
.mm-form textarea:focus-visible {
  outline: none;
  border-color: hsl(var(--mm-accent));
  box-shadow: 0 0 0 3px hsl(var(--mm-ring) / 0.28);
}

.mm-field--invalid input:not([type=checkbox]):not([type=radio]),
.mm-field--invalid select,
.mm-field--invalid textarea {
  border-color: hsl(var(--mm-danger));
}

/* Checkbox / radio — native widget, accented, inline with its label */
.mm-form input[type=checkbox],
.mm-form input[type=radio] {
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  accent-color: hsl(var(--mm-accent));
  cursor: pointer;
}

.mm-field--checkbox {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--mm-space-3);
}

.mm-field--checkbox .mm-label {
  font-weight: var(--mm-weight-normal);
  cursor: pointer;
}

.mm-help {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-subtle));
  max-width: none;
}

/* ---- Errors & messages -------------------------------------------- */
.mm-field-errors,
.mm-form-errors {
  margin: 0;
  padding: 0;
  list-style: none;
  color: hsl(var(--mm-danger));
  font-size: var(--mm-text-sm);
}

.mm-form-errors {
  padding: var(--mm-space-3) var(--mm-space-4);
  border: var(--mm-border-width) solid hsl(var(--mm-danger) / 0.4);
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-danger) / 0.08);
}

.mm-form-error {
  margin: 0;
  max-width: none;
  color: hsl(var(--mm-danger));
}

.mm-auth-messages {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-2);
}

.mm-msg {
  padding: var(--mm-space-3) var(--mm-space-4);
  border-radius: var(--mm-radius-md);
  font-size: var(--mm-text-sm);
  background-color: hsl(var(--mm-surface));
  border: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-msg--error {
  color: hsl(var(--mm-danger));
  border-color: hsl(var(--mm-danger) / 0.4);
  background-color: hsl(var(--mm-danger) / 0.08);
}

.mm-msg--success {
  color: hsl(var(--mm-success-text));
  border-color: hsl(var(--mm-success) / 0.4);
  background-color: hsl(var(--mm-success) / 0.1);
}

/* ---- Session management (list + revoke) [C14] --------------------- */
/* Structural defaults on the design tokens; the markup reuses .mm-card /
   .mm-form / .mm-btn so a designer pass refines expression without touching it. */
.mm-sessions {
  width: 100%;
  max-width: 44rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

.mm-sessions-head {
  display: grid;
  gap: var(--mm-space-2);
}

.mm-session-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-3);
}

.mm-session {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-4);
  align-items: flex-start;
  justify-content: space-between;
  /* One calm entrance — transform + opacity only; reduced-motion disables it
     via the global guard. No stagger: a security surface stays crisp, not showy. */
  animation: mm-fade-in var(--mm-duration-base) var(--mm-ease-out) both;
}

/* The current session gets a subtle accent edge so it reads as "you are here".
   Both values in ONE declaration: `box-shadow` is a single longhand, not additive
   across rules, and `.mm-card` above sets `var(--mm-shadow-sm)` at the same (0,1,0)
   specificity. Naming only the accent edge here therefore *replaced* the card
   elevation, leaving the one card on the page meant to stand out the only flat one
   in the list. Held by `test_the_current_session_keeps_the_card_elevation`. */
.mm-session--current {
  border-color: hsl(var(--mm-accent) / 0.5);
  box-shadow:
    inset 3px 0 0 0 hsl(var(--mm-accent)),
    var(--mm-shadow-sm);
}

.mm-session-info {
  display: grid;
  gap: var(--mm-space-3);
  min-width: 0;
  flex: 1 1 18rem;
}

.mm-session-device {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-3);
  word-break: break-word;
}

/* The device string is the row's primary label — carry the emphasis here so the
   status/badge that precedes it reads as a secondary state marker. */
.mm-session-name {
  min-width: 0;
  word-break: break-word;
}

/* Liveness marker for non-current sessions — a small success dot, not a loud
   pill. Uses the semantic success state colour, distinct from the brand accent
   that flags the current device. */
.mm-session-status {
  display: inline-flex;
  align-items: center;
  gap: var(--mm-space-2);
  flex-shrink: 0;
  font-family: var(--mm-font-sans);
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-medium);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-muted));
}

.mm-session-dot {
  width: 0.5rem;
  height: 0.5rem;
  flex-shrink: 0;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-success));
}

/* A session held at the two-factor challenge is not live and must not wear the
   success colour. The label beside it carries the meaning — the dot only has to
   stop reading as "fine", so it takes the warning state colour and the status text
   drops the muted tone it shares with "Active". */
.mm-session-status--pending {
  color: hsl(var(--mm-warning-text));
}

.mm-session-dot--pending {
  background-color: hsl(var(--mm-warning));
}

/* The one sentence that says what a challenged session can and cannot do. Sized
   as body text, not as the footnote the raw User-Agent gets: it is the reason the
   card is on the page. */
.mm-session-pending-note {
  margin: 0;
  max-width: 60ch;
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-normal);
  color: hsl(var(--mm-text-muted));
}

/* The raw User-Agent, kept under the parsed device name as the evidence for it.
   Muted, small, and monospace so it reads as a footnote to the label and never as
   the label — that inversion is what made every row unreadable. */
.mm-session-agent {
  margin: 0;
  font-family: var(--mm-font-mono);
  font-size: var(--mm-text-xs);
  line-height: var(--mm-leading-snug);
  color: hsl(var(--mm-text-subtle));
  /* `anywhere`, not `break-word`: this is attacker-controlled text and a single
     400-character token with no spaces would otherwise widen the card. `anywhere`
     is the only hint that reduces min-content width — and the `min-width` is its
     counterweight, without which it happily renders one character per line. */
  overflow-wrap: anywhere;
  min-width: 8rem;
}

.mm-session-meta {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--mm-space-2) var(--mm-space-4);
}

/* Same stretched-auto-rows drift as `.mm-invite-meta div` below. */
.mm-session-meta div {
  display: grid;
  align-content: start;
  gap: 0.125rem;
}

.mm-session-meta dt {
  font-size: var(--mm-text-xs);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
}

.mm-session-meta dd {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
  word-break: break-word;
}

.mm-session-action {
  flex: 0 0 auto;
}

/* Per-session revoke form is a bare button; drop the grid gap the .mm-form
   scope would otherwise add. */
.mm-session-action .mm-form {
  gap: 0;
}

.mm-sessions-bulk {
  margin-top: var(--mm-space-2);
  display: grid;
  gap: var(--mm-space-2);
}

.mm-sessions-bulk-note {
  margin: 0;
  text-align: center;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-subtle));
}

/* Empty state — a real card, not a bare line; sensible when no sessions exist. */
.mm-session-empty {
  display: grid;
  gap: var(--mm-space-2);
  text-align: center;
}

.mm-session-empty-title {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-session-empty .mm-auth-sub {
  margin-inline: auto;
}

/* Narrow screens: give the revoke control a full-width tap target so it never
   crowds the session metadata. */
@media (max-width: 560px) {
  .mm-session-action,
  .mm-session-action .mm-btn {
    width: 100%;
  }
}

/* ---- MFA / TOTP UI [C13] ------------------------------------------ */
/* .mm-code — monospace block for setup keys, provisioning URIs, and
   recovery codes. user-select:all lets a single click select the full
   string for copy; the tinted surface makes it visually distinct from
   prose. Works in both :root (light) and .dark via the semantic tokens. */
.mm-code {
  display: block;
  font-family: var(--mm-font-mono);
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-snug);
  color: hsl(var(--mm-text));
  background-color: hsl(var(--mm-surface));
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  border-radius: var(--mm-radius-md);
  padding: var(--mm-space-2) var(--mm-space-3);
  word-break: break-all;
  user-select: all;
  -webkit-user-select: all;
}

/* Long otpauth:// URIs must wrap on narrow viewports without clipping
   the auth card. overflow-wrap:anywhere is the strongest safe wrap hint. */
.mm-code--wrap {
  overflow-wrap: anywhere;
  word-break: normal;
}

/* Recovery code list — two-column grid on wider auth cards, single
   column on narrow viewports. role="list" on the <ul> is preserved. */
.mm-recovery-codes {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--mm-space-2);
}

/* Ensure each code fills its grid cell */
.mm-recovery-codes .mm-code {
  width: 100%;
}

/* ---- Status badges ------------------------------------------------- */
/* Inline pill used in invite list and other status displays. */
.mm-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem var(--mm-space-2);
  font-family: var(--mm-font-sans);
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-medium);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  border-radius: var(--mm-radius-full);
  border: var(--mm-border-width) solid transparent;
  background-color: hsl(var(--mm-surface));
  color: hsl(var(--mm-text-muted));
}

.mm-badge--success {
  color: hsl(var(--mm-success-text));
  background-color: hsl(var(--mm-success) / 0.1);
  border-color: hsl(var(--mm-success) / 0.35);
}

.mm-badge--danger {
  color: hsl(var(--mm-danger));
  background-color: hsl(var(--mm-danger) / 0.08);
  border-color: hsl(var(--mm-danger) / 0.35);
}

.mm-badge--warning {
  color: hsl(var(--mm-warning-text));
  background-color: hsl(var(--mm-warning) / 0.1);
  border-color: hsl(var(--mm-warning) / 0.35);
}

.mm-badge--active {
  color: hsl(var(--mm-accent-text));
  background-color: hsl(var(--mm-accent) / 0.1);
  border-color: hsl(var(--mm-accent) / 0.35);
}

/* ---- Invite pages (list / create / accept) [C21] ------------------ */
.mm-invites {
  width: 100%;
  max-width: 52rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

.mm-invites-head {
  display: grid;
  gap: var(--mm-space-2);
  /* A page header has to separate itself from what follows. Without this the
     strategies verdict sat flush against the header's own button, reading as one
     stack — the pages using this component happened to have a top-margined first
     child, so the gap was borrowed rather than owned. */
  margin-bottom: var(--mm-space-4);
}

.mm-invite-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-3);
}

.mm-invite {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-4);
  align-items: flex-start;
  justify-content: space-between;
}

.mm-invite-info {
  display: grid;
  gap: var(--mm-space-3);
  min-width: 0;
  flex: 1 1 18rem;
}

.mm-invite-email {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
  word-break: break-word;
}

.mm-invite-meta {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--mm-space-2) var(--mm-space-4);
}

/* `align-content: start` is what makes the 0.125rem gap the rendered gap. The
   auto-fit columns stretch every cell to the tallest one, and a stretched grid
   whose rows are `auto` distributes the leftover height *between* them — so as
   soon as one neighbour's value wrapped to two lines, every other value floated
   ~20px below its own label and lined up with nothing. */
.mm-invite-meta div {
  display: grid;
  align-content: start;
  gap: 0.125rem;
}

.mm-invite-meta dt {
  font-size: var(--mm-text-xs);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
}

.mm-invite-meta dd {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-2);
  word-break: break-word;
}

.mm-invite-redeemer {
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-subtle));
}

/* The accept link, printed on every usable invite because nothing in this
   product mails it. It is long and unbroken and is meant to be selected in one
   drag, so it gets its own surface, breaks anywhere rather than pushing the card
   wide, and selects whole on a single click. */
.mm-invite-link {
  display: grid;
  gap: var(--mm-space-1);
  min-width: 0;
  margin-top: var(--mm-space-2);
}

/* In the portal's invite table the same block sits inside a cell, where an
   unbounded URL would widen the column past every other one. */
.mm-invite-link--cell {
  max-width: 24rem;
  font-weight: var(--mm-weight-normal);
}

.mm-invite-link-url {
  display: block;
  padding: var(--mm-space-2) var(--mm-space-3);
  border: 1px solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-sm);
  background: hsl(var(--mm-surface));
  color: hsl(var(--mm-text));
  overflow-wrap: anywhere;
  word-break: break-all;
  user-select: all;
}

.mm-invite-action {
  flex: 0 0 auto;
}

.mm-invite-action .mm-form {
  gap: 0;
}

.mm-invite-empty {
  display: grid;
  gap: var(--mm-space-2);
  text-align: center;
}

.mm-invite-empty-title {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-invite-empty .mm-auth-sub {
  margin-inline: auto;
}

/* Invite create form */
.mm-invite-create {
  width: 100%;
  max-width: 36rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

.mm-invite-form-card {
  display: grid;
  gap: var(--mm-space-4);
}

/* Invite accept page */
.mm-invite-accept {
  width: 100%;
  max-width: 28rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
}

.mm-invite-accept-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-invite-accept-card--error {
  border-color: hsl(var(--mm-danger) / 0.4);
}

.mm-invite-expiry {
  font-size: var(--mm-text-sm);
}

.mm-invite-accept-form {
  margin-top: var(--mm-space-2);
}

.mm-invite-note {
  font-size: var(--mm-text-sm);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
  padding-top: var(--mm-space-3);
}

@media (max-width: 560px) {
  .mm-invite-action,
  .mm-invite-action .mm-btn {
    width: 100%;
  }
}

/* ---- Income-source list (C28) -------------------------------------- */
.mm-income-sources {
  width: 100%;
  max-width: 52rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

.mm-income-sources-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--mm-space-2);
}

.mm-income-source-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-3);
}

.mm-income-source {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-4);
  align-items: flex-start;
  justify-content: space-between;
}

.mm-income-source-info {
  display: grid;
  gap: var(--mm-space-3);
  min-width: 0;
  flex: 1 1 18rem;
}

.mm-income-source-name {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
  word-break: break-word;
}

.mm-income-source-meta {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--mm-space-2) var(--mm-space-4);
}

/* Same stretched-auto-rows drift as `.mm-invite-meta div` above. */
.mm-income-source-meta div {
  display: grid;
  align-content: start;
  gap: 0.125rem;
}

.mm-income-source-meta dt {
  font-size: var(--mm-text-xs);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
}

.mm-income-source-meta dd {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-2);
  word-break: break-word;
}

.mm-income-source-action {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-2);
  justify-content: flex-end;
}

.mm-income-source-empty {
  display: grid;
  gap: var(--mm-space-2);
  text-align: center;
}

.mm-income-source-empty-title {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-income-source-empty .mm-auth-sub {
  margin-inline: auto;
}

/* ---- New paycheck initiation (C28) --------------------------------- */
.mm-new-paycheck {
  width: 100%;
  max-width: 36rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

.mm-new-paycheck-head {
  display: grid;
  gap: var(--mm-space-2);
}

.mm-new-paycheck-prefill {
  display: grid;
  gap: var(--mm-space-2);
  background-color: hsl(var(--mm-accent) / 0.08);
  border-color: hsl(var(--mm-accent) / 0.35);
}

.mm-new-paycheck-prefill-label {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-subtle));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-2);
}

.mm-new-paycheck-prefill-name {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-new-paycheck-noprefill {
  border-style: dashed;
}

.mm-new-paycheck-form-card {
  display: grid;
  gap: var(--mm-space-4);
}

@media (max-width: 560px) {
  .mm-income-sources-head .mm-btn,
  .mm-income-source-action,
  .mm-income-source-action form,
  .mm-income-source-action .mm-btn {
    width: 100%;
  }
}

/* ---- Utilities ----------------------------------------------------- */
/* Muted inline text — secondary text tier (de-emphasised labels, placeholders). */
.mm-text-muted {
  color: hsl(var(--mm-text-muted));
}

/* Subtle inline text — tertiary tier (stat-card hints, chart captions).
   Lighter than .mm-text-muted in both themes; AA-safe on all surfaces. */
.mm-text-subtle {
  color: hsl(var(--mm-text-subtle));
}

/* Screen-reader-only — visually hides an element while keeping it in the
   accessibility tree (e.g. column headers that duplicate visible label text). */
.mm-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- Finalization review screen (C33) ------------------------------ */
/* The read-only pre-finalize gate. Blocking errors (must-fix) and non-blocking
   warnings (surplus/overdraw) are given categorically distinct treatment —
   danger vs warning tokens — so the two can never be confused. Data-dense but
   breathable; one accent, tinted feedback surfaces, soft thin-bordered cards. */
.mm-review {
  width: 100%;
  max-width: 52rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

.mm-review-head {
  display: grid;
  gap: var(--mm-space-2);
}

.mm-review-title {
  font-size: var(--mm-text-2xl);
  margin: 0;
}

/* ---- Verdict banner — the primary read (ready vs blocked) ---------- */
.mm-review-verdict {
  display: flex;
  align-items: flex-start;
  gap: var(--mm-space-4);
  padding: var(--mm-space-4) var(--mm-space-5);
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-lg);
  /* One calm entrance — transform + opacity only; the global reduced-motion
     guard disables it. A money surface stays crisp, not showy. */
  animation: mm-fade-in var(--mm-duration-base) var(--mm-ease-out) both;
}

.mm-review-verdict--ready {
  border-color: hsl(var(--mm-success) / 0.4);
  background-color: hsl(var(--mm-success) / 0.08);
}

.mm-review-verdict--blocked {
  border-color: hsl(var(--mm-danger) / 0.45);
  background-color: hsl(var(--mm-danger) / 0.08);
}

.mm-review-verdict-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border-radius: var(--mm-radius-full);
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-bold);
  line-height: 1;
  color: hsl(var(--mm-accent-foreground));
}

.mm-review-verdict--ready .mm-review-verdict-mark {
  background-color: hsl(var(--mm-success));
}

.mm-review-verdict--blocked .mm-review-verdict-mark {
  background-color: hsl(var(--mm-danger));
}

.mm-review-verdict-body {
  display: grid;
  gap: 0.125rem;
}

.mm-review-verdict-title {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
}

.mm-review-verdict-sub {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* ---- Error / warning panels — categorically distinct surfaces ------ */
.mm-review-panel {
  display: grid;
  gap: var(--mm-space-3);
  padding: var(--mm-space-5);
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-lg);
}

.mm-review-panel--error {
  border-color: hsl(var(--mm-danger) / 0.4);
  background-color: hsl(var(--mm-danger) / 0.06);
}

.mm-review-panel--warning {
  border-color: hsl(var(--mm-warning) / 0.4);
  background-color: hsl(var(--mm-warning) / 0.08);
}

.mm-review-panel-title {
  margin: 0;
  font-size: var(--mm-text-lg);
}

.mm-review-panel--error .mm-review-panel-title {
  color: hsl(var(--mm-danger));
}

.mm-review-panel-note {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-review-issues,
.mm-review-warnings {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-2);
}

.mm-review-issue {
  position: relative;
  padding-left: var(--mm-space-5);
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
}

/* Leading marker instead of a default disc — a small danger dot carries the
   must-fix meaning without a loud fill on every row. */
.mm-review-issue::before {
  content: "";
  position: absolute;
  left: var(--mm-space-2);
  top: 0.5em;
  width: 0.4rem;
  height: 0.4rem;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-danger));
}

.mm-review-warning {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--mm-space-2) var(--mm-space-3);
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
}

.mm-review-warning-amount {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--mm-text));
}

.mm-review-warning-text {
  flex: 1 1 16rem;
  color: hsl(var(--mm-text-muted));
}

/* ---- Section headings --------------------------------------------- */
.mm-review-section-title {
  font-size: var(--mm-text-lg);
  margin: 0 0 var(--mm-space-3);
}

/* ---- Totals -------------------------------------------------------- */
.mm-review-totals {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-review-totals-grid {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--mm-space-4);
}

/* Same stretched-auto-rows drift as the `*-meta div` pairs: the totals grid is
   `auto-fit`, so every cell is stretched to the tallest and the surplus height
   lands between the label and its figure rather than under the pair. */
.mm-review-total {
  display: grid;
  align-content: start;
  gap: 0.25rem;
}

.mm-review-total dt {
  font-size: var(--mm-text-xs);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
}

.mm-review-total-value {
  margin: 0;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-lg);
  font-weight: var(--mm-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--mm-text));
}

.mm-review-total--surplus .mm-review-total-value {
  font-weight: var(--mm-weight-bold);
  color: hsl(var(--mm-success));
}

.mm-review-total--overdraw .mm-review-total-value {
  color: hsl(var(--mm-danger));
}

.mm-review-hint {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-subtle));
}

/* ---- Per-line detail ---------------------------------------------- */
.mm-review-line-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-3);
}

.mm-review-line {
  display: grid;
  gap: var(--mm-space-3);
  padding: var(--mm-space-5);
}

/* A blocked line gets a subtle danger edge so it reads as "this one needs work"
   at a glance, echoing the current-session accent-edge pattern. */
.mm-review-line--blocked {
  border-color: hsl(var(--mm-danger) / 0.4);
  box-shadow: inset 3px 0 0 0 hsl(var(--mm-danger));
}

.mm-review-line-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-3);
}

.mm-review-line-name {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
  word-break: break-word;
}

.mm-review-line-meta {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: var(--mm-space-2) var(--mm-space-4);
}

/* Same stretched-auto-rows drift as `.mm-invite-meta div` above — and this one
   carries three money figures per line, so a wrapped neighbour floats each value
   away from the word that names it. */
.mm-review-line-meta div {
  display: grid;
  align-content: start;
  gap: 0.125rem;
}

.mm-review-line-meta dt {
  font-size: var(--mm-text-xs);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
}

.mm-review-line-meta dd {
  margin: 0;
  font-size: var(--mm-text-sm);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--mm-text));
}

.mm-review-line-notes {
  margin: 0;
  max-width: none;
  padding: var(--mm-space-3) var(--mm-space-4);
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  background-color: hsl(var(--mm-surface));
  border-radius: var(--mm-radius-md);
  word-break: break-word;
}

.mm-review-line-notes-label {
  display: inline;
  margin-right: var(--mm-space-2);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-semibold);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
}

.mm-review-line-flag {
  display: grid;
  gap: var(--mm-space-2);
  padding: var(--mm-space-3) var(--mm-space-4);
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-danger) / 0.06);
  border: var(--mm-border-width) solid hsl(var(--mm-danger) / 0.3);
}

.mm-review-line-flag-label {
  margin: 0;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-semibold);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-danger));
}

.mm-review-line-issues {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-2);
}

.mm-review-empty {
  display: grid;
  gap: var(--mm-space-2);
  text-align: center;
}

.mm-review-empty-title {
  margin: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}


/* ---- Edit finalized paycheck (C35) --------------------------------- */
.mm-edit-paycheck {
  width: 100%;
  max-width: 44rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

.mm-edit-paycheck-head {
  display: grid;
  gap: var(--mm-space-2);
}

/* Money figures — ALWAYS high-contrast ink (never a tinted state colour), so text on
   colour never dips under WCAG AA 4.5:1 in either theme. Tabular numerals keep columns
   aligned. State (surplus/overdraw) is carried by an adjacent badge label, not by tinting
   the number. */
.mm-figure {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--mm-text));
}

.mm-figure--sm {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
}

.mm-edit-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-6);
  align-items: center;
}

.mm-edit-summary-item {
  display: grid;
  gap: var(--mm-space-2);
  /* The flex ITEM is where the floor is set, not the text inside it. Without this
     a long unbroken label sets this item's min-content width, the item refuses to
     shrink past it, and the whole row widens the page — wrapping the label alone
     would not help, because `overflow-wrap` does not shrink min-content. */
  min-width: 0;
}

.mm-edit-summary-label {
  margin: 0;
  font-size: var(--mm-text-xs);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
  /* This cell held only `{% translate %}` literals until the destination history
     put a field's own label in it (`{{ change.label }}`, read off the model's
     verbose_name so the words follow the reader's language). A variable here is
     of unpredictable length, which is what `health/tests/test_hostile_input.py`
     derives from the templates and demands a break for.

     `break-word`, not `anywhere`: these are multi-word phrases that already wrap
     at their spaces, and `anywhere` on a narrow grid track renders one character
     per line. `min-width: 0` above is what lets this take effect. */
  overflow-wrap: break-word;
}

.mm-edit-summary-item dd {
  margin: 0;
  font-size: var(--mm-text-lg);
}

.mm-edit-form {
  gap: var(--mm-space-5);
}

/* Reset native fieldset chrome — the design language uses tokens, not the UA border. */
.mm-edit-lines,
.mm-edit-line {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0; /* allow flex/grid children to shrink inside a fieldset */
}

.mm-edit-lines {
  display: grid;
  gap: var(--mm-space-3);
}

.mm-edit-lines-legend,
.mm-edit-history-title {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-lg);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  padding: 0;
  margin: 0 0 var(--mm-space-2);
}

.mm-edit-line {
  display: grid;
  gap: var(--mm-space-3);
  padding: var(--mm-space-4);
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-lg);
  box-shadow: var(--mm-shadow-sm);
}

/* Invalid line — a restrained danger edge, matching the current-session accent-edge idiom. */
.mm-edit-line--invalid {
  border-color: hsl(var(--mm-danger) / 0.5);
  box-shadow: inset 3px 0 0 0 hsl(var(--mm-danger));
}

.mm-edit-line-label {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  padding: 0;
}

.mm-edit-line-planned {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--mm-space-2);
  font-size: var(--mm-text-xs);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
}

/* Status + actual side by side on wide screens; the line note spans the full width below. */
.mm-edit-line-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--mm-space-4);
}

.mm-edit-line-note {
  grid-column: 1 / -1;
}

/* Per-line field grid supplies its own gap; drop the mm-form scope's row gap here. */
.mm-edit-line .mm-field {
  gap: var(--mm-space-2);
}

.mm-edit-history {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-edit-history-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-4);
}

.mm-edit-history-item {
  display: grid;
  gap: var(--mm-space-2);
  padding-bottom: var(--mm-space-4);
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-edit-history-item:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.mm-edit-history-meta {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-3);
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-edit-history-actor {
  color: hsl(var(--mm-text-subtle));
}

.mm-edit-history-note {
  margin: 0;
  color: hsl(var(--mm-text));
  max-width: none;
}

/* ---- Reduced motion (mandatory) ----------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .mm-btn:active {
    transform: none;
  }
}

/* ---- Buttons: small variant (line card actions) -------------------
   The one place this variant is declared. It was declared twice — here and again
   under "Small size modifiers" beside `.mm-input--sm` — at the same specificity,
   so the later block's `font-size` won and the `var(--mm-text-sm)` written here
   had never applied. Editing this block to change small-button type did nothing
   at all, which is worse than the duplication. The shipped value is kept: `xs`
   is what a small button has always rendered at, and it is the size
   `.mm-input--sm` pairs with. */
.mm-btn--sm {
  min-height: 2rem;
  padding: var(--mm-space-1) var(--mm-space-3);
  font-size: var(--mm-text-xs);
}

/* ---- Extra message variants (Django messages framework) ----------- */
.mm-msg--info {
  color: hsl(var(--mm-text));
  border-color: hsl(var(--mm-border-strong));
}

.mm-msg--warning {
  color: hsl(var(--mm-warning-text));
  border-color: hsl(var(--mm-warning) / 0.4);
  background-color: hsl(var(--mm-warning) / 0.08);
}

/* ---- Utility text colours ---------------------------------------- */
.mm-text-danger {
  color: hsl(var(--mm-danger));
}

.mm-text-success {
  color: hsl(var(--mm-success-text));
}

/* ---- HTMX loading indicator --------------------------------------- */
/* HTMX sets .htmx-request on the indicator element while the request
   is in flight.  We default to opacity:0 + pointer-events:none and
   transition to visible so readers receive a polite "Saving…" hint. */
.htmx-indicator {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--mm-duration-base) var(--mm-ease-out);
}

.htmx-request .htmx-indicator,
.htmx-indicator.htmx-request {
  opacity: 1;
  pointer-events: auto;
}

/* ---- Paycheck draft list [C29] ------------------------------------ */
.mm-drafts {
  width: 100%;
  max-width: 52rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

/* ---- Paycheck draft editor [C29] ---------------------------------- */
.mm-draft {
  width: 100%;
  max-width: 52rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-6);
}

.mm-draft-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-2);
}

/* Contextual headings within cards and sections */
.mm-card-heading {
  margin: 0 0 var(--mm-space-4);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-lg);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
}

.mm-section-heading {
  margin: 0 0 var(--mm-space-4);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-xl);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
}

.mm-section-subheading {
  margin: var(--mm-space-4) 0 var(--mm-space-2);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-muted));
}

.mm-draft-section {
  display: grid;
  gap: var(--mm-space-4);
}

/* Autosave status indicator */
.mm-autosave-status {
  display: flex;
  align-items: center;
  gap: var(--mm-space-2);
  font-size: var(--mm-text-sm);
  min-height: 1.5rem;
}

.mm-autosave-idle {
  color: hsl(var(--mm-text-subtle));
}

.mm-autosave-saved {
  color: hsl(var(--mm-success-text));
}

.mm-autosave-error {
  color: hsl(var(--mm-danger));
}

.mm-autosave-saving {
  color: hsl(var(--mm-text-muted));
  font-style: italic;
}

/* ---- Allocation lines list --------------------------------------- */
.mm-lines-list {
  display: grid;
  gap: var(--mm-space-3);
}

/* Add-line card — slightly less visual weight than the editor card */
/* Not dashed. A dashed border is the drop-zone / placeholder idiom — "something
   goes here" — and this is a real form with real fields in it. On the Allocate
   stage it read as an empty box at the bottom of the page rather than the way to
   add a line, which is the one thing that stage is for. */
.mm-add-line-card {
  border-style: solid;
}

/* ---- Individual line card --------------------------------------- */
.mm-line-card {
  display: grid;
  gap: var(--mm-space-3);
  padding: var(--mm-space-4);
}

.mm-line-form {
  gap: var(--mm-space-3);
}

/* Inline field grid — compact row layout for line editing */
.mm-line-fields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-3);
}

.mm-field--inline {
  flex: 1 1 9rem;
  min-width: 0;
}

.mm-field--wide {
  flex: 2 1 18rem;
}

/* Explicit widget classes used in hand-rolled line fields */
.mm-input {
  width: 100%;
  min-height: 2.75rem;
  padding: var(--mm-space-2) var(--mm-space-3);
  font-family: var(--mm-font-sans);
  font-size: var(--mm-text-base);
  color: hsl(var(--mm-text));
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  border-radius: var(--mm-radius-md);
  transition: border-color var(--mm-duration-fast) var(--mm-ease-out),
    box-shadow var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-input::placeholder {
  color: hsl(var(--mm-text-subtle));
}

.mm-input:focus-visible {
  outline: none;
  border-color: hsl(var(--mm-accent));
  box-shadow: 0 0 0 3px hsl(var(--mm-ring) / 0.28);
}

.mm-field--invalid .mm-input {
  border-color: hsl(var(--mm-danger));
}

/* A box nothing reads — the column a destination's `claim_kind` does not select
   (docs/design/priority-tiers.md, *One row, one claim*). It stays editable, because the
   dropdown that decides which of the pair is live sits directly above it and both have
   to be changeable in one submission; what changes is that it stops looking like a
   working control. Dashed rather than solid, and the figure muted: a member scanning the
   card sees which of the two figures the paycheck is going to read without reading a
   word.

   Not `disabled` and not `readonly`, and that is the decision. Either would mean the
   share could only be typed *after* the kind had been saved as a percentage, and in
   between the row would be a percentage claim asking for 0% — a destination that
   silently stops being funded, which is a worse outcome than the one this styling is
   here to prevent. The sentence under the box says it is not read; the form refuses a
   figure typed into it anyway (`DestinationEditForm._refuse_an_inert_edit`).

   Focus is deliberately untouched: it keeps `.mm-input:focus-visible`'s ring, because a
   box a keyboard can still reach must still show where the keyboard is.

   TWO SELECTORS, AND THE SECOND ONE IS THE WHOLE REASON THIS WORKS. Every widget the
   shared field renderer emits is inside `.mm-form`, which styles it through
   `.mm-form input:not([type=checkbox]):not([type=radio])` — specificity (0,3,1), because
   each `:not()` counts its argument. A bare `.mm-input--inert` is (0,1,0) and loses to
   it, so the class was in the markup and changed nothing on screen: shot under the
   production CSP, the box the page had just called "not in play" was indistinguishable
   from the one beside it that a paycheck actually reads.

   So the input form matches the base rule's specificity exactly and wins on source
   order, which is the pattern `.mm-field--invalid input:not(…):not(…)` above already
   uses for the same collision. The bare class stays for anything that is not an input. */
.mm-input--inert,
input.mm-input--inert:not([type=checkbox]):not([type=radio]) {
  color: hsl(var(--mm-text-muted));
  background-color: hsl(var(--mm-surface));
  /* The weaker border token as well as the dash. On dark, `--mm-surface` and
     `--mm-surface-raised` are close enough that the recessed fill alone barely reads —
     shot in both themes — and the box has to look inert in the one a member is using. */
  border-color: hsl(var(--mm-border));
  border-style: dashed;
}

.mm-select {
  width: 100%;
  min-height: 2.75rem;
  padding: var(--mm-space-2) var(--mm-space-3);
  font-family: var(--mm-font-sans);
  font-size: var(--mm-text-base);
  color: hsl(var(--mm-text));
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  border-radius: var(--mm-radius-md);
  cursor: pointer;
  transition: border-color var(--mm-duration-fast) var(--mm-ease-out),
    box-shadow var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-select:focus-visible {
  outline: none;
  border-color: hsl(var(--mm-accent));
  box-shadow: 0 0 0 3px hsl(var(--mm-ring) / 0.28);
}

.mm-field--invalid .mm-select {
  border-color: hsl(var(--mm-danger));
}

.mm-line-actions {
  display: flex;
  gap: var(--mm-space-2);
}

.mm-line-delete-form {
  display: flex;
  align-items: center;
}

/* Read-only derived values row */
.mm-line-derived {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-4);
  padding-top: var(--mm-space-2);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
  font-size: var(--mm-text-sm);
}

.mm-line-derived-item {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.mm-line-derived-item .mm-label {
  font-size: var(--mm-text-xs);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-subtle));
}

/* ---- Allocation preview panel [C29] ------------------------------- */
.mm-allocation-preview {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-allocation-preview--error {
  border-color: hsl(var(--mm-danger) / 0.4);
}

/* Generic error text (not in a field context) */
.mm-error {
  color: hsl(var(--mm-danger));
  font-size: var(--mm-text-sm);
  margin: 0;
}

.mm-alloc-summary {
  background-color: hsl(var(--mm-surface));
  border-radius: var(--mm-radius-md);
  padding: var(--mm-space-3);
}

.mm-alloc-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-2);
}

.mm-alloc-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-2);
  padding: var(--mm-space-2) var(--mm-space-3);
  background-color: hsl(var(--mm-surface));
  border-radius: var(--mm-radius-md);
  font-size: var(--mm-text-sm);
}

.mm-alloc-label {
  flex: 1 1 auto;
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-alloc-kind {
  flex-shrink: 0;
}

.mm-alloc-pct {
  flex-shrink: 0;
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text-muted));
  min-width: 3rem;
  text-align: right;
}

.mm-alloc-amount {
  flex-shrink: 0;
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  min-width: 5rem;
  text-align: right;
}

/* ---- C30 — Catch-all line card ------------------------------------ */

/* Catch-all lines get a subtle left accent so they're distinguishable
   without a heavy visual treatment (restraint — one accent per product). */
.mm-line-card--catchall {
  border-left: 3px solid hsl(var(--mm-accent) / 0.5);
}

.mm-catchall-header {
  display: flex;
  align-items: center;
  gap: var(--mm-space-3);
  margin-bottom: var(--mm-space-3);
  padding-bottom: var(--mm-space-3);
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-catchall-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2em 0.65em;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-semibold);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-accent-text));
  background-color: hsl(var(--mm-accent) / 0.1);
  border: var(--mm-border-width) solid hsl(var(--mm-accent) / 0.25);
  border-radius: var(--mm-radius-full);
}

.mm-catchall-hint {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* Read-only computed amount display inside the catch-all line card */
.mm-catchall-amount {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: var(--mm-space-2) var(--mm-space-3);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-base);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  background-color: hsl(var(--mm-surface));
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  width: 100%;
}

/* ---- C30 — Add catch-all button area ------------------------------ */
.mm-catchall-add {
  margin-top: var(--mm-space-5);
  padding-top: var(--mm-space-4);
  border-top: var(--mm-border-width) dashed hsl(var(--mm-border));
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-2);
}

.mm-catchall-hint-outer {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-subtle));
  max-width: 56ch;
}

/* ---- C30 — Obligations quick-add panel ---------------------------- */
.mm-obligations-panel {
  margin-top: var(--mm-space-5);
}

.mm-obligations-hint {
  margin-bottom: var(--mm-space-4);
  font-size: var(--mm-text-sm);
}

.mm-obligations-list {
  list-style: none;
  margin: 0 0 var(--mm-space-4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-2);
}

.mm-obligations-item {
  display: block;
}

.mm-obligations-label {
  display: flex;
  align-items: center;
  gap: var(--mm-space-3);
  /* Wraps rather than crushing the name. This row is a checkbox, a label, a
     badge and a number input, and only the label was allowed to shrink — so as
     the card narrowed, the label absorbed every pixel the others would not give
     up. At the width this panel actually gets, "Credit Card" was squeezed to 9px
     and rendered one letter per line, vertically, in production.
     Letting the row wrap moves the input to its own line instead. */
  flex-wrap: wrap;
  row-gap: var(--mm-space-2);
  padding: var(--mm-space-2) var(--mm-space-3);
  border-radius: var(--mm-radius-md);
  cursor: pointer;
  transition: background-color var(--mm-duration-fast) var(--mm-ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .mm-obligations-label:hover {
    background-color: hsl(var(--mm-surface));
  }
}

.mm-obligations-checkbox {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  accent-color: hsl(var(--mm-accent));
  cursor: pointer;
}

.mm-obligations-name {
  /* Basis 10rem, floor 8rem: `flex: 1` alone means basis 0, which lets this be
     sized down to whatever is left over after the fixed-width siblings — which
     is how it reached 9px. It still grows to fill a wide row; it just refuses to
     become unreadable in a narrow one, and the row wraps instead. */
  flex: 1 1 10rem;
  min-width: 8rem;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-obligations-amount {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text-muted));
  min-width: 5rem;
  text-align: right;
}

.mm-obligations-actions {
  padding-top: var(--mm-space-3);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
}

/* ---- C31 — Bulk completion panel (draft editor) ------------------- */
.mm-bulk-complete-panel {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-bulk-complete-hint {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-bulk-complete-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--mm-space-2);
}

.mm-bulk-complete-item {
  display: block;
}

.mm-bulk-complete-label {
  display: flex;
  align-items: center;
  gap: var(--mm-space-3);
  padding: var(--mm-space-2) var(--mm-space-3);
  border-radius: var(--mm-radius-md);
  cursor: pointer;
  transition: background-color var(--mm-duration-fast) var(--mm-ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .mm-bulk-complete-label:hover {
    background-color: hsl(var(--mm-surface));
  }
}

.mm-bulk-complete-checkbox {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  accent-color: hsl(var(--mm-accent));
  cursor: pointer;
}

.mm-bulk-complete-name {
  flex: 1;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-bulk-complete-amount {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text-muted));
  min-width: 5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ---- C31 — Bulk completion review screen -------------------------- */
.mm-bulk-review {
  width: 100%;
  max-width: 52rem;
  margin-inline: auto;
  padding-block: var(--mm-space-6);
  display: grid;
  gap: var(--mm-space-5);
}

.mm-bulk-review-head {
  display: grid;
  gap: var(--mm-space-2);
}

.mm-bulk-review-title {
  font-size: var(--mm-text-2xl);
  margin: 0;
}

.mm-bulk-review-section-title {
  font-size: var(--mm-text-lg);
  margin: 0 0 var(--mm-space-3);
}

.mm-bulk-review-panel {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-bulk-review-line-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-3);
}

.mm-bulk-review-line {
  display: grid;
  gap: var(--mm-space-3);
  padding: var(--mm-space-4);
}

.mm-bulk-review-line-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-3);
}

.mm-bulk-review-actions {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-bulk-review-confirm-note {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* ---- Destinations (C40) ------------------------------------------- */
.mm-destinations {
  display: grid;
  gap: var(--mm-space-8);
}

.mm-destinations-head {
  display: grid;
  gap: var(--mm-space-3);
}

.mm-destinations-title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-4);
}

.mm-dest-group {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-dest-group-heading {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--mm-tracking-wide);
  color: hsl(var(--mm-text-subtle));
  margin: 0;
}

.mm-dest-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--mm-space-3);
}

.mm-dest-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-4);
  padding: var(--mm-space-4) var(--mm-space-5);
}

.mm-dest-item--inactive {
  opacity: 0.65;
}

.mm-dest-item-info {
  display: grid;
  gap: var(--mm-space-2);
  min-width: 0;
}

.mm-dest-item-name-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-2);
  /* A grid item of `.mm-dest-item-info`, which already carries `min-width: 0` —
     but that only frees the parent. Without this the row's own min-content (the
     longest word in a destination name) is the floor, and a 54-character name
     widened the whole document past the viewport at 375px. */
  min-width: 0;
}

.mm-dest-item-name {
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
  /* This name is the row's only link. Overriding the base `a` colour took the one
     always-on signal that said so, leaving `a:hover`'s underline as the whole
     affordance — and hover does not exist on touch, so the card read as a dead
     end. Hence a permanent underline rather than a hover state: it survives
     no-hover input, and unlike colour it does not depend on the reader
     distinguishing accent red from body black. The name keeps body colour so a
     list of ten rows does not render as ten red headlines. */
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  /* The wrap itself is declared once, in the user-text bulk rule at the end of
     this file, which names this class and sets `break-word`. This block asked for
     `anywhere` and had done nothing since: same specificity, three thousand lines
     earlier, so `break-word` won and the two comments sat in one file requiring
     opposite things.

     `break-word` is the right value — `anywhere` shrinks an element's min-content
     to one character, which is how a "Credit Card" label shipped rendering
     vertically — and it does mean the wrap is not what stops a 118-character name
     widening the document. `min-width` is: an explicit floor replaces the
     automatic minimum size, so this element's intrinsic width never reaches the
     grid above it. Pinned by destinations/tests/test_destination_list_surface.py,
     which reads the effective value rather than one declaration. */
  min-width: 6rem;
}

.mm-dest-item-name:hover,
.mm-dest-item-name:focus-visible {
  color: hsl(var(--mm-accent));
}

.mm-dest-item-desc,
.mm-dest-item-portfolio {
  margin: 0;
  font-size: var(--mm-text-sm);
  /* This paragraph now carries the drift verdict's detail, which interpolates a
     portfolio name and a ticker — both member-supplied and both unbounded. Same
     story as `.mm-dest-item-name` above: the `anywhere` that stood here was
     overridden by the bulk rule's `break-word` and never applied, and it is the
     `min-width` floor that keeps the intrinsic width off `.mm-dest-item-info`'s
     grid. Both classes are named in the bulk rule now, so the wrap does not
     depend on `mm-text-muted` happening to be on the same element. */
  min-width: 6rem;
}

/* The one action a flagged row offers. Underlined at rest rather than on hover
   only: it sits inside a block of muted caption text, where an accent-coloured
   word with no other affordance reads as emphasis, not as somewhere to click. */
.mm-dest-item-action {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  text-decoration: underline;
}

/* The portfolios list carries up to three lines in this column — the invested
   figure, the "invested · $X cash" caption, and a pricing note — so it has to
   stack instead of letting the spans run together inline. End-aligned: a column
   of money is read by comparing magnitudes, and left-aligned figures make $900
   and $9,000 look alike.

   A modifier, not a rule on `.mm-dest-item-balance`, because that class is bare
   and shared: templates/destinations/destination_list.html uses it for both its
   balance columns, where the cell holds a single span and has nothing to stack.
   Styling the shared class would have silently converted those two containers to
   grids as well — a change to a page this work never looked at. */
.mm-dest-item-balance--stacked {
  display: grid;
  justify-items: end;
  text-align: right;
  gap: var(--mm-space-1);
}

.mm-dest-balance-amount {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  font-size: var(--mm-text-lg);
  white-space: nowrap;
}

.mm-dest-empty {
  display: grid;
  gap: var(--mm-space-4);
  padding: var(--mm-space-8);
  text-align: center;
}

.mm-dest-empty-title {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-medium);
  font-size: var(--mm-text-lg);
  margin: 0;
}

.mm-dest-inactive-section {
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  overflow: hidden;
}

.mm-dest-inactive-summary {
  padding: var(--mm-space-4) var(--mm-space-5);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text-muted));
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.mm-dest-inactive-summary::-webkit-details-marker {
  display: none;
}

.mm-dest-list--inactive {
  padding: 0 var(--mm-space-4) var(--mm-space-4);
}

/* Destination create */
.mm-dest-create {
  display: grid;
  gap: var(--mm-space-8);
}

.mm-dest-form-card {
  max-width: 36rem;
}

.mm-dest-portfolio-hint {
  font-size: var(--mm-text-sm);
  margin-bottom: var(--mm-space-4);
}

/* Destination detail */
.mm-dest-detail {
  display: grid;
  gap: var(--mm-space-6);
}

.mm-dest-detail-head {
  display: grid;
  gap: var(--mm-space-3);
}

.mm-dest-detail-title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-3);
}

.mm-back-link {
  font-size: var(--mm-text-sm);
  margin-right: var(--mm-space-2);
}

.mm-dest-detail-grid {
  display: grid;
  gap: var(--mm-space-4);
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
}

.mm-dest-section-heading {
  font-size: var(--mm-text-base);
  font-weight: var(--mm-weight-medium);
  margin: 0 0 var(--mm-space-4);
}

.mm-dest-balance-figure {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-bold);
  font-size: var(--mm-text-3xl);
  margin: 0 0 var(--mm-space-2);
}

.mm-dest-balance-updated {
  font-size: var(--mm-text-sm);
  margin: 0;
}

.mm-dest-semantics {
  display: grid;
  gap: var(--mm-space-4);
  margin: 0;
}

.mm-dest-semantics > div {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-3);
}

.mm-dest-semantics dt {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  min-width: 10rem;
}

.mm-dest-semantics dd {
  margin: 0;
}

.mm-dest-edit-card {
  max-width: 36rem;
}

.mm-dest-actions {
  display: flex;
  gap: var(--mm-space-3);
}

/* Badge type modifier */
.mm-badge--type {
  background-color: hsl(var(--mm-surface-raised));
  color: hsl(var(--mm-text-subtle));
}


/* ---- Goals (C41) -------------------------------------------------- */
.mm-goal-card {
  display: grid;
  gap: var(--mm-space-5);
}

.mm-goal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--mm-space-4);
}

.mm-goal-name {
  font-size: var(--mm-text-xl);
  margin: var(--mm-space-1) 0 0;
}

.mm-goal-target {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-semibold);
  font-size: var(--mm-text-lg);
}

.mm-goal-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-5);
  margin: 0;
  padding: 0;
}

/* Same drift as the `*-meta div` grids elsewhere: a wrapping flex row stretches
   every item to the tallest, and a stretched auto-row grid spends the surplus
   height between its rows rather than below them. */
.mm-goal-meta div {
  display: grid;
  align-content: start;
  gap: var(--mm-space-1);
}

.mm-goal-meta dt {
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--mm-tracking-wide);
  color: hsl(var(--mm-text-muted));
}

.mm-goal-meta dd {
  margin: 0;
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

/* Progress bar */
.mm-goal-progress-section {
  display: grid;
  gap: var(--mm-space-2);
}

.mm-goal-progress-labels {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.mm-goal-progress-label {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text-muted));
}

.mm-goal-progress-pct {
  font-family: var(--mm-font-display);
  font-weight: var(--mm-weight-bold);
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
}

.mm-goal-progress-bar {
  width: 100%;
  height: 0.5rem;
  background-color: hsl(var(--mm-border));
  border-radius: var(--mm-radius-full);
  overflow: hidden;
}

.mm-goal-progress-fill {
  height: 100%;
  background-color: hsl(var(--mm-accent));
  border-radius: var(--mm-radius-full);
  transition: width var(--mm-duration-base) var(--mm-ease-out);
  min-width: 0.25rem;
}

.mm-goal-progress-fill--reached {
  background-color: hsl(var(--mm-success));
}

.mm-goal-reached {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-success));
  margin: 0;
}

.mm-goal-remaining {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  margin: 0;
}

/* Pace */
.mm-goal-pace-section {
  display: grid;
  gap: var(--mm-space-2);
  padding-top: var(--mm-space-4);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-goal-pace-amount {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-2xl);
  font-weight: var(--mm-weight-bold);
  color: hsl(var(--mm-text));
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--mm-space-2);
}

.mm-goal-pace-label {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-normal);
  color: hsl(var(--mm-text-muted));
}

/* Projection */
.mm-goal-projection-section {
  display: grid;
  gap: var(--mm-space-2);
  padding-top: var(--mm-space-4);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-goal-projection-date {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-xl);
  font-weight: var(--mm-weight-bold);
  color: hsl(var(--mm-text));
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--mm-space-3);
  flex-wrap: wrap;
}

.mm-goal-projection-date--behind {
  color: hsl(var(--mm-warning));
}

.mm-goal-empty-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-text-sm {
  font-size: var(--mm-text-sm);
}

/* ============================================================
   Reconciliation (C42)
   ============================================================ */

.mm-reconcile-section {
  display: grid;
  gap: var(--mm-space-6);
  max-width: 48rem;
  margin: 0 auto;
  padding: var(--mm-space-6) var(--mm-space-4);
}

.mm-reconcile-balance-card {
  display: grid;
  gap: var(--mm-space-2);
}

.mm-reconcile-ledger-balance {
  font-size: var(--mm-text-2xl);
  font-weight: 600;
  color: hsl(var(--mm-text));
  margin: 0;
}

.mm-reconcile-form-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-reconcile-history-link-card {
  text-align: center;
}

/* Reconciliation history list */

.mm-reconcile-list-section {
  display: grid;
  gap: var(--mm-space-6);
  max-width: 64rem;
  margin: 0 auto;
  padding: var(--mm-space-6) var(--mm-space-4);
}

.mm-reconcile-list-actions {
  display: flex;
  gap: var(--mm-space-3);
}

.mm-reconcile-history-card {
  overflow-x: auto;
}

.mm-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--mm-text-sm);
}

.mm-table th,
.mm-table td {
  padding: var(--mm-space-3) var(--mm-space-4);
  text-align: left;
  border-bottom: 1px solid hsl(var(--mm-border));
}

.mm-table th {
  font-weight: 600;
  color: hsl(var(--mm-text-muted));
  background: hsl(var(--mm-surface-raised));
}

.mm-reconcile-delta {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.mm-reconcile-delta--positive {
  color: hsl(var(--mm-success-text));
}

.mm-reconcile-delta--negative {
  color: hsl(var(--mm-warning-text));
}

.mm-reconcile-empty-card {
  display: grid;
  gap: var(--mm-space-4);
}

/* Reconciliation panel on destination detail */

.mm-reconcile-panel {
  display: grid;
  gap: var(--mm-space-4);
}

/* ============================================================
   Holdings manual edit + audit (C57)
   ============================================================ */

/* Section wrappers — mirror .mm-reconcile-section layout */
.mm-holding-edit-section,
.mm-holding-history-section,
.mm-cash-edit-section,
.mm-cash-history-section {
  display: grid;
  gap: var(--mm-space-6);
  max-width: 48rem;
  margin: 0 auto;
  padding: var(--mm-space-6) var(--mm-space-4);
}

/* Current-value cards — matching .mm-reconcile-balance-card */
.mm-holding-edit-current,
.mm-holding-history-current,
.mm-cash-edit-current,
.mm-cash-history-current {
  display: grid;
  gap: var(--mm-space-2);
}

/* Form cards — matching .mm-reconcile-form-card */
.mm-holding-edit-form-card,
.mm-cash-edit-form-card {
  display: grid;
  gap: var(--mm-space-4);
}

/* History preview / list cards */
.mm-holding-edit-history-preview,
.mm-cash-edit-history-preview,
.mm-holding-history-list-card,
.mm-cash-history-list-card {
  display: grid;
  gap: var(--mm-space-4);
}

/* Compact edit-summary inside diff disclosure — tighter than the default */
.mm-edit-summary--compact {
  gap: var(--mm-space-3);
}

.mm-edit-summary--compact .mm-edit-summary-item dd {
  font-size: var(--mm-text-base);
}

/* Diff disclosure within an edit-history item */
.mm-edit-history-diff {
  display: grid;
  gap: var(--mm-space-3);
  margin-top: var(--mm-space-2);
}

.mm-edit-history-diff summary {
  cursor: pointer;
  list-style: none;
}

.mm-edit-history-diff summary::-webkit-details-marker {
  display: none;
}

/* ---- Portfolios (C58) --------------------------------------------- */
.mm-portfolios {
  display: grid;
  gap: var(--mm-space-8);
}

/* ============================================================
   Balance-run checklist + trade-action update (C55)
   ============================================================ */

.mm-checklist-section,
.mm-trade-update-section {
  display: grid;
  gap: var(--mm-space-6);
  max-width: 72rem;
  margin: 0 auto;
  padding: var(--mm-space-6) var(--mm-space-4);
}

.mm-checklist-header {
  display: grid;
  gap: var(--mm-space-2);
}

.mm-checklist-header-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-3);
}

.mm-checklist-header-badges {
  display: flex;
  gap: var(--mm-space-2);
}

.mm-checklist-quality-warnings {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--mm-space-2);
}

.mm-checklist-progress {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-checklist-progress-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-6);
}

.mm-checklist-stat {
  display: flex;
  align-items: baseline;
  gap: var(--mm-space-2);
}

.mm-checklist-done-msg {
  color: hsl(var(--mm-success-text));
  font-weight: 600;
}

/* "Nothing left to place" over a portfolio that is still off target: the same
   card, deliberately not the same green. */
.mm-checklist-open-msg {
  color: hsl(var(--mm-warning-text));
  font-weight: 600;
}

.mm-checklist-open-detail {
  margin: var(--mm-space-2) 0 0;
  padding-left: var(--mm-space-5);
  display: grid;
  gap: var(--mm-space-1);
  color: hsl(var(--mm-text-muted));
}

.mm-checklist-table-card {
  overflow-x: auto;
}

.mm-checklist-table {
  min-width: 56rem;
}

/* Status-coloured row tints */
.mm-checklist-row--completed td:first-child {
  border-left: 3px solid hsl(var(--mm-success-text));
}

.mm-checklist-row--adjusted td:first-child {
  border-left: 3px solid hsl(var(--mm-accent));
}

/* Skipped rows: tint the background but keep status badge and Edit button at
   full opacity. Only mute numeric/text data cells (td.mm-figure and
   td.mm-text-muted); bare td cells that hold badges or interactive controls are
   left untouched so state and affordance remain legible above AA 4.5:1. */
.mm-checklist-row--skipped {
  background-color: hsl(var(--mm-surface) / 0.5);
}

.mm-checklist-row--skipped td.mm-figure,
.mm-checklist-row--skipped td.mm-text-muted {
  opacity: 0.55;
}

.mm-checklist-run-info {
  display: grid;
  gap: var(--mm-space-4);
}

/* Trade-action update form */
.mm-trade-update-planned {
  display: grid;
  gap: var(--mm-space-3);
}

.mm-trade-update-form-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-form-fieldset {
  border: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--mm-space-3);
}

.mm-form-radio-row {
  display: flex;
  align-items: center;
  gap: var(--mm-space-3);
}

.mm-radio-label {
  cursor: pointer;
  font-size: var(--mm-text-sm);
}

.mm-form-help {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  margin: 0;
}

/* Portfolio detail — balance runs list (C55 navigation) */
.mm-portfolio-runs-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-portfolio-runs-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--mm-space-2);
}

.mm-portfolio-run-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-4);
  padding: var(--mm-space-3) 0;
  border-bottom: 1px solid hsl(var(--mm-border));
}

.mm-portfolio-run-item:last-child {
  border-bottom: none;
}

.mm-portfolio-run-info {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-3);
}

/* ============================================================
   Dashboards — finalized-only lenses (C60 pt.1)
   Shell + server-rendered inline-SVG charts. Geometry lives in
   SVG attributes (CSP-safe under style-src 'self' 'nonce'); colour
   comes from the design tokens only. Both light + premium-dark.
   ============================================================ */

.mm-dash {
  display: grid;
  gap: var(--mm-space-6);
  max-width: var(--mm-content-width);
  margin: 0 auto;
  padding: var(--mm-space-6) var(--mm-space-4);
}

.mm-dash-head {
  display: grid;
  gap: var(--mm-space-2);
}

.mm-dash-title {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-3xl);
  font-weight: var(--mm-weight-bold);
  letter-spacing: var(--mm-tracking-tight);
  line-height: var(--mm-leading-tight);
  color: hsl(var(--mm-text));
  margin: 0;
}

.mm-dash-sub {
  max-width: 52ch;
  font-size: var(--mm-text-base);
  line-height: var(--mm-leading-normal);
  margin: 0;
}

/* ---- Lens sub-nav (navigation links, not ARIA tabs) -------------- */
.mm-dash-nav {
  display: flex;
  gap: var(--mm-space-1);
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
  /* Was var(--mm-space-1). The active tab marks itself with a border-bottom on
     the tab while the strip closes with a border-bottom on the container, so any
     padding between them renders the selection as a second, detached line 4px
     above the rule. */
  padding-bottom: 0;
  /* Scroll, don't wrap — the same idiom .mm-portal-nav uses, and for a stronger
     reason here. Ten lenses never fit one row, so "Reconciliation" dropped to a
     second line and the active tab's indicator landed wherever that row put it:
     mid-component and above another tab when the active lens was on row 1, and
     doubled against the container rule when it was on the last row. The selected
     tab is the one piece of state this strip exists to communicate, and it
     rendered differently depending on which lens you were on. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}

.mm-dash-tab {
  display: inline-flex;
  align-items: center;
  /* Without this a tab shrinks to fit rather than letting the strip scroll. */
  flex: none;
  white-space: nowrap;
  /* Seats the 2px indicator on the container's rule instead of above it. */
  margin-bottom: calc(var(--mm-border-width) * -1);
  padding: var(--mm-space-2) var(--mm-space-3);
  border-radius: var(--mm-radius-sm);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text-muted));
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color var(--mm-duration-fast) var(--mm-ease-out),
    background-color var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-dash-tab:hover {
  color: hsl(var(--mm-text));
  background-color: hsl(var(--mm-surface));
}

.mm-dash-tab:focus-visible {
  outline: 2px solid hsl(var(--mm-ring));
  outline-offset: 2px;
}

.mm-dash-tab--active {
  color: hsl(var(--mm-accent-text));
  border-bottom-color: hsl(var(--mm-accent));
}

.mm-dash-body {
  display: grid;
  gap: var(--mm-space-5);
}

/* ---- Stat cards (overview) --------------------------------------- */
.mm-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--mm-space-4);
}

.mm-stat-card {
  display: grid;
  gap: var(--mm-space-1);
  text-decoration: none;
  color: inherit;
  transition: transform var(--mm-duration-fast) var(--mm-ease-out),
    box-shadow var(--mm-duration-fast) var(--mm-ease-out),
    border-color var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-stat-card:focus-visible {
  outline: 2px solid hsl(var(--mm-ring));
  outline-offset: 2px;
}

.mm-stat-label {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
}

.mm-stat-value {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-2xl);
  font-weight: var(--mm-weight-bold);
  letter-spacing: var(--mm-tracking-tight);
  color: hsl(var(--mm-text));
  font-variant-numeric: tabular-nums;
}

.mm-stat-hint {
  font-size: var(--mm-text-xs);
}

@media (hover: hover) and (pointer: fine) {
  .mm-stat-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--mm-shadow-md);
    border-color: hsl(var(--mm-border-strong));
  }
}

/* ---- Chart cards ------------------------------------------------- */
.mm-chart-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-chart-figure {
  margin: 0;
  display: grid;
  gap: var(--mm-space-2);
}

.mm-chart {
  width: 100%;
  height: auto;
  display: block;
}

.mm-chart--area {
  aspect-ratio: 640 / 200;
  overflow: visible;
}

.mm-chart-area-fill {
  fill: hsl(var(--mm-accent) / 0.12);
  stroke: none;
}

.mm-chart-area-line {
  fill: none;
  stroke: hsl(var(--mm-accent));
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.mm-chart-dot {
  fill: hsl(var(--mm-surface-raised));
  stroke: hsl(var(--mm-accent));
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

/* Matches `.mm-panel-hint`, which is the same thing one panel over: a sentence
   explaining what the panel below it shows. This was `--mm-text-xs` with no
   colour, so it inherited full-strength body ink at a smaller size — beside a
   sibling panel's muted `--mm-text-sm` hint it read as small and emphatic, which
   is the opposite of what a caption is for. */
.mm-chart-caption {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  margin: 0 0 var(--mm-space-2);
}

/* ---- Horizontal bars (planned vs actual) ------------------------- */
.mm-bar-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--mm-space-3);
}

.mm-bar-row {
  display: grid;
  grid-template-columns: 8rem 1fr auto;
  align-items: center;
  gap: var(--mm-space-3);
}

.mm-bar-label {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-bar-track {
  width: 100%;
  height: 0.75rem;
  display: block;
  background-color: hsl(var(--mm-surface));
  border-radius: var(--mm-radius-full);
  overflow: hidden;
}

.mm-bar-track--diverging {
  background-color: transparent;
  overflow: visible;
}

.mm-bar-fill {
  fill: hsl(var(--mm-accent));
}

.mm-bar-fill--planned {
  fill: hsl(var(--mm-text-muted) / 0.55);
}

.mm-bar-fill--actual {
  fill: hsl(var(--mm-accent));
}

.mm-bar-fill--pos {
  fill: hsl(var(--mm-diverge-pos));
}

.mm-bar-fill--neg {
  fill: hsl(var(--mm-diverge-neg));
}

.mm-bar-axis {
  stroke: hsl(var(--mm-border-strong));
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.mm-bar-value {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
}

/* ---- Stacked completion bar + legend ----------------------------- */
.mm-completion-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--mm-space-3);
}

/* Renamed off `.mm-chart`. The completion bar is an inline SVG, not a Chart.js
   canvas, and it was sharing that class with the canvas container — whose own
   rule ~1700 lines below sets `height: 16rem` for Chart.js to size into. Equal
   specificity, so the later rule won and a 12px status strip rendered as a 256px
   green slab. It now carries the layout declarations it used to inherit. */
.mm-stackbar {
  width: 100%;
  display: block;
  height: 0.75rem;
  border-radius: var(--mm-radius-full);
  overflow: hidden;
  background-color: hsl(var(--mm-surface));
}

.mm-seg--completed {
  fill: hsl(var(--mm-success));
  background-color: hsl(var(--mm-success));
}

.mm-seg--adjusted {
  fill: hsl(var(--mm-accent));
  background-color: hsl(var(--mm-accent));
}

.mm-seg--skipped {
  fill: hsl(var(--mm-text-subtle));
  background-color: hsl(var(--mm-text-subtle));
}

.mm-seg--incomplete {
  fill: hsl(var(--mm-warning));
  background-color: hsl(var(--mm-warning));
}

.mm-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  /* Wider than the gap *inside* an item, so each value groups with its own
     label. It was `--mm-space-2` both here and inside, and the count carried
     `margin-left: auto` — which pinned the value to the right edge of its cell,
     8px from the NEXT item's swatch and a long way from the label it belongs to.
     Every number read as though it described the following status. */
  gap: var(--mm-space-2) var(--mm-space-6);
}

.mm-legend-item {
  display: flex;
  align-items: center;
  gap: var(--mm-space-2);
  font-size: var(--mm-text-sm);
}

.mm-legend-swatch {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: var(--mm-radius-sm);
  flex-shrink: 0;
}

.mm-legend-label {
  color: hsl(var(--mm-text));
  font-weight: var(--mm-weight-medium);
}

.mm-legend-count {
  font-variant-numeric: tabular-nums;
}

/* ---- Status badges (finalized-line status) ----------------------- */
.mm-badge--completed {
  color: hsl(var(--mm-success-text));
  background-color: hsl(var(--mm-success) / 0.1);
  border-color: hsl(var(--mm-success) / 0.35);
}

.mm-badge--adjusted {
  color: hsl(var(--mm-accent-text));
  background-color: hsl(var(--mm-accent) / 0.1);
  border-color: hsl(var(--mm-accent) / 0.35);
}

.mm-badge--skipped {
  color: hsl(var(--mm-text-muted));
  background-color: hsl(var(--mm-surface));
  border-color: hsl(var(--mm-border-strong));
}

.mm-badge--incomplete {
  color: hsl(var(--mm-warning-text));
  background-color: hsl(var(--mm-warning) / 0.1);
  border-color: hsl(var(--mm-warning) / 0.35);
}

/* ---- Numeric cells + variance emphasis --------------------------- */
/* No `text-align` here on purpose. This selector is (0,1,0) and loses to
   `.mm-table th, .mm-table td { text-align: left }` at (0,1,1), so a right-align
   written here does nothing at all — the alignment lives with the other numeric
   cell rules, scoped to `.mm-table` so it actually wins.

   Tabular figures and the no-wrap stay unscoped: `.mm-num` is also carried by two
   inline spans outside any table (dashboards/completion.html,
   dashboards/goal_pace.html), where `text-align` is inert on an inline box but
   lining figures still do real work. */
.mm-num {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.mm-variance {
  font-weight: var(--mm-weight-semibold);
}

.mm-variance--pos {
  color: hsl(var(--mm-success-text));
}

.mm-variance--neg {
  color: hsl(var(--mm-warning-text));
}

/* ---- Table card + horizontal scroll on narrow screens ------------ */
.mm-table-card {
  padding: 0;
  overflow: hidden;
}

.mm-table-scroll {
  /* A table is the one block that legitimately exceeds the page width. It scrolls
     inside its own container so the page body never does — a horizontally
     scrolling document breaks every other column on the screen. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* And this is what makes that hold for an absolutely-positioned descendant.
     `overflow-x: auto` clips and scrolls ordinary content, but an abs-positioned
     element is laid out against its nearest *positioned* ancestor — with none, that is
     the initial containing block, i.e. the document — so it contributes to the page's
     own scroll width from wherever the unclipped table put it, straight through the
     scroller. `.mm-visually-hidden` is exactly that: four templates hide an "Actions"
     column heading with one, and on the planner's seven-column history table the 1px
     span sat 102px past a 375px viewport and scrolled the whole document sideways.
     Position it and the scroller contains it. */
  position: relative;
}

.mm-table tfoot th,
.mm-table tfoot td {
  font-weight: var(--mm-weight-semibold);
  border-top: 2px solid hsl(var(--mm-border-strong));
  border-bottom: none;
}

/* ---- Inline notes ------------------------------------------------ */
.mm-note {
  margin: 0;
  padding: var(--mm-space-3) var(--mm-space-4);
  border-radius: var(--mm-radius-md);
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-snug);
}

.mm-note--warning {
  color: hsl(var(--mm-warning-text));
  background-color: hsl(var(--mm-warning) / 0.1);
  border: var(--mm-border-width) solid hsl(var(--mm-warning) / 0.3);
}

/* ---- Dashboard empty state --------------------------------------- */
.mm-dash-empty {
  display: grid;
  gap: var(--mm-space-3);
  justify-items: start;
}

.mm-dash-empty-title {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-lg);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  .mm-dash-tab,
  .mm-stat-card {
    transition: none;
  }
}

/* ---- Pt.2 lens CSS (C61) ----------------------------------------- */

/* Goal pace cards */
.mm-goal-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-goal-header {
  display: grid;
  gap: var(--mm-space-1);
}

.mm-goal-title-row {
  display: flex;
  align-items: center;
  gap: var(--mm-space-3);
  flex-wrap: wrap;
}

.mm-goal-name {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-lg);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  margin: 0;
  letter-spacing: var(--mm-tracking-tight);
}

.mm-goal-amounts {
  font-size: var(--mm-text-sm);
  font-variant-numeric: tabular-nums;
}

.mm-goal-progress-wrap {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--mm-space-3);
}

.mm-goal-progress-bar {
  width: 100%;
  height: 0.75rem;
  display: block;
}

.mm-goal-track-bg {
  fill: hsl(var(--mm-surface));
}

.mm-goal-track-fill {
  fill: hsl(var(--mm-accent));
}

.mm-goal-track-fill--reached {
  fill: hsl(var(--mm-success));
}

.mm-goal-pct {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  white-space: nowrap;
}

.mm-goal-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--mm-space-3);
  margin: 0;
}

.mm-goal-meta-item {
  display: grid;
  /* The wrapping flex row above stretches every item to the tallest, and the
     first cell here carries a figure plus a two-line note — so without this the
     other three values sit well below the labels that name them. */
  align-content: start;
  gap: var(--mm-space-1);
  /* Grid items default to `min-width: auto` (= min-content), so a value longer
     than its column pushes into the neighbouring one rather than wrapping. That
     is how "averaged over the last 180 days" ended up overprinting the target
     date. `min-width: 0` belongs on the ITEM, not the container. */
  min-width: 0;
}

.mm-goal-meta-item dd {
  overflow-wrap: break-word;
}

/* The averaging window explains how the figure was derived; it is not part of
   the figure, so it sits under it rather than running on from it. */
.mm-goal-meta-note {
  display: block;
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-subtle));
}

.mm-goal-meta-item dt {
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--mm-tracking-wide);
}

.mm-goal-meta-item dd {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
  margin: 0;
}

/* Portfolio section headings */
.mm-portfolio-section {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-portfolio-heading {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-xl);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  letter-spacing: var(--mm-tracking-tight);
  margin: 0;
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
  padding-bottom: var(--mm-space-3);
}

/* Dimmed table rows (e.g. unpriced holdings) */
.mm-table-row--dim td,
.mm-table-row--dim th {
  color: hsl(var(--mm-text-subtle));
}

/* Reconciliation note cell. Same split, same reason, as .mm-audit-note: the cap
   goes on the span inside the cell because `max-width` on a `<td>` is undefined
   per CSS 2.1 §10.4 and auto table layout ignores it. */
.mm-recon-note {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  white-space: normal;
}

.mm-recon-note-text {
  display: inline-block;
  max-width: 30ch;
}

/* Trade action badges */
.mm-badge--action-buy {
  color: hsl(var(--mm-success-text));
  background-color: hsl(var(--mm-success) / 0.1);
  border-color: hsl(var(--mm-success) / 0.35);
}

.mm-badge--action-sell {
  color: hsl(var(--mm-warning-text));
  background-color: hsl(var(--mm-warning) / 0.1);
  border-color: hsl(var(--mm-warning) / 0.35);
}

.mm-badge--action-hold_cash {
  color: hsl(var(--mm-text-muted));
  background-color: hsl(var(--mm-surface));
  border-color: hsl(var(--mm-border-strong));
}

/* success badge (on-track goals) — reuses mm-badge base */
.mm-badge--success {
  color: hsl(var(--mm-success-text));
  background-color: hsl(var(--mm-success) / 0.1);
  border-color: hsl(var(--mm-success) / 0.35);
}

/* ---- Audit log (C67) --------------------------------------------- */

.mm-audit-log {
  display: grid;
  gap: var(--mm-space-5);
}

/* Filter card: responsive grid of filter fields + submit/clear actions */
.mm-audit-filter-card {
  padding: var(--mm-space-4);
}

.mm-audit-filter-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--mm-space-4);
  align-items: end;
}

.mm-audit-filter-actions {
  display: flex;
  gap: var(--mm-space-2);
  align-items: center;
  flex-wrap: wrap;
}

/* Inputs rendered inside the filter form (outside .mm-form scope) */
.mm-audit-filter-form input {
  width: 100%;
  min-height: 2.75rem;
  padding: var(--mm-space-2) var(--mm-space-3);
  font-family: var(--mm-font-sans);
  font-size: var(--mm-text-base);
  color: hsl(var(--mm-text));
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  border-radius: var(--mm-radius-md);
  transition: border-color var(--mm-duration-fast) var(--mm-ease-out),
    box-shadow var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-audit-filter-form input:focus-visible {
  outline: none;
  border-color: hsl(var(--mm-accent));
  box-shadow: 0 0 0 3px hsl(var(--mm-ring) / 0.28);
}

.mm-audit-filter-form input::placeholder {
  color: hsl(var(--mm-text-subtle));
}

/* Timestamp column: monospace, nowrap */
.mm-audit-ts {
  font-family: var(--mm-font-mono, monospace);
  font-size: var(--mm-text-xs);
  white-space: nowrap;
  color: hsl(var(--mm-text-muted));
}

/* Event cell: what happened, then the key that says it. Two lines rather than
   one, because the label is the row's subject and the key is its evidence —
   running them together would make the reader choose which one to read.

   The stack is a wrapper INSIDE the cell and must never move onto the `<td>`.
   A cell that is not `display: table-cell` gets wrapped by the browser in an
   anonymous table-cell: `.mm-table td`'s padding and border-bottom then paint on
   the inner box, which is content-height rather than row-height, so the row's
   rule breaks into a notch under this one column whenever another cell is taller
   — and the Note column now wraps to several lines, which is exactly that
   condition. Same hazard, same fix as `.mm-td--actions`; guarded by
   audit/tests/test_log_css.py. */
.mm-audit-event-stack {
  display: grid;
  gap: 0.125rem;
  justify-items: start;
}

.mm-audit-event-label {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text));
  /* A grid item floors at its own min-content width, so the counterweight that
     lets a long value narrow this column belongs on the ITEM — here and on the
     link below — not on the container. */
  min-width: 0;
  overflow-wrap: break-word;
}

/* Event type inline code */
.mm-audit-event-type {
  font-family: var(--mm-font-mono, monospace);
  font-size: var(--mm-text-xs);
  background-color: hsl(var(--mm-surface));
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-sm);
  padding: 0.1em var(--mm-space-1);
  /* `break-word`, NOT `anywhere` — the rule this file states at the bottom, and
     this is the element that was breaking it.

     `anywhere` reduces min-content to one character, and nothing here supplies a
     floor: `min-width: 0` *removes* the automatic minimum, it does not establish
     one. At 375px the whole Event column collapsed to 35px, so
     `portfolio.balance_run.computed` rendered ~4 characters per line down 196px
     and the human label beside it ("Rebalance calculated", 63px) overflowed its
     own cell. `break-word` keeps min-content at the longest token: 208px, one
     line.

     Nothing was bought by the crush either. The table already lives inside
     `.mm-table-scroll` and was scrolling at that width regardless, so the column's
     floor never reached the document — which is the only thing `anywhere` is for. */
  overflow-wrap: break-word;
  min-width: 0;
}

/* The key is click-to-filter. Recolouring it took the base `a`'s one always-on
   signal and left `a:hover`'s underline as the whole affordance — and hover does
   not exist on touch, so at rest the key read as static text. Hence a permanent
   underline, the same fix as `.mm-dest-item-name`. It keeps the muted colour so a
   full page of rows does not read as a page of links competing with the label
   above each one; hover and focus add the accent. */
.mm-audit-event-link {
  color: hsl(var(--mm-text-muted));
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  min-width: 0;
}

.mm-audit-event-link:hover .mm-audit-event-type,
.mm-audit-event-link:focus-visible .mm-audit-event-type {
  color: hsl(var(--mm-accent-text));
  border-color: hsl(var(--mm-accent) / 0.55);
}

/* Recovery list in the filtered-to-nothing empty state: the types that exist. */
.mm-audit-type-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-2) var(--mm-space-4);
  list-style: none;
  margin: var(--mm-space-2) 0 var(--mm-space-4);
  padding: 0;
}

/* Target pill: a record named in the reader's words, so it is prose and not a
   status token — the base badge's uppercasing would turn "Finalized paycheck #3"
   into a shout. Capped and wrapping rather than ellipsised: nothing here is
   recoverable by hovering, so a clipped name is a name that is simply gone.

   The cap works here and would not on the `<td>`: `.mm-badge` is
   `display: inline-flex`, so this is an inline-level BLOCK box and `max-width`
   applies to it normally.

   No `overflow-wrap` here on purpose. This element carries both `.mm-badge` and
   `.mm-badge--target`, both (0,1,0), and the bulk user-text rule at the foot of
   this file names `.mm-badge` some 1700 lines below — so a declaration written
   here loses on source order and does nothing. It read as the rule governing
   this pill and was not; worse, the `anywhere`-vs-`break-word` correction that
   rule exists to enforce would have been swallowed silently if it were made
   here. The wrap for this element is set once, down there, with the reasoning
   beside it.

   No `min-width: 0` either, and not by omission: that is the counterweight for a
   flex or grid ITEM, and this pill is neither — its parent is a `<td>`. */
.mm-badge--target {
  text-transform: none;
  letter-spacing: normal;
  max-width: 24ch;
}

/* Actor cell.
   ---------------------------------------------------------------------------
   The cap, the clip and the ellipsis are on the span INSIDE the cell, never on
   the cell. `min-width`/`max-width` on a table cell is undefined per CSS 2.1
   §10.4 and auto table layout ignores it — and nothing in this file sets
   `table-layout: fixed` (deliberately: these tables size to their content).
   Carried on the `<td>`, all four declarations did nothing at all: no cap, no
   ellipsis, and a long address widened the column and pushed the table into the
   horizontal scroll the rule was written to avoid.

   The cell keeps only what a cell can actually do — type and colour. */
.mm-audit-actor {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* One line, ellipsised. An email address has no useful wrap point, and the local
   part is enough to tell two members apart. `inline-block` is what gives the
   ellipsis a definite box to overflow; the template pairs it with a `title` so
   the clipped half is still reachable. */
.mm-audit-actor-email {
  display: inline-block;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* Note cell is prose — the only cell on this page written for a human — so it
   wraps. It used to share the actor's clipping rule, which cut four of six rows
   mid-word ("Finalized paycheck draft #5: 3 m…") with no tooltip, no row link
   and no detail page, leaving the text on screen and permanently unreachable.
   The `overflow-wrap: break-word` this class already carries in the bulk rule
   below was inert the whole time; dropping `white-space: nowrap` is what
   activates it.

   The 30ch cap stays — AuditEvent.note is an unbounded TextField and the table
   scrolls horizontally, so an uncapped note widens the table rather than
   wrapping inside it — but it moves onto the span inside the cell, because
   `max-width` on a `<td>` is undefined per CSS 2.1 §10.4 and auto table layout
   ignores it. On the cell it was capping nothing. Matches .mm-recon-note. */
.mm-audit-note {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  white-space: normal;
}

.mm-audit-note-text {
  display: inline-block;
  max-width: 30ch;
}

/* Pagination nav */
.mm-pagination {
  display: flex;
  align-items: center;
  gap: var(--mm-space-3);
  justify-content: center;
  padding: var(--mm-space-2) 0;
}

.mm-pagination-info {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}


/* ==========================================================================
   Admin portal (C63)
   ========================================================================== */

.mm-portal {
  display: grid;
  gap: var(--mm-space-6);
}

.mm-portal-head {
  display: grid;
  gap: var(--mm-space-2);
}

/* The workspace this portal is administering — the one place the display name
   set on the Settings page is stated at full size. It holds a name a person
   typed, so it has to be allowed to break: `anywhere` rather than `break-word`
   because only `anywhere` lowers the element's min-content width, and it is that
   floor which propagates up through the grid above and widens the whole page
   (health/tests/test_no_horizontal_overflow.py). */
.mm-portal-workspace {
  margin: 0;
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
  overflow-wrap: anywhere;
}

.mm-portal-title {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-2xl);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  letter-spacing: var(--mm-tracking-tight);
  margin: 0;
}

.mm-portal-sub {
  margin: 0;
  font-size: var(--mm-text-sm);
}

/* Sub-navigation tabs (Settings / Team / … future C64/C65) */
.mm-portal-nav {
  display: flex;
  gap: var(--mm-space-1);
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
  padding-bottom: 0;
  /* Eight tabs do not fit a narrow window, and with neither wrapping nor
     scrolling they pushed the whole document 268px wide — the page scrolled
     sideways and took every heading with it. Wrapping is wrong for a tab strip
     (a second row of tabs reads as a second level of navigation), so it scrolls
     itself. The dashboard lens nav wrapped until it was found doing exactly what
     that parenthesis warns about; it now scrolls too. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}

.mm-portal-tab {
  /* Without this a tab shrinks to fit rather than letting the strip scroll. */
  flex: none;
  white-space: nowrap;
}

.mm-portal-tab {
  display: inline-block;
  padding: var(--mm-space-2) var(--mm-space-4);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text-muted));
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--mm-duration-fast) var(--mm-ease-out), border-color var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-portal-tab:hover {
  color: hsl(var(--mm-text));
}

.mm-portal-tab--active {
  color: hsl(var(--mm-text));
  border-bottom-color: hsl(var(--mm-accent));
}

/* Portal body */
.mm-portal-body {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-portal-section {
  display: grid;
  gap: var(--mm-space-5);
}

/* Cards used inside the portal */
.mm-portal-card {
  display: grid;
  gap: var(--mm-space-4);
}

.mm-portal-card--table {
  padding: 0;
  overflow-x: auto;
}

/* Only the scroll container may bleed to the card edge. Any prose child — a
   heading, an empty state — keeps the card's normal inset; without this the
   "All Access Grants" heading was painted on top of the card border. A child
   combinator, not a descendant one: the bare `--table` wrappers in
   audit_summary.html and usage_metrics.html hold a table directly, and indenting
   that inside an `overflow-x: auto` parent puts the left edge out of reach. */
.mm-portal-card--table > :not(.mm-table-scroll) {
  padding-inline: var(--mm-space-6);
}

.mm-portal-card--table > :not(.mm-table-scroll):first-child {
  padding-top: var(--mm-space-5);
}

.mm-portal-card--table > :not(.mm-table-scroll):last-child {
  padding-bottom: var(--mm-space-5);
}

.mm-portal-card--info {
  background-color: hsl(var(--mm-surface));
}

.mm-portal-card-heading {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-base);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  margin: 0;
}

.mm-portal-card-desc {
  margin: 0;
  font-size: var(--mm-text-sm);
}

/* Definition list for account info */
.mm-portal-dl {
  display: grid;
  gap: var(--mm-space-3);
  margin: 0;
}

.mm-portal-dl-row {
  display: grid;
  grid-template-columns: 10rem 1fr;
  gap: var(--mm-space-2);
  align-items: baseline;
}

.mm-portal-dt {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
}

.mm-portal-dd {
  font-size: var(--mm-text-sm);
  margin: 0;
}

/* Monospace badge for slugs/IDs */
.mm-text-mono {
  font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, monospace;
  font-size: var(--mm-text-xs);
}

/* Team table */
.mm-team-email {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text));
}

.mm-tr--self td {
  background-color: hsl(var(--mm-surface));
}

.mm-td--actions {
  /* Stays a table-cell. `display: flex` here makes the browser wrap the td in an
     anonymous table-cell; the td then sizes to its own (empty) content and paints
     only its padding and border-bottom — leaving a white notch through the card's
     rounded corner and a rule that stops halfway across the row. */
  white-space: nowrap;
}

.mm-td--actions > form {
  display: inline-flex;
  gap: var(--mm-space-2);
  align-items: center;
  vertical-align: middle;
}

/* Load-bearing: replaces the `gap` that stops applying once the td is no longer
   a flex container. */
.mm-td--actions > form + form {
  margin-left: var(--mm-space-2);
}

/* The row's account of itself where the controls are withheld. `.mm-td--actions`
   above is `white-space: nowrap` so a pair of buttons never breaks mid-row, and a
   sentence inheriting that would run the table sideways inside `.mm-table-scroll`
   instead of wrapping — hence the explicit `normal`, which beats an inherited
   value whatever the specificity. The measure and the cap sit on this span rather
   than on the cell because a `<td>` cannot be sized by `max-width` at all under
   auto table layout (audit/tests/test_log_css.py), and `inline-block` is what
   gives them a box to apply to. */
.mm-row-note {
  /* `block`, not `inline-block`. An inline-block baseline-aligns its LAST line with
     the text before it, so the moment a note wraps to two lines the earlier lines
     ride up above that text and the cell reads as two sentences printed over each
     other. It is a note *under* a value in every one of its uses, so it gets its
     own line — which is also what makes `max-width` mean something. Found by
     looking at the goal-pace lens: "No contributions recorded" and its explanation
     were colliding, and every test on that page passed. */
  display: block;
  white-space: normal;
  max-width: 26ch;
  font-size: var(--mm-text-sm);
}

/* No `.mm-team-action-form` rule, and no such class on the two forms it named.
   It was (0,1,0) against `.mm-td--actions > form` above at (0,1,1), on elements
   that are always inside `.mm-td--actions` — so every declaration in it lost, the
   forms computed to `inline-flex`, and the rule read in review as if it were
   holding the row's layout. The `> form` rule above is what actually does, with
   the `+ form` margin standing in for the `gap` a non-flex `<td>` cannot apply. */

/* Small size modifiers. `.mm-btn--sm` is not here: it is declared once, with the
   other button variants, and this block was silently overriding it. */
.mm-input--sm {
  padding: var(--mm-space-1) var(--mm-space-2);
  font-size: var(--mm-text-xs);
}

/* Role-specific badge tinting */
.mm-badge--role-owner {
  color: hsl(var(--mm-accent));
  background-color: hsl(var(--mm-accent) / 0.08);
  border-color: hsl(var(--mm-accent) / 0.3);
}

.mm-badge--role-admin {
  color: hsl(var(--mm-text));
  background-color: hsl(var(--mm-border));
  border-color: hsl(var(--mm-border-strong));
}

.mm-badge--role-user {
  color: hsl(var(--mm-text-muted));
  background-color: transparent;
  border-color: hsl(var(--mm-border));
}

.mm-badge--self {
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-subtle));
  background-color: transparent;
  border-color: hsl(var(--mm-border));
  margin-left: var(--mm-space-1);
}

/* ---- Admin portal billing / subscription status badges (C64) ------- */

/* BEM namespace anchor; color applied by the per-status modifier below. */
.mm-badge--status {
  /* structural anchor only */
}

/* active → success-ish (green) */
.mm-badge--status-active {
  color: hsl(var(--mm-success-text));
  background-color: hsl(var(--mm-success) / 0.1);
  border-color: hsl(var(--mm-success) / 0.35);
}

/* trial / free → info / neutral (surface-raised + lighter border) */
.mm-badge--status-trial,
.mm-badge--status-free {
  color: hsl(var(--mm-text-muted));
  background-color: hsl(var(--mm-surface-raised));
  border-color: hsl(var(--mm-border));
}

/* internal → accent (brand color — staff / internal tiers) */
.mm-badge--status-internal {
  color: hsl(var(--mm-accent-text));
  background-color: hsl(var(--mm-accent) / 0.1);
  border-color: hsl(var(--mm-accent) / 0.35);
}

/* expired → warning (amber — past-state, not an active error) */
.mm-badge--status-expired {
  color: hsl(var(--mm-warning-text));
  background-color: hsl(var(--mm-warning) / 0.1);
  border-color: hsl(var(--mm-warning) / 0.35);
}

/* Revoked / deactivated intent — explicit so templates can use it as a
   semantic class rather than relying on the mm-badge base fallback. */
.mm-badge--muted {
  color: hsl(var(--mm-text-muted));
  background-color: hsl(var(--mm-surface));
  border-color: hsl(var(--mm-border-strong));
}

/* Small left-margin utility — keeps inline badges from sitting flush
   against preceding text (used by the Expired and "joined" badges). */
.mm-ml-1 {
  margin-left: var(--mm-space-1);
}

/* C65 — Admin portal metrics + audit summary tabs
   ========================================================================== */

/* Filter form inside a portal card (date range + provider inputs in a row) */
.mm-metrics-filter-form {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--mm-space-4);
  align-items: end;
}

/* Numeric table cells (right-aligned for scannable columns).
   Scoped to .mm-table on purpose: an unscoped class selector is (0,1,0) and loses
   to `.mm-table th, .mm-table td { text-align: left }` at (0,1,1), so every
   numeric column in the app rendered ragged-left despite carrying these classes.
   Adding the ancestor makes these (0,2,0), which wins without needing !important.

   All three classes belong here, not just the portal's two. `.mm-num` is the one
   the nine dashboard tables use — 66 call sites, and it is the class carrying the
   money columns (planned, actual, variance, balance after, current value), which
   is where ragged-left costs the most. Every table call site of all three is
   inside a `<table class="mm-table">`. */
.mm-table .mm-th--num,
.mm-table .mm-td--num,
.mm-table .mm-num {
  text-align: right;
}

/* "View full audit log" footer link area */
.mm-metrics-view-all {
  display: flex;
  justify-content: flex-end;
  padding-top: var(--mm-space-2);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
}

/* ==========================================================================
   Account menu (header) — sign-out + personal security surfaces
   ========================================================================== */

.mm-account {
  position: relative;
}

/* Hide the native disclosure triangle; the caret span is the affordance. */
.mm-account > summary {
  list-style: none;
}

.mm-account > summary::-webkit-details-marker {
  display: none;
}

.mm-account-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--mm-space-1);
  height: 2.75rem; /* matches .mm-theme-toggle's 44px touch target */
  padding: 0 var(--mm-space-2);
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-surface-raised));
  color: hsl(var(--mm-text));
  cursor: pointer;
  transition: background-color var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-account-trigger:hover {
  background-color: hsl(var(--mm-surface));
}

.mm-account-trigger:focus-visible {
  outline: 2px solid hsl(var(--mm-ring));
  outline-offset: 2px;
}

.mm-account-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-accent));
  color: hsl(var(--mm-accent-foreground));
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  line-height: 1;
}

.mm-account-caret {
  --mm-icon-size: 0.7rem;
  color: hsl(var(--mm-text-muted));
  /* Points down when the menu is shut and up when it is open, so the control
     says which way it will go. The rotation is the state change, which is the
     only kind of movement this app spends time on. */
  transition: transform var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-account[open] .mm-account-caret {
  transform: rotate(180deg);
}

.mm-account-menu {
  position: absolute;
  top: calc(100% + var(--mm-space-2));
  right: 0;
  z-index: 20;
  min-width: 15rem;
  padding: var(--mm-space-2);
  border: var(--mm-border-width) solid hsl(var(--mm-border-strong));
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-surface-raised));
  box-shadow: var(--mm-shadow-lg);
}

.mm-account-meta {
  margin: 0 0 var(--mm-space-2);
  padding: var(--mm-space-2);
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-account-email {
  display: block;
  color: hsl(var(--mm-text));
  font-weight: var(--mm-weight-medium);
  overflow-wrap: break-word;
}

.mm-account-workspace {
  display: block;
  margin-top: var(--mm-space-1);
}

.mm-account-item {
  display: block;
  width: 100%;
  padding: var(--mm-space-2);
  border: 0;
  border-radius: var(--mm-radius-sm);
  background: none;
  color: hsl(var(--mm-text));
  font: inherit;
  font-size: var(--mm-text-sm);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.mm-account-item:hover {
  background-color: hsl(var(--mm-surface));
}

.mm-account-item:focus-visible {
  outline: 2px solid hsl(var(--mm-ring));
  outline-offset: -2px;
}

.mm-account-item--signout {
  margin-top: var(--mm-space-1);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: 0 0 var(--mm-radius-sm) var(--mm-radius-sm);
  color: hsl(var(--mm-danger));
}

/* ==========================================================================
   Public landing page

   The one surface an anonymous visitor sees. It used to be a left-aligned hero,
   three identical numbered cards and a note card in a single 68rem column — a
   third of the page was empty, nothing used the width, and it never once showed
   the product.

   What replaced it alternates on purpose. Hero with the waterfall on the right,
   a ruled three-step strip with no card chrome at all, three cards for the three
   claim kinds, then the same waterfall again on the *left* against prose on the
   right, a six-up fact grid, and a tinted close. Every band is a different shape,
   so the eye has somewhere to go.

   NOTHING HERE IS A NEW COMPONENT. The two figures are `.mm-wf` — the planning
   screen's own waterfall, its own `.mm-wf-bar--*` flow colours and its own
   `.mm-w-N` widths — and the legend swatches borrow `.mm-wf-bar-fill--*` rather
   than restating the series tokens, so a flow that changes colour changes here
   with it. What is below is layout and the two or three text roles a marketing
   page needs and an app page does not.
   ========================================================================== */

.mm-landing {
  display: grid;
  gap: var(--mm-space-8);
  padding-block: var(--mm-space-5) var(--mm-space-8);
  min-width: 0;
}

/* Every band is a grid item, and a grid item defaults to `min-width: auto` —
   "never narrower than my content". One unbreakable child (a long destination
   name, a bar track, a legend) would then force the track wider than the
   viewport and push the whole document sideways. It has to be on the items;
   setting it on `.mm-landing` does nothing. */
.mm-landing > * {
  min-width: 0;
}

@media (min-width: 60rem) {
  .mm-landing {
    gap: var(--mm-space-9);
  }
}

/* ---- Hero ------------------------------------------------------------- */

/* Carries `.mm-hero` as well, for the h1 scale, the editorial `<em>` and the
   lede — all three are written as descendants of that class, so the copy column
   inside this row picks them up unchanged. This rule only re-lays the row. */
.mm-landing-top {
  display: grid;
  gap: var(--mm-space-7);
  align-items: start;
  padding-block: var(--mm-space-4) 0;
}

/* THREE children in TWO columns, and the third is what fixes the hole.
   Intro-over-nothing beside a 860px panel left the copy column about 400px
   short, so the page opened on a headline floating in a field of white — the
   same emptiness this rebuild exists to remove, moved up one screen. The steps
   take that space, the panel spans both rows, and the first screen carries the
   claim, the proof and the method together.

   Placement lives here and only here: below this width the three stack in
   source order — sentence, then the thing itself, then how to work it. */
@media (min-width: 60rem) {
  .mm-landing-top {
    /* Not `1fr 1fr`. The demo is a fixed-ish object — a column of money figures
       reads at one width and gains nothing from more — so it takes a ceiling and
       the sentence takes the rest. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
    /* `auto 1fr`, so the demo's extra height lands in ONE place and that place is
       the bottom. Left to `auto auto` the grid splits a tall spanner's excess
       across both rows it covers, which opens a band of white between the lede and
       the steps — a hole in the middle of the copy, which is precisely the defect
       the three-child layout was built to remove. Pinned this way the steps follow
       the lede immediately and the slack collects under them, beside the tail of
       the panel, where it reads as the panel being long rather than as a gap. */
    grid-template-rows: auto 1fr;
    column-gap: var(--mm-space-8);
    row-gap: var(--mm-space-7);
  }

  .mm-landing-intro {
    grid-column: 1;
    grid-row: 1;
  }

  .mm-landing-top > .mm-landing-demo {
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  .mm-landing-top > .mm-landing-step-strip {
    grid-column: 1;
    grid-row: 2;
    /* One per row inside a single column. `auto-fit` would give two columns of
       an already-halved track and the sequence would read across then down. */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--mm-space-5);
  }
}

.mm-landing-intro {
  display: grid;
  gap: var(--mm-space-4);
  align-content: start;
  min-width: 0;
}

.mm-landing-fineprint {
  display: flex;
  align-items: flex-start;
  margin: 0;
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-snug);
  color: hsl(var(--mm-text-subtle));
}

/* The icon leads a sentence rather than sitting in a control, so it keeps its
   `em` size and only needs nudging onto the first line's x-height. */
.mm-landing-fineprint .mm-icon {
  margin-top: 0.2em;
}

/* ---- The demo: the product itself, not a picture of it ---------------- */

.mm-landing-demo {
  display: grid;
  gap: var(--mm-space-3);
  /* A <figure> carries a user-agent `margin: 1em 40px`, which nothing in the
     reset above clears. Left alone it insets the demo by 40px on one side of a
     grid track and reads as a mis-aligned column. */
  margin: 0;
  min-width: 0;
}

.mm-landing-demo-cap {
  display: grid;
  gap: var(--mm-space-1);
  min-width: 0;
}

.mm-landing-demo-title {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-lg);
  font-weight: var(--mm-weight-semibold);
  letter-spacing: var(--mm-tracking-tight);
  line-height: var(--mm-leading-snug);
}

/* The band brings its own bottom margin for the app layout, where it sits above
   a run of cards. Here the grid's gap owns the spacing and the two would add. */
.mm-landing-demo .mm-lens-verdict {
  margin-bottom: 0;
}

/* One step above the page rather than two, because this card is the thing the
   hero is pointing at. Same shadow ramp — a menu's elevation on the one object
   that is meant to lift off the page. */
.mm-landing-wf {
  min-width: 0;
  box-shadow: var(--mm-shadow-md);
}

/* Three columns, at 26rem, by tightening the floors rather than by wrapping.
   The waterfall's third column is the running remainder — the thing the planner
   rebuild is *for* — so this panel keeps it beside the amount the way the app does
   at desktop width. The component's own min-widths (6rem and 5.5rem) are sized for
   a full-width planner; here they left the label about seven characters a line, so
   they come down. They are only floors: a wider figure still widens its column and
   the label's `minmax(0, 1fr)` yields.

   Dropping the remainder to its own line under each row — the treatment the
   `max-width: 40rem` rule below the component uses — was the first attempt, and it
   doubled the panel's height. That reopened the hole in the hero that
   `.mm-landing-top`'s three-child layout exists to close, which is a worse defect
   than a tight column: it is the emptiness this page was rebuilt to remove.

   (The rule this replaced collapsed the row to two columns and said the third was
   an empty cell held for a draft input. True of the planner before the remainder
   column landed, and it left this panel's remainders in an unnamed implicit
   track.) */
.mm-landing-wf .mm-wf-figure {
  min-width: 5rem;
}

.mm-landing-wf .mm-wf-left {
  min-width: 4.25rem;
}

.mm-landing-legend {
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--mm-space-2) var(--mm-space-4);
}

.mm-landing-legend .mm-legend-item {
  font-size: var(--mm-text-xs);
}

.mm-landing-legend .mm-legend-label {
  font-weight: var(--mm-weight-normal);
  color: hsl(var(--mm-text-muted));
}

/* ---- Three steps, as a ruled strip -------------------------------------
   Deliberately not cards. Three cards of equal weight is what this page already
   was, and it is what made every step look like the same claim written out three
   times. A hairline, a small figure and a title is enough structure. */

.mm-landing-step-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--mm-space-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mm-landing-step {
  display: grid;
  gap: var(--mm-space-2);
  align-content: start;
  min-width: 0;
  padding-top: var(--mm-space-4);
  border-top: 2px solid hsl(var(--mm-border-strong));
}

/* The one accent on this band, on the step that says where to start. Matador red
   is the brand, the primary button and the focus ring; three accent rules in a
   row would make it decoration. */
.mm-landing-step:first-child {
  border-top-color: hsl(var(--mm-accent));
}

.mm-landing-step-num {
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-bold);
  letter-spacing: var(--mm-tracking-wide);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--mm-text-subtle));
}

.mm-landing-step-title {
  margin: 0;
  font-size: var(--mm-text-lg);
  line-height: var(--mm-leading-snug);
}

.mm-landing-step-text {
  margin: 0;
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-normal);
  color: hsl(var(--mm-text-muted));
}

/* ---- A band with a heading over a grid --------------------------------- */

.mm-landing-band {
  display: grid;
  gap: var(--mm-space-6);
  min-width: 0;
}

.mm-landing-band-head {
  display: grid;
  gap: var(--mm-space-3);
  max-width: 46rem;
  min-width: 0;
}

.mm-landing-title {
  margin: 0;
  font-size: var(--mm-text-2xl);
  text-wrap: balance;
}

.mm-landing-band-lede {
  margin: 0;
  font-size: var(--mm-text-lg);
  color: hsl(var(--mm-text-muted));
  text-wrap: pretty;
}

/* ---- The three claim kinds --------------------------------------------- */

.mm-kinds {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--mm-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mm-kind {
  display: grid;
  gap: var(--mm-space-3);
  align-content: start;
  min-width: 0;
}

/* Neutral, and that is the decision. Six accent-tinted tiles down one page would
   turn the brand colour into wallpaper, and none of these three is a status or a
   category with a colour of its own — the only colours that mean anything on
   this page are the four flow colours in the waterfall. */
.mm-kind-icon,
.mm-fact-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  background-color: hsl(var(--mm-surface));
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  color: hsl(var(--mm-text-muted));
}

/* A fixed box does not inherit the type beside it, so the icon inside one is
   sized in `rem` rather than `em` — docs/brand/README.md §5, which is the same
   rule that stopped a 15px icon rattling around a 44px button. */
.mm-kind-icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--mm-radius-md);
  --mm-icon-size: 1.25rem;
}

.mm-kind-title {
  margin: 0;
  font-size: var(--mm-text-lg);
  line-height: var(--mm-leading-snug);
}

.mm-kind-text {
  margin: 0;
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-normal);
  color: hsl(var(--mm-text-muted));
}

/* ---- The short-paycheck band: demo left, argument right ---------------- */

.mm-landing-split {
  display: grid;
  gap: var(--mm-space-6);
  min-width: 0;
}

@media (min-width: 60rem) {
  .mm-landing-split {
    grid-template-columns: minmax(0, 26rem) minmax(0, 1fr);
    gap: var(--mm-space-8);
    align-items: center;
  }
}

.mm-landing-split-text {
  display: grid;
  gap: var(--mm-space-4);
  align-content: start;
  min-width: 0;
}

.mm-landing-points {
  display: grid;
  gap: var(--mm-space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mm-landing-point {
  display: flex;
  align-items: flex-start;
  min-width: 0;
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-normal);
  color: hsl(var(--mm-text-muted));
}

/* Subtle, not accent: a bullet is chrome and carries no meaning of its own. */
.mm-landing-point .mm-icon {
  margin-top: 0.3em;
  color: hsl(var(--mm-text-subtle));
}

/* ---- The fact grid ------------------------------------------------------ */

.mm-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--mm-space-5) var(--mm-space-6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mm-fact {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--mm-space-3);
  align-items: start;
  min-width: 0;
}

.mm-fact-icon {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--mm-radius-full);
  --mm-icon-size: 1.125rem;
}

.mm-fact-body {
  display: grid;
  gap: var(--mm-space-1);
  min-width: 0;
}

.mm-fact-title {
  margin: 0;
  font-size: var(--mm-text-base);
  line-height: var(--mm-leading-snug);
}

.mm-fact-text {
  margin: 0;
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-normal);
  color: hsl(var(--mm-text-muted));
}

/* ---- Close -------------------------------------------------------------- */

.mm-landing-cta {
  display: grid;
  justify-items: center;
  gap: var(--mm-space-4);
  min-width: 0;
  padding: var(--mm-space-8) var(--mm-space-5);
  text-align: center;
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-lg);
  background-color: hsl(var(--mm-surface));
}

.mm-landing-cta-title {
  margin: 0;
  font-size: var(--mm-text-3xl);
  text-wrap: balance;
}

.mm-landing-cta-text {
  margin: 0;
  max-width: 44ch;
  font-size: var(--mm-text-lg);
  color: hsl(var(--mm-text-muted));
  text-wrap: pretty;
}

.mm-landing-cta-actions {
  justify-content: center;
}

/* ==========================================================================
   First-run setup checklist (dashboard overview)
   ========================================================================== */

.mm-setup-list {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-3);
  margin: var(--mm-space-5) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
}

.mm-setup-step {
  display: flex;
  align-items: flex-start;
  gap: var(--mm-space-3);
  padding: var(--mm-space-4);
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-surface));
}

.mm-setup-step--done {
  border-color: hsl(var(--mm-success));
  background-color: transparent;
}

.mm-setup-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-accent));
  color: hsl(var(--mm-accent-foreground));
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  line-height: 1;
}

.mm-setup-step--done .mm-setup-marker {
  background-color: hsl(var(--mm-success));
}

.mm-setup-body {
  min-width: 0;
}

.mm-setup-body > h3 {
  margin: 0 0 var(--mm-space-1);
  font-size: var(--mm-text-base);
}

.mm-setup-body > p {
  margin: 0 0 var(--mm-space-2);
}

.mm-setup-body > p:last-child {
  margin-bottom: 0;
}

/* ==========================================================================
   Allocation strategy editor
   ========================================================================== */

.mm-alloc-head {
  margin-bottom: var(--mm-space-6);
}


.mm-alloc-sub {
  max-width: 46ch;
  margin: 0;
  color: hsl(var(--mm-text-muted));
}

/* Editor beside a sticky preview. Collapses to a single column below the
   breakpoint, where the preview follows the fields it describes. */
.mm-alloc-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--mm-space-5);
  align-items: start;
}

@media (min-width: 60rem) {
  .mm-alloc-grid {
    grid-template-columns: minmax(0, 1fr) 20rem;
  }
}

.mm-alloc-main {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-4);
  min-width: 0;
}

.mm-alloc-step {
  padding: var(--mm-space-5);
}

.mm-alloc-step-head {
  display: flex;
  align-items: flex-start;
  gap: var(--mm-space-3);
  margin-bottom: var(--mm-space-4);
}

.mm-alloc-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-accent));
  color: hsl(var(--mm-accent-foreground));
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  line-height: 1;
}

.mm-alloc-step-title {
  margin: 0 0 var(--mm-space-1);
  font-size: var(--mm-text-lg);
}

.mm-alloc-step-hint {
  max-width: 62ch;
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* --- repeating rows --- */

.mm-rows {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-2);
  margin-bottom: var(--mm-space-3);
}

.mm-row {
  padding: var(--mm-space-3);
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-surface));
}

.mm-row-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--mm-space-3);
}

.mm-row-field {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-1);
  min-width: 0;
}

.mm-row-field--grow {
  flex: 1 1 12rem;
}

.mm-row-field--money {
  flex: 0 0 8rem;
}

.mm-label--sm {
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
}

.mm-row-badge {
  align-self: flex-start;
  padding: 0.1rem var(--mm-space-2);
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-surface-raised));
  color: hsl(var(--mm-text-muted));
  font-size: var(--mm-text-xs);
}

.mm-row-remove {
  flex: 0 0 auto;
}

.mm-row-btn {
  width: 2rem;
  height: 2rem;
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  background: none;
  color: hsl(var(--mm-text-muted));
  font-size: var(--mm-text-lg);
  line-height: 1;
  cursor: pointer;
}

.mm-row-btn:hover {
  border-color: hsl(var(--mm-danger));
  color: hsl(var(--mm-danger));
}

.mm-row-btn:focus-visible {
  outline: 2px solid hsl(var(--mm-ring));
  outline-offset: 2px;
}

/* The DELETE checkbox stays reachable without JavaScript, but the button above
   is the affordance when scripting is available. Visually hidden, not `hidden`. */
.mm-row-delete {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.mm-alloc-actions {
  align-items: center;
  padding-top: var(--mm-space-2);
}

.mm-alloc-immutable {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--mm-text-xs);
}

/* --- live preview --- */

.mm-alloc-side {
  min-width: 0;
}

@media (min-width: 60rem) {
  .mm-alloc-side {
    position: sticky;
    top: var(--mm-space-4);
  }
}

.mm-preview {
  padding: var(--mm-space-5);
}

.mm-preview-title {
  margin: 0 0 var(--mm-space-2);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text-muted));
  text-transform: uppercase;
  letter-spacing: var(--mm-tracking-wide);
}

.mm-preview-input {
  display: flex;
  align-items: center;
  gap: var(--mm-space-1);
  margin-bottom: var(--mm-space-4);
}

.mm-preview-currency {
  font-size: var(--mm-text-xl);
  color: hsl(var(--mm-text-muted));
}

.mm-preview-net {
  font-size: var(--mm-text-xl);
  font-variant-numeric: tabular-nums;
}

.mm-preview-rows {
  margin: 0 0 var(--mm-space-3);
  padding-bottom: var(--mm-space-3);
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-preview-row {
  display: flex;
  justify-content: space-between;
  gap: var(--mm-space-3);
  padding: var(--mm-space-1) 0;
  font-size: var(--mm-text-sm);
}

.mm-preview-row > dt {
  color: hsl(var(--mm-text-muted));
}

.mm-preview-row > dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.mm-preview-row--strong {
  font-size: var(--mm-text-base);
  font-weight: var(--mm-weight-semibold);
}

.mm-preview-neg {
  color: hsl(var(--mm-danger));
}

.mm-preview-split {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-1);
}

.mm-preview-split-row {
  display: flex;
  justify-content: space-between;
  gap: var(--mm-space-3);
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-preview-split-row > span:last-child {
  color: hsl(var(--mm-text));
  font-variant-numeric: tabular-nums;
}

.mm-preview-note {
  margin: var(--mm-space-3) 0 0;
  font-size: var(--mm-text-sm);
}

.mm-preview-note.mm-preview-warn {
  color: hsl(var(--mm-warning-text));
}

.mm-preview-note.mm-preview-ok {
  color: hsl(var(--mm-success-text));
}

.mm-preview-caveat {
  margin: var(--mm-space-2) 0 0;
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-subtle));
}

/* ==========================================================================
   Paycheck flow — staged pages with a progress rail
   ========================================================================== */

.mm-flow-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--mm-space-4);
  margin-bottom: var(--mm-space-5);
}


.mm-flow-head-meta {
  display: flex;
  align-items: center;
  gap: var(--mm-space-4);
}

/* The number a member tracks while allocating. It was nowhere on the old page —
   they had to hold it in their head while scrolling. */
.mm-flow-remaining {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.mm-flow-remaining-label {
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
  text-transform: uppercase;
  letter-spacing: var(--mm-tracking-wide);
  /* This became an interpolated value when the header figure went stage-aware,
     so it falls under the same rule as every other slot that renders text it did
     not author. Short today; the guard is about what the slot permits. */
  overflow-wrap: break-word;
}

.mm-flow-remaining-value {
  font-size: var(--mm-text-xl);
  font-weight: var(--mm-weight-semibold);
  font-variant-numeric: tabular-nums;
}

.mm-flow-remaining--over .mm-flow-remaining-value {
  color: hsl(var(--mm-danger));
}

/* --- progress rail --- */

.mm-rail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--mm-space-2);
  margin: 0 0 var(--mm-space-5);
  padding: 0;
  list-style: none;
}

.mm-rail-link {
  display: flex;
  align-items: flex-start;
  gap: var(--mm-space-2);
  height: 100%;
  padding: var(--mm-space-3);
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-surface));
  color: inherit;
  text-decoration: none;
  transition: border-color var(--mm-duration-fast) var(--mm-ease-out);
}

a.mm-rail-link:hover {
  border-color: hsl(var(--mm-border-strong));
}

a.mm-rail-link:focus-visible {
  outline: 2px solid hsl(var(--mm-ring));
  outline-offset: 2px;
}

.mm-rail-item--current .mm-rail-link {
  border-color: hsl(var(--mm-accent));
  background-color: hsl(var(--mm-surface-raised));
}

.mm-rail-item--locked .mm-rail-link {
  opacity: 0.55;
  cursor: not-allowed;
}

.mm-rail-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-surface-raised));
  color: hsl(var(--mm-text-muted));
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-semibold);
  line-height: 1;
}

.mm-rail-item--current .mm-rail-marker {
  background-color: hsl(var(--mm-accent));
  color: hsl(var(--mm-accent-foreground));
}

.mm-rail-item--done .mm-rail-marker {
  background-color: hsl(var(--mm-success));
  color: hsl(var(--mm-accent-foreground));
}

/* Worked, but still carrying a caveat — the stage whose subtext reads
   "$1,420.00 unassigned". It must not take the success fill above: a green tick
   sitting on top of that sentence is the rail contradicting the header. The tint
   is a second signal only; the marker keeps its step number and the subtext says
   the thing in words, so nothing here is carried by colour alone. */
.mm-rail-item--caveat .mm-rail-marker {
  background-color: hsl(var(--mm-warning) / 0.16);
  color: hsl(var(--mm-warning-text));
}

.mm-rail-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.mm-rail-title {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
}

.mm-rail-sub {
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
}

/* --- step panel --- */

.mm-step {
  padding: var(--mm-space-5);
}

.mm-step-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--mm-space-3);
  margin-bottom: var(--mm-space-2);
}

.mm-step-title {
  margin: 0;
  font-size: var(--mm-text-lg);
}

.mm-step-hint {
  max-width: 68ch;
  margin: 0 0 var(--mm-space-4);
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-step-counter {
  padding: 0.1rem var(--mm-space-2);
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-surface-raised));
  color: hsl(var(--mm-text-muted));
  font-size: var(--mm-text-xs);
  font-variant-numeric: tabular-nums;
}

.mm-step-counter--over {
  background-color: hsl(var(--mm-danger));
  color: hsl(var(--mm-accent-foreground));
}

.mm-step-counter--done {
  background-color: hsl(var(--mm-success));
  color: hsl(var(--mm-accent-foreground));
}

.mm-step-note {
  margin: 0 0 var(--mm-space-4);
  padding: var(--mm-space-3);
  border-left: 3px solid hsl(var(--mm-accent));
  border-radius: var(--mm-radius-sm);
  background-color: hsl(var(--mm-surface));
  font-size: var(--mm-text-sm);
}

/* --- money inputs --- */

.mm-money-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--mm-space-4);
  margin-bottom: var(--mm-space-4);
}

.mm-money-field {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-1);
  min-width: 0;
}

.mm-money-input {
  display: flex;
  align-items: center;
  gap: var(--mm-space-1);
}

.mm-money-currency {
  font-size: var(--mm-text-xl);
  color: hsl(var(--mm-text-muted));
}

.mm-money-input .mm-input {
  font-size: var(--mm-text-xl);
  font-variant-numeric: tabular-nums;
}

/* Prose of unpredictable length — a form's own help text renders through this —
   so a long unbroken token has to be allowed to break rather than widening the
   column it sits in. `break-word` and not `anywhere`: this is a sentence, and
   `anywhere` would break ordinary words mid-syllable. */
.mm-field-hint {
  margin: 0;
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
  overflow-wrap: break-word;
}

.mm-flow-next {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--mm-space-5);
}

/* ==========================================================================
   Dashboard — conclusions first
   ========================================================================== */

/* Hero figure: the one number the page leads with. Sans (never the serif display
   face — it reads as decoration on a number), and PROPORTIONAL figures.
   tabular-nums gives every digit the width of a zero, which looks loose at
   display sizes; it is reserved for columns that must align. */
.mm-hero-block {
  margin-bottom: var(--mm-space-5);
}

.mm-hero-label {
  margin: 0;
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
  text-transform: uppercase;
  letter-spacing: var(--mm-tracking-wide);
}

.mm-hero-figure {
  margin: var(--mm-space-1) 0;
  font-family: var(--mm-font-sans);
  font-size: 3rem;
  font-weight: var(--mm-weight-semibold);
  line-height: var(--mm-leading-tight);
  letter-spacing: var(--mm-tracking-tight);
}

.mm-hero-verdict {
  display: flex;
  align-items: center;
  gap: var(--mm-space-2);
  margin: 0;
  font-size: var(--mm-text-lg);
}

/* --- status tones: icon + label, never colour alone --- */

/* The tone chip was a coloured disc with a Unicode character knocked out of it —
   ✓ ▲ ! –. Two things were wrong with that and neither was visible in code:
   the four characters are three different weights and two different footprints,
   so the set never looked like a set; and where Segoe UI Emoji is installed ✓
   resolves to a *colour* glyph, which ignores `color:` outright — the "good" chip
   rendered the same green in dark mode as in light, and the disc behind it did
   all the work.

   Now each tone is its own silhouette from the sprite — filled circle, filled
   triangle, filled octagon, dashed ring — so the disc is redundant: the icon *is*
   the shape. That also fixes the accessibility claim the old set could not honour.
   "Status is never carried by colour alone" was satisfied by a hidden label, but
   a sighted reader with a colour-vision deficiency still saw four identical discs.
   Four outlines are four things in greyscale.

   No background and no border, so nothing here depends on which surface token the
   host container happens to paint — the reason the old `unknown` chip needed its
   own special case to stop it vanishing into --mm-surface-raised. */
/* rem, not em: a verdict chip appears beside a 16px headline on the lens band and
   beside 14px body text in a system-check row, and it should be the same size in
   both — it is a status badge, not punctuation. */
.mm-tone-icon {
  --mm-icon-size: 1.25rem;
  display: block;
  flex: 0 0 auto;
  color: hsl(var(--mm-text-muted));
}

.mm-tone-good .mm-tone-icon {
  color: hsl(var(--mm-success));
}

.mm-tone-warning .mm-tone-icon {
  color: hsl(var(--mm-warning));
}

.mm-tone-serious .mm-tone-icon {
  color: hsl(var(--mm-danger));
}

/* Unknown is the tone a new member sees most — every lens reads unknown until the
   first paycheck is saved — so it has to look deliberate rather than unfinished.
   The dashed ring says "nothing recorded yet" on its own; muted ink keeps it from
   competing with the three tones that are reporting something. */
.mm-tone-unknown .mm-tone-icon {
  color: hsl(var(--mm-text-subtle));
}

.mm-verdicts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--mm-space-3);
  margin-bottom: var(--mm-space-5);
}

.mm-verdict {
  padding: var(--mm-space-4);
  border-left: 3px solid hsl(var(--mm-border));
}

.mm-verdict.mm-tone-good { border-left-color: hsl(var(--mm-success)); }
.mm-verdict.mm-tone-warning { border-left-color: hsl(var(--mm-warning)); }
.mm-verdict.mm-tone-serious { border-left-color: hsl(var(--mm-danger)); }

.mm-verdict-headline {
  display: flex;
  align-items: flex-start;
  gap: var(--mm-space-2);
  margin: 0 0 var(--mm-space-1);
  font-weight: var(--mm-weight-medium);
}

.mm-verdict-detail {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* --- panels --- */

.mm-panel {
  margin-bottom: var(--mm-space-4);
  padding: var(--mm-space-5);
}

.mm-panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--mm-space-3);
  margin-bottom: var(--mm-space-2);
}

.mm-panel-title {
  margin: 0;
  font-size: var(--mm-text-lg);
}

.mm-panel-link {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-panel-hint {
  margin: 0 0 var(--mm-space-4);
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* ---- The chart component's own frame -------------------------------------
   `templates/dashboards/partials/chart.html` emits `.mm-chart-block` around every
   chart in the app, an optional `.mm-chart-title`, and the `.mm-chart-empty`
   sentence the renderer unhides whenever a series is empty or too thin to plot.
   None of the three had a rule anywhere in this file, on all nine lenses plus the
   overview: the message rendered at body size in full-strength ink — louder than
   the caption two lines above it, and reading as a finding rather than as the
   note it is — and the `<h3>` took the global `h2, h3` size with `--mm-space-4`
   beneath it, which is a section break sitting inside a card. `.mm-chart-caption`
   and `.mm-chart-solo` beside them are both styled, so this was an oversight. */
.mm-chart-block {
  /* A grid item inside `.mm-chart-card`. The "View as table" disclosure it wraps
     carries a `.mm-table-scroll`, whose own overflow only contains the table once
     the item is allowed to be narrower than its content — min-width belongs on
     the item, not on the scroller. */
  min-width: 0;
}

.mm-chart-title {
  margin: 0 0 var(--mm-space-1);
  font-size: var(--mm-text-base);
}

.mm-chart-empty {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* The canvas needs a bounded box; Chart.js sizes itself to the parent. */
.mm-chart {
  position: relative;
  height: 16rem;
  margin-bottom: var(--mm-space-3);
}

/* ...but only when there is a chart. When the renderer shows a message instead
   — no data, or a line with a single reading — the fixed height leaves one
   sentence adrift in 16rem of empty card, which looks like a failed load. */
.mm-chart--message {
  height: auto;
}

.mm-chart--message canvas {
  display: none;
}

/* A horizontal bar family draws one row per label, so a one-row chart in a fixed
   16rem frame is 33px of bar and 220px of empty ruled space — which reads as a
   panel that failed to load. The axis, the zero line and the disclosure all stay;
   only the box shrinks to what is in it. Set by the renderer, which is the only
   place that knows how many rows survived. */
.mm-chart--rows-1 {
  height: 8rem;
}

.mm-chart--rows-2 {
  height: 10rem;
}

.mm-chart--rows-3 {
  height: 12rem;
}

/* The one reading a too-thin series does have. A time series with a single point
   draws no chart, and the card named after the figure — "Total tracked balance" —
   showed no figure at all: the number was in the payload and rendered nowhere the
   reader could see. Treated as a headline value, not body copy, because that is
   what it is. */
.mm-chart-solo {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--mm-space-2);
  margin: 0 0 var(--mm-space-1);
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-2xl);
  font-weight: var(--mm-weight-bold);
  font-variant-numeric: tabular-nums;
}

.mm-chart-solo-label {
  font-family: var(--mm-font-sans);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-normal);
  color: hsl(var(--mm-text-muted));
}

.mm-chart-table > summary {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  cursor: pointer;
}

/* Any table can outgrow a narrow screen; scroll it inside its own box rather
   than letting it push the page sideways. */
/* --- goal meters: one ratio against a limit --- */

.mm-goal-list {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mm-goal-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--mm-space-2);
  margin-bottom: var(--mm-space-2);
}

.mm-goal-name {
  font-weight: var(--mm-weight-medium);
}

.mm-goal-figures {
  font-variant-numeric: tabular-nums;
}

/* Track is a lighter step of the fill's own ramp, so state reads across the
   whole bar rather than only where it happens to end. */
.mm-meter {
  height: 0.5rem;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-accent) / 0.18);
  overflow: hidden;
}

.mm-meter-fill {
  display: block;
  height: 100%;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-accent));
}

.mm-meter.mm-tone-good .mm-meter-fill { background-color: hsl(var(--mm-success)); }
.mm-meter.mm-tone-good { background-color: hsl(var(--mm-success) / 0.18); }
.mm-meter.mm-tone-warning .mm-meter-fill { background-color: hsl(var(--mm-warning)); }
.mm-meter.mm-tone-warning { background-color: hsl(var(--mm-warning) / 0.18); }

/* Unknown is a tone, and it needs a rule of its own. Without one the meter fell
   through to the defaults above — --mm-accent, matador red, which is the same
   hue as --mm-danger — so a goal whose verdict is the neutral "No pace yet."
   drew an alarm-coloured bar, potentially in a list beside a green on-track
   goal. Every lens is unknown until the first paycheck is finalized, so this is
   the state a new member sees first and the one they see most.

   Neutral grey, matching the treatment the unknown tone *chip* was deliberately
   given for exactly this reason (`.mm-tone-unknown .mm-tone-icon` above).
   --mm-text-subtle is a mid grey in light mode and a light grey on dark, so the
   fill reads as "no judgement" against the track in both themes. */
.mm-meter.mm-tone-unknown .mm-meter-fill { background-color: hsl(var(--mm-text-subtle)); }
.mm-meter.mm-tone-unknown { background-color: hsl(var(--mm-text-subtle) / 0.18); }

.mm-goal-verdict {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-2);
  margin: var(--mm-space-2) 0 0;
  font-size: var(--mm-text-sm);
}

/* --- diverging variance bars --- */

.mm-diverge {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mm-diverge-row {
  display: grid;
  grid-template-columns: minmax(6rem, 10rem) 1fr minmax(5rem, auto);
  align-items: center;
  gap: var(--mm-space-3);
  font-size: var(--mm-text-sm);
}

.mm-diverge-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Bars grow out from a centre axis: left is under plan, right is over. */
.mm-diverge-track {
  position: relative;
  display: block;
  height: 1.25rem;
}

.mm-diverge-axis {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background-color: hsl(var(--mm-border-strong));
}

.mm-diverge-bar {
  position: absolute;
  top: 0.25rem;
  bottom: 0.25rem;
  border-radius: var(--mm-radius-sm);
}

.mm-diverge-bar--under {
  right: 50%;
  background-color: hsl(var(--mm-diverge-neg));
}

.mm-diverge-bar--over {
  left: 50%;
  background-color: hsl(var(--mm-diverge-pos));
}

.mm-diverge-value {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Lens verdict band -----------------------------------------------------------
   The conclusion each lens opens with. Tone is carried by the left rule and the
   icon's own colour; the card surface stays neutral so a page with a warning on
   it does not read as an alarm. The icon is paired with a visually-hidden label
   in the markup, so none of this is colour-alone. */

.mm-lens-verdict {
  display: flex;
  align-items: flex-start;
  gap: var(--mm-space-3);
  margin-bottom: var(--mm-space-4);
  padding: var(--mm-space-3) var(--mm-space-4);
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-left-width: 3px;
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-surface-raised));
}

.mm-lens-verdict-body {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-1);
  min-width: 0;
}

.mm-lens-verdict-headline {
  margin: 0;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-base);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  text-wrap: balance;
}

.mm-lens-verdict-detail {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  text-wrap: pretty;
}

.mm-lens-verdict .mm-tone-icon {
  flex: none;
  margin-top: 0.1rem;
}

/* The band's left rule takes the tone; the tone-icon rules above already colour
   the glyph, so this is the only additional colour a tone introduces. */

.mm-lens-verdict.mm-tone-good {
  border-left-color: hsl(var(--mm-success));
}

.mm-lens-verdict.mm-tone-warning {
  border-left-color: hsl(var(--mm-warning));
}

.mm-lens-verdict.mm-tone-serious {
  border-left-color: hsl(var(--mm-danger));
}

.mm-lens-verdict.mm-tone-unknown {
  border-left-color: hsl(var(--mm-border-strong));
}

/* System check ---------------------------------------------------------------
   The admin portal's self-check list. Deliberately the same components as the
   dashboard verdict band — a second status vocabulary would be a second thing to
   keep in sync, and this page means exactly what that one means. */

.mm-check-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: var(--mm-space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mm-check {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-2);
  padding: var(--mm-space-4);
  border-left-width: 3px;
}

.mm-check.mm-tone-good {
  border-left-color: hsl(var(--mm-success));
}

.mm-check.mm-tone-warning {
  border-left-color: hsl(var(--mm-warning));
}

.mm-check.mm-tone-serious {
  border-left-color: hsl(var(--mm-danger));
}

.mm-check.mm-tone-unknown {
  border-left-color: hsl(var(--mm-border-strong));
}

.mm-check-head {
  display: flex;
  align-items: center;
  gap: var(--mm-space-2);
}

.mm-check-name {
  margin: 0;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: hsl(var(--mm-text-muted));
}

.mm-check-summary {
  margin: 0;
  font-size: var(--mm-text-base);
  color: hsl(var(--mm-text));
  text-wrap: pretty;
}

.mm-check-detail {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  text-wrap: pretty;
}

/* Let layout children shrink -------------------------------------------------
   A grid or flex item defaults to `min-width: auto`, which means "never narrower
   than my content". One unbreakable child — the horizontally-scrolling nav, a
   long destination name, a wide table — then forces its whole track wider than
   the viewport, and because the container is a grid the overflow propagates all
   the way out to the document. The page scrolls sideways and takes every heading
   with it.

   The container itself having `min-width: 0` does not help; the rule has to be on
   the items. This was 16px of horizontal scroll on every dashboard at 375px,
   caused by the nav scroller inside the header. */

.mm-shell > *,
.mm-shell > * > *,
.mm-dash > *,
.mm-dash > * > *,
.mm-dash-head > *,
.mm-dash-body > *,
.mm-dash-body > * > *,
.mm-portal > *,
.mm-portal > * > *,
.mm-portal-body > *,
.mm-portal-body > * > *,
.mm-auth-shell > *,
.mm-auth-shell > * > *,
.mm-verdicts > *,
.mm-check-list > *,
.mm-lens-verdict > * {
  min-width: 0;
}

/* Narrow-width gutters -------------------------------------------------------
   At 375px the container, page section and card each contributed their full
   desktop padding — 144px of a 375px screen, 38% of it, spent on gutters. The
   chart canvas got 229px and its money axis took 43 of those.

   Padding that reads as generous at 1280 reads as a squeeze at 375. These are the
   three that nest, so they are the three that compound. */

@media (max-width: 40rem) {
  .mm-container {
    padding-left: var(--mm-space-3);
    padding-right: var(--mm-space-3);
  }

  .mm-dash,
  .mm-portal {
    padding-left: var(--mm-space-2);
    padding-right: var(--mm-space-2);
  }

  .mm-card {
    padding-left: var(--mm-space-4);
    padding-right: var(--mm-space-4);
  }
}

/* User-supplied text has to be allowed to break ------------------------------
   Every selector here renders something a person typed — a destination name, a
   goal, a line label, a note, a verdict built from one. The list is *derived*
   from the templates, not written by hand: the first version of this rule was
   hand-written, named five classes that do not exist, and missed
   `mm-dest-item-name`, which is the one that actually broke. Its test passed
   against the invented list while a 56-character destination name pushed the real
   page 201px wide.

   A word with nowhere to break does not wrap. It makes its container wider than
   the screen, and because these sit inside grids that overflow propagates out to
   the document — the same horizontal scroll the min-width rules fixed
   structurally, reintroduced one element at a time by content.

   `break-word`, NOT `anywhere`. They break text identically; they differ in
   intrinsic sizing, and that difference shipped a live defect. `anywhere` reduces
   an element's MIN-CONTENT width to a single character. `.mm-obligations-name` is
   `flex: 1` — basis 0 — sharing a row with a number input, so with `anywhere` its
   automatic minimum size became one character and the label rendered vertically,
   "Credit Card" as one letter per line, in production.

   `break-word` keeps min-content at the longest word, so a flex item still claims
   its share, while still breaking a word that would otherwise overflow. The
   container-shrinking half of the job is already done by the `min-width: 0` rules
   above, which is where it belongs — the reason for reaching for `anywhere` in the
   first place was a job something else was already doing.

   "On short labels it does nothing, so covering everything costs nothing" was the
   reasoning that shipped this. It was wrong: the cost is not on the text, it is on
   the box.

   Regenerate with health/tests/test_hostile_input.py, which derives the same list
   and fails if a template starts rendering user text in a class not covered. */

.mm-account-workspace,
.mm-alloc-label,
.mm-audit-note,
.mm-audit-note-text,
.mm-auth-heading,
.mm-auth-sub,
.mm-badge,
.mm-badge--active,
.mm-badge--role,
.mm-badge--type,
.mm-bulk-complete-name,
.mm-check-detail,
.mm-dest-item-desc,
.mm-dest-item-name,
.mm-dest-item-portfolio,
.mm-diverge-label,
.mm-edit-history-note,
.mm-edit-line-label,
.mm-flow-title,
.mm-form-help,
.mm-goal-name,
.mm-income-source-name,
.mm-invite-email,
.mm-label,
.mm-legend-label,
.mm-lens-verdict-detail,
.mm-nav-link,
.mm-nav-link--pending,
.mm-obligations-name,
.mm-portal-dd,
.mm-portfolio-heading,
.mm-recon-note,
.mm-recon-note-text,
.mm-review-line-name,
.mm-review-title,
.mm-session-name,
.mm-td,
.mm-text-muted,
.mm-text-sm,
.mm-verdict-detail {
  overflow-wrap: break-word;
}

/* ---------------------------------------------------------------------------
   Work stage — one line, one question.

   Replaces `.mm-line-card` on this stage (it stays for Allocate, which really
   does edit every field). The open row is a single strip: what was planned on
   the left, the one-click answer on the right, and everything else folded away
   behind a disclosure that only opens when the answer is not "as planned".
   --------------------------------------------------------------------------- */

.mm-work-list {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-2);
  margin-top: var(--mm-space-4);
}

.mm-work-row {
  border: 1px solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  background: hsl(var(--mm-surface));
  padding: var(--mm-space-3) var(--mm-space-4);
}

/* A settled row is quieter than an open one: the open ones are the work. */
.mm-work-row--settled {
  background: hsl(var(--mm-bg));
  border-color: hsl(var(--mm-border) / 0.6);
}

.mm-work-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-4);
  flex-wrap: wrap;
  row-gap: var(--mm-space-2);
}

.mm-work-what {
  display: flex;
  align-items: baseline;
  gap: var(--mm-space-2);
  flex-wrap: wrap;
  /* Basis well above one word: `flex: 1` alone resolves to basis 0, which is
     how a destination label ended up rendering one letter per line. */
  flex: 1 1 12rem;
  min-width: 8rem;
}

.mm-work-name {
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
  overflow-wrap: break-word;
}

.mm-work-plan,
.mm-work-outcome {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-work-mark {
  flex: none;
  font-weight: var(--mm-weight-bold);
}

.mm-work-figures {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  flex: none;
  min-width: 0;
}

.mm-work-amount {
  font-variant-numeric: tabular-nums;
  font-weight: var(--mm-weight-semibold);
}

.mm-work-variance {
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
  font-variant-numeric: tabular-nums;
}

.mm-work-confirm {
  flex: none;
}

.mm-work-note {
  margin: var(--mm-space-2) 0 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-subtle));
  font-style: italic;
  overflow-wrap: break-word;
}

.mm-work-differed {
  margin-top: var(--mm-space-2);
}

.mm-work-differed-summary {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  cursor: pointer;
  width: fit-content;
}

.mm-work-differed-summary:hover {
  color: hsl(var(--mm-text));
}

.mm-work-differed-body {
  display: grid;
  grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr);
  gap: var(--mm-space-3);
  align-items: start;
  margin-top: var(--mm-space-3);
  padding-top: var(--mm-space-3);
  border-top: 1px solid hsl(var(--mm-border));
}

.mm-work-differed-actions {
  grid-column: 1 / -1;
  display: flex;
  gap: var(--mm-space-2);
  flex-wrap: wrap;
}

.mm-work-bulk {
  display: flex;
  align-items: center;
  gap: var(--mm-space-3);
  flex-wrap: wrap;
  margin-top: var(--mm-space-3);
}

.mm-work-bulk-hint {
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-work-reopen {
  margin-top: var(--mm-space-2);
}

.mm-step-footnote {
  margin-top: var(--mm-space-5);
  padding-top: var(--mm-space-3);
  border-top: 1px solid hsl(var(--mm-border));
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

/* One column when there is no room for two — the note field is the one that
   suffers most from being squeezed, and it is the one that must get written. */
@media (max-width: 40rem) {
  .mm-work-differed-body {
    grid-template-columns: minmax(0, 1fr);
  }
}


/* ---------------------------------------------------------------------------
   Proportional widths, without an inline style attribute.

   The enforced CSP is `style-src 'self' 'nonce-…'`, and a nonce whitelists
   `<style>` ELEMENTS — it does nothing for `style=` ATTRIBUTES. So every
   `style="width: {{ pct }}%"` in a template was dropped by the browser in
   production. Measured in headless Chrome against that exact policy: the element
   rendered at its parent's full width.

   That is worse than rendering empty. A goal 22.5% funded displayed as a
   COMPLETELY FULL bar — the app telling you that you had reached a savings target
   you were a fifth of the way to. It was invisible in development because dev
   runs CSP_REPORT_ONLY, so the policy reports and does not enforce.

   The fills below default to `width: 0`, so a percentage that does not resolve to
   a class renders as nothing rather than as everything. Under-reporting progress
   is recoverable; over-reporting it is what this bug did.
   --------------------------------------------------------------------------- */

.mm-meter-fill,
.mm-diverge-bar,
.mm-goal-progress-fill {
  width: 0;
}

/* The planner's proportion bars, same rule and same reason — in a block of their own
   because the group above is matched verbatim by
   moneymap/tests/test_no_inline_style_attributes.py, and because the *order* is the
   whole point: a `.mm-w-57` utility and a `.mm-wf-bar-fill` rule are both (0,1,0), so
   whichever is written later in this file wins. The default has to lose, which means it
   has to come first. Written the other way round the fills rendered at zero on every
   row — the safe direction of the same mistake, and still a screen saying nothing.

   `.mm-claim-ceiling-mark` is in here too, and it is the one member of this list whose
   width is not a fill: it is an invisible spacer whose RIGHT EDGE is the ceiling on the
   assigned meter. A ceiling that failed to resolve would default to the full track and
   draw the wall hard against the right-hand end — "you may give away 100% of this
   paycheck", which is the same over-permissive failure in a different costume. Zero puts
   it against the left instead: "there is nothing left to give", which is the reading a
   member can only act on by looking. */
.mm-wf-bar-fill,
.mm-claim-fill,
.mm-claim-ceiling-mark {
  width: 0;
}

.mm-w-0 { width: 0%; }
.mm-w-1 { width: 1%; }
.mm-w-2 { width: 2%; }
.mm-w-3 { width: 3%; }
.mm-w-4 { width: 4%; }
.mm-w-5 { width: 5%; }
.mm-w-6 { width: 6%; }
.mm-w-7 { width: 7%; }
.mm-w-8 { width: 8%; }
.mm-w-9 { width: 9%; }
.mm-w-10 { width: 10%; }
.mm-w-11 { width: 11%; }
.mm-w-12 { width: 12%; }
.mm-w-13 { width: 13%; }
.mm-w-14 { width: 14%; }
.mm-w-15 { width: 15%; }
.mm-w-16 { width: 16%; }
.mm-w-17 { width: 17%; }
.mm-w-18 { width: 18%; }
.mm-w-19 { width: 19%; }
.mm-w-20 { width: 20%; }
.mm-w-21 { width: 21%; }
.mm-w-22 { width: 22%; }
.mm-w-23 { width: 23%; }
.mm-w-24 { width: 24%; }
.mm-w-25 { width: 25%; }
.mm-w-26 { width: 26%; }
.mm-w-27 { width: 27%; }
.mm-w-28 { width: 28%; }
.mm-w-29 { width: 29%; }
.mm-w-30 { width: 30%; }
.mm-w-31 { width: 31%; }
.mm-w-32 { width: 32%; }
.mm-w-33 { width: 33%; }
.mm-w-34 { width: 34%; }
.mm-w-35 { width: 35%; }
.mm-w-36 { width: 36%; }
.mm-w-37 { width: 37%; }
.mm-w-38 { width: 38%; }
.mm-w-39 { width: 39%; }
.mm-w-40 { width: 40%; }
.mm-w-41 { width: 41%; }
.mm-w-42 { width: 42%; }
.mm-w-43 { width: 43%; }
.mm-w-44 { width: 44%; }
.mm-w-45 { width: 45%; }
.mm-w-46 { width: 46%; }
.mm-w-47 { width: 47%; }
.mm-w-48 { width: 48%; }
.mm-w-49 { width: 49%; }
.mm-w-50 { width: 50%; }
.mm-w-51 { width: 51%; }
.mm-w-52 { width: 52%; }
.mm-w-53 { width: 53%; }
.mm-w-54 { width: 54%; }
.mm-w-55 { width: 55%; }
.mm-w-56 { width: 56%; }
.mm-w-57 { width: 57%; }
.mm-w-58 { width: 58%; }
.mm-w-59 { width: 59%; }
.mm-w-60 { width: 60%; }
.mm-w-61 { width: 61%; }
.mm-w-62 { width: 62%; }
.mm-w-63 { width: 63%; }
.mm-w-64 { width: 64%; }
.mm-w-65 { width: 65%; }
.mm-w-66 { width: 66%; }
.mm-w-67 { width: 67%; }
.mm-w-68 { width: 68%; }
.mm-w-69 { width: 69%; }
.mm-w-70 { width: 70%; }
.mm-w-71 { width: 71%; }
.mm-w-72 { width: 72%; }
.mm-w-73 { width: 73%; }
.mm-w-74 { width: 74%; }
.mm-w-75 { width: 75%; }
.mm-w-76 { width: 76%; }
.mm-w-77 { width: 77%; }
.mm-w-78 { width: 78%; }
.mm-w-79 { width: 79%; }
.mm-w-80 { width: 80%; }
.mm-w-81 { width: 81%; }
.mm-w-82 { width: 82%; }
.mm-w-83 { width: 83%; }
.mm-w-84 { width: 84%; }
.mm-w-85 { width: 85%; }
.mm-w-86 { width: 86%; }
.mm-w-87 { width: 87%; }
.mm-w-88 { width: 88%; }
.mm-w-89 { width: 89%; }
.mm-w-90 { width: 90%; }
.mm-w-91 { width: 91%; }
.mm-w-92 { width: 92%; }
.mm-w-93 { width: 93%; }
.mm-w-94 { width: 94%; }
.mm-w-95 { width: 95%; }
.mm-w-96 { width: 96%; }
.mm-w-97 { width: 97%; }
.mm-w-98 { width: 98%; }
.mm-w-99 { width: 99%; }
.mm-w-100 { width: 100%; }


/* The commitments panel's status line — see obligations_panel.html. Empty until a
   submit fills it, so it takes no space on arrival. */
/* The add-line card's status line. Empty until `views.line_add` swaps a reason
   into it, so it takes no room on a page nobody has submitted yet. Its text is
   always a rejection, so it carries the danger colour the field errors use. */
.mm-add-line-status:empty {
  display: none;
}

.mm-add-line-status {
  margin: var(--mm-space-2) 0 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-danger));
}

.mm-obligations-status:empty {
  display: none;
}

.mm-obligations-status {
  margin: var(--mm-space-2) 0 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-obligations-status--good {
  color: hsl(var(--mm-success-text));
}

/* ==========================================================================
   The planner — one screen for planning
   ==========================================================================

   Two columns: what you tell it on the left, what it makes of that on the
   right. The right column is sticky, because its whole job is to stay in front
   of you while you type into the left one.

   Every colour goes through hsl(). The tokens are bare HSL triplets so the alpha
   form composes, which makes an unwrapped one silently invalid CSS that renders
   no border and no background at all — guarded by
   moneymap/tests/test_css_tokens_are_wrapped.py, and it has shipped before. */

.mm-plan-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--mm-space-3);
  margin-bottom: var(--mm-space-5);
}

.mm-plan-head-text {
  min-width: 0;
}

.mm-plan-title {
  margin: 0;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-2xl);
  line-height: var(--mm-leading-tight);
  letter-spacing: var(--mm-tracking-tight);
  overflow-wrap: anywhere;
}

.mm-plan {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--mm-space-5);
  align-items: start;
}

/* On the ITEMS, never the container. A grid item defaults to `min-width: auto`
   — "never narrower than my content" — so one long destination name sets the
   floor for the whole document, and in a grid that overflow propagates all the
   way out to it. */
.mm-plan > * {
  min-width: 0;
}

.mm-plan-inputs {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-4);
  min-width: 0;
}

.mm-plan-card {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-4);
  min-width: 0;
}

.mm-plan-card-title {
  margin: 0;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-lg);
  line-height: var(--mm-leading-snug);
}

.mm-plan-card-hint {
  margin: 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
}

.mm-plan-field {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-1);
  min-width: 0;
}

.mm-plan-save {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--mm-space-2);
}

@media (min-width: 60rem) {
  /* Full width under both columns. Stacked it is simply last, which is where a
     Save belongs relative to the thing it saves. */
  .mm-plan-save {
    grid-column: 1 / -1;
  }
}

/* -- Gross to net, behind one disclosure --------------------------------- */

.mm-plan-gross {
  padding: var(--mm-space-3);
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  background-color: hsl(var(--mm-surface));
}

.mm-plan-gross-summary {
  cursor: pointer;
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-medium);
  color: hsl(var(--mm-text-muted));
}

.mm-plan-gross-body {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-3);
  margin-top: var(--mm-space-3);
}

.mm-plan-rates {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: var(--mm-space-3);
}

.mm-plan-rates > * {
  min-width: 0;
}

/* The shared money field renders its figure at display size, which is right for
   the two amounts this screen leads with and far too loud for a tax rate. */
.mm-money-input--percent .mm-input {
  font-size: var(--mm-text-base);
  text-align: right;
}

.mm-plan-derived {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-1);
  margin: 0;
  padding-top: var(--mm-space-2);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-plan-derived > div {
  display: flex;
  justify-content: space-between;
  gap: var(--mm-space-3);
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
}

.mm-plan-derived dt,
.mm-plan-derived dd {
  margin: 0;
  min-width: 0;
}

.mm-plan-derived dd {
  font-variant-numeric: tabular-nums;
}

.mm-plan-derived-total {
  padding-top: var(--mm-space-1);
  border-top: var(--mm-border-width) solid hsl(var(--mm-border));
  font-weight: var(--mm-weight-bold);
  color: hsl(var(--mm-text));
}

/* -- Commitments: a plain row with an amount, and no checkbox anywhere ---- */

.mm-plan-commitments {
  display: flex;
  flex-direction: column;
}

.mm-plan-commit-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--mm-space-3);
  align-items: center;
  padding: var(--mm-space-2) 0;
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-plan-commit-row:last-child {
  border-bottom: 0;
}

.mm-plan-commit-name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--mm-space-2);
  min-width: 0;
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-snug);
  overflow-wrap: anywhere;
}

.mm-plan-commit-input {
  width: 8rem;
}

.mm-plan-commit-input .mm-input {
  font-size: var(--mm-text-sm);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* -- The waterfall ------------------------------------------------------- */

.mm-wf {
  min-width: 0;
  padding: var(--mm-space-4);
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-lg);
  box-shadow: var(--mm-shadow-sm);
}

.mm-wf-title {
  margin: 0;
  font-family: var(--mm-font-display);
  font-size: var(--mm-text-lg);
  line-height: var(--mm-leading-snug);
}

.mm-wf-empty {
  margin: var(--mm-space-3) 0 0;
  font-size: var(--mm-text-sm);
  color: hsl(var(--mm-text-muted));
  /* It renders a verdict, and a verdict names destinations the member named. */
  overflow-wrap: break-word;
}

.mm-wf-section {
  margin: var(--mm-space-4) 0 0;
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-bold);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-muted));
}

/* THREE columns — what this row is, what it moved, and what is left after it —
   and every row renders all three even when the last two are empty. A flex row
   with space-between stops aligning the moment one row has a remainder and the
   next does not; and because each row is its OWN grid, the columns line up down
   the page only because the two figure cells carry a min-width. A row that
   dropped its remainder cell would pull its amount out of line with every other
   row on the page, which is why the template emits it empty rather than
   conditionally. */
.mm-wf-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--mm-space-2);
  align-items: center;
  padding: var(--mm-space-2) 0;
  border-bottom: var(--mm-border-width) solid hsl(var(--mm-border));
}

.mm-wf-row:last-child {
  border-bottom: 0;
}

.mm-wf-label {
  min-width: 0;
  font-size: var(--mm-text-sm);
  line-height: var(--mm-leading-snug);
  overflow-wrap: anywhere;
}

/* Detail rows sit under their subtotal and are SMALLER than it. The size
   difference is what makes the column read as arithmetic rather than a list. */
.mm-wf-label--indent {
  padding-left: var(--mm-space-3);
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
}

/* Muted, because most second lines are an *explanation* and not a warning. "What the
   account reads once every transfer clears" in warning amber, under every plan, is a
   screen crying wolf on its own arithmetic — and it makes the two lines that really are
   warnings indistinguishable from the six that are not. Those carry `--warn`. */
.mm-wf-note {
  display: block;
  font-size: var(--mm-text-xs);
  /* Set, not inherited: the result row's label is bold, and a bold explanatory line
     under a bold total reads as a second total. */
  font-weight: var(--mm-weight-normal);
  color: hsl(var(--mm-text-muted));
  overflow-wrap: anywhere;
}

.mm-wf-note--warn {
  color: hsl(var(--mm-warning-text));
}

.mm-wf-note--edited {
  color: hsl(var(--mm-accent-text));
}

/* The row's own name and its category chip, on one line and wrapping together.
   Baseline-aligned rather than centred: the chip is a size down, and centring it
   against a heading leaves the two words sitting on different lines to the eye. */
.mm-wf-headline {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--mm-space-2);
}

/* ---- The category chip -------------------------------------------------------
   How a category stays legible on a page ordered by FUNDING. The rows may not be
   re-grouped — a running remainder read down a re-ordered page is a column of
   figures that do not follow from each other — so the category travels on the row
   instead, as a word in the same hue as the bar underneath it. Both come from the
   row's `kind` in plan_screen.py, so the word and the colour cannot drift apart.

   THE HUE IS A BACKGROUND, NEVER THE TEXT COLOUR. The series ramp is validated for
   a 4px bar, not for 12px type: `--mm-series-6` is #5A8A00, which is 4.09:1 on
   white — under AA for small text — and `--mm-series-5` violet is worse. A tint at
   14% carries the same categorical signal and leaves the type in the page's own
   muted ink, which is already the contrast every other second line is read at.

   Unmodified is the neutral chip, matching `.mm-wf-bar-fill`'s own default: rows
   with no direction do not get a colour, here or there. */
.mm-wf-cat {
  display: inline-block;
  padding: 0 var(--mm-space-2);
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-text-subtle) / 0.16);
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-normal);
  line-height: 1.6;
  color: hsl(var(--mm-text-muted));
  white-space: nowrap;
}

.mm-wf-cat--out {
  background-color: hsl(var(--mm-series-2) / 0.18);
}

.mm-wf-cat--held {
  background-color: hsl(var(--mm-series-5) / 0.18);
}

.mm-wf-cat--save {
  background-color: hsl(var(--mm-series-6) / 0.18);
}

.mm-wf-cat--stay {
  background-color: hsl(var(--mm-series-4) / 0.18);
}

.mm-wf-input {
  width: 6rem;
  min-height: 2.25rem;
  font-size: var(--mm-text-sm);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The proportional bar. It sits under the label rather than in a column of its own so the
   row keeps its three-column rhythm — label, optional input, figure — whether or not it is
   editable.

   The fill's width is a `.mm-w-N` class and can be nothing else. `style="width: 56%"` is
   dropped outright by the enforced CSP, and a dropped width on a block inside a track
   renders it FULL: the bar would say a bill took the whole paycheck. Failing to zero is
   recoverable; failing to full is the bug that reported an unreached savings goal as
   reached. */
/* Neutral grey by default, and the default is now what the *subtotals* wear. A bar with
   no `--flow` modifier is a row where nothing moves — "After bills", "Planning pool" —
   and `--mm-text-subtle` is the tone the goal meters already use for "no judgement", in
   both themes. Not the accent, ever: this app reserves matador red for the one accent and
   for danger, and twenty red bars down a column of ordinary allocations reads as twenty
   alarms. The track is the same colour at 18% so the bar reads across its whole length
   rather than only where it ends, matching `.mm-meter`.

   `width` is deliberately absent here: it is declared once, further up, *before* the
   `.mm-w-*` utilities, because same-specificity rules are decided by source order. */
.mm-wf-bar {
  display: block;
  height: 4px;
  margin-top: var(--mm-space-1);
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-text-subtle) / 0.18);
  overflow: hidden;
}

/* Quieter than any flow colour, and that is the whole job. At full strength this is the
   LOUDEST bar in the column — `--mm-text-subtle` is #AFAFB6 on dark, brighter than every
   step of the series ramp — so the two rows carrying no direction shouted over the ten
   that do. Worse, at 100% width on a dark screen it was very nearly the deposit's own
   near-white bar sitting four rows below it, which is a subtotal impersonating the whole
   paycheck. Dimmed, the arithmetic recedes and the movement leads. */
.mm-wf-bar-fill {
  display: block;
  height: 100%;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-text-subtle) / 0.6);
}

/* ---- Flow: which way this row's money is going -------------------------------
   The member's verdict on the first build was that the paycheck surfaces were entirely
   grey — money leaving, money held back, money going to savings and money staying in
   checking all read identically. `paychecks/services/plan_screen.row_flow` decides which
   of those a row is; this is the only place the decision becomes a colour.

   FROM THE SERIES RAMP, NOT THE STATUS RAMP. A bill is not bad news. It is the plan
   working, and `--mm-danger` down a column of paid obligations would make every correct
   paycheck look like a failure. `--mm-series-*` exists for exactly this: categories that
   differ without ranking, validated as a set for lightness, chroma, CVD separation and
   contrast in both modes (see tokens.css and docs/data-series-palette.md).

   TWO SLOTS ARE UNUSABLE HERE and are skipped rather than assigned in order:

     * `--mm-series-1` is #D13D44, and `--mm-accent` is #E4252F and `--mm-danger` #CE272F.
       Same hue, four points of lightness apart. On a 4px bar it is the alarm colour.
     * `--mm-series-3` is #B7860B against `--mm-warning` #A3610A — and `--mm-warning` is
       what `--neg` below paints, on this exact bar, to mean "this row is the bad news".
       A category that looks like the warning, beside the warning, is the collision
       tokens.css was written to stop.

   The remaining four are bound one-to-one and permanently. They are never cycled and a
   flow never borrows another's slot, which is the part of "assign in fixed order" that
   matters: the same meaning is the same colour on every paycheck a member ever opens.

     out   series-2  #0076A8  azure   money leaving for a creditor
     stay  series-4  #00998C  teal    what checking reads once everything clears
     held  series-5  #8C5FB9  violet  the checking floor, released or held back
     save  series-6  #5A8A00  olive   money reaching a destination

   `deposit` takes `--mm-text` rather than a fifth series slot, and that is a decision and
   not a shortage. The deposit is the 100% every other bar is a share of; drawing the
   whole in the page's own ink and the parts in the ramp is what makes the column read as
   a division of one paycheck rather than as five unrelated categories. It is also the one
   row the member did not say was indistinguishable.

   Track *and* fill, because most of these bars are short: a bill at 3% of the deposit is
   three pixels of fill. The tinted track gives every row a lane in its own colour for its
   whole width, which is what the grey track above was already for. */

.mm-wf-bar--deposit {
  background-color: hsl(var(--mm-text) / 0.16);
}

.mm-wf-bar-fill--deposit {
  background-color: hsl(var(--mm-text));
}

.mm-wf-bar--out {
  background-color: hsl(var(--mm-series-2) / 0.2);
}

.mm-wf-bar-fill--out {
  background-color: hsl(var(--mm-series-2));
}

.mm-wf-bar--held {
  background-color: hsl(var(--mm-series-5) / 0.2);
}

.mm-wf-bar-fill--held {
  background-color: hsl(var(--mm-series-5));
}

.mm-wf-bar--save {
  background-color: hsl(var(--mm-series-6) / 0.2);
}

.mm-wf-bar-fill--save {
  background-color: hsl(var(--mm-series-6));
}

.mm-wf-bar--stay {
  background-color: hsl(var(--mm-series-4) / 0.2);
}

.mm-wf-bar-fill--stay {
  background-color: hsl(var(--mm-series-4));
}

/* Tone, and it is written LAST on purpose. `.mm-wf-bar-fill--save` and
   `.mm-wf-bar-fill--neg` are both single-class selectors, so whichever comes later in
   this file wins a tie — and the tie is real: a capped savings transfer is a `save` row
   that is also the one warning the waterfall makes. The template only ever emits one of
   the two, but the ordering is what makes that a belt rather than the only strap.
   Above, this rule sat before the flow rules and a capped Roth would have rendered in
   cheerful olive. */
.mm-wf-bar--neg {
  background-color: hsl(var(--mm-warning) / 0.2);
}

.mm-wf-bar-fill--neg {
  background-color: hsl(var(--mm-warning));
}

.mm-wf-heading {
  display: block;
}

/* What this row got, what it asked for and its share of the deposit — stacked and
   right-aligned so the decimal points line up down the column whatever is beside
   them. The min-width is what makes that true ACROSS rows: every row is its own
   grid, so nothing aligns by itself. It is sized for the widest thing this cell
   holds, which is now `of $1,234.56` rather than the amount. */
.mm-wf-figure {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  min-width: 6rem;
}

/* What the row asked for, printed directly under what it got so the two read as
   one comparison rather than as two facts. Quiet by default — on a plan that
   funds everything the two figures match and there is nothing to look at — and
   amber only on a row that came up short, which is the one case the pair exists
   to make visible before the second line is read. */
.mm-wf-ask {
  font-size: var(--mm-text-xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: hsl(var(--mm-text-muted));
}

.mm-wf-ask--short {
  color: hsl(var(--mm-warning-text));
  font-weight: var(--mm-weight-semibold);
}

/* ---- What is left after this row ---------------------------------------------
   The column the planner rebuild is for: the pot at the top, one claim per line,
   and beside each the balance still to be handed out. It is only truthful in
   funding order, which is why the rows are never re-grouped by category.

   Present on every row, EMPTY where the question does not apply — the deposit
   band is assembling the pot rather than spending it, and by the summary the
   handing out is over. An absent cell would shift that row's amount out of the
   column above it. */
.mm-wf-left {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  min-width: 5.5rem;
}

.mm-wf-left-amount {
  font-size: var(--mm-text-sm);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--mm-text-muted));
}

/* On its own line under the figure rather than beside it, so the numbers keep a
   column of their own and the word cannot push one out of line. */
.mm-wf-left-word {
  font-size: var(--mm-text-xs);
  line-height: 1.2;
  color: hsl(var(--mm-text-subtle));
}

.mm-wf-pct {
  font-size: var(--mm-text-xs);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--mm-text-muted));
}

/* `font-variant-numeric` directly, not `.mm-num`: that class is a table utility
   and carries `white-space: nowrap` along with it. */
.mm-wf-amount {
  min-width: 6rem;
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.mm-wf-amount--muted {
  font-weight: var(--mm-weight-normal);
  color: hsl(var(--mm-text-muted));
}

.mm-wf-row--total .mm-wf-label {
  font-size: var(--mm-text-sm);
  font-weight: var(--mm-weight-semibold);
  color: hsl(var(--mm-text));
}

.mm-wf-row--total .mm-wf-amount {
  font-weight: var(--mm-weight-bold);
}

.mm-wf-row--result {
  border-top: 2px solid hsl(var(--mm-border-strong));
  border-bottom: 0;
  padding-top: var(--mm-space-3);
}

.mm-wf-row--result .mm-wf-label {
  font-size: var(--mm-text-base);
  font-weight: var(--mm-weight-bold);
  color: hsl(var(--mm-text));
}

/* At phone width three columns of figures leave the label about seven characters
   a line, so the remainder drops to a line of its own beneath the row — still
   right-aligned, still a column the eye can run down, and now reading as one
   phrase ("$2,600.00 left") instead of a stack of two.

   `1 / -1` rather than a column number: the row is a three-column grid above this
   breakpoint and a two-column one below it, and naming the last line by index is
   how a later change to the template silently puts this cell in the wrong place. */
@media (max-width: 40rem) {
  .mm-wf-row {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .mm-wf-left {
    grid-column: 1 / -1;
    flex-direction: row;
    align-items: baseline;
    justify-content: flex-end;
    gap: var(--mm-space-1);
    min-width: 0;
  }

  /* Empty on the deposit and summary rows. It still holds a grid line, so the
     row's own `gap` opens a strip of white space under half the rows on the
     page. A server-set modifier and not `:empty`, which the newline Django
     leaves between the tags is enough to defeat. */
  .mm-wf-left--none {
    display: none;
  }
}

.mm-wf-alert {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-2);
  margin: var(--mm-space-3) 0 0;
  padding: var(--mm-space-3);
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-md);
  font-size: var(--mm-text-sm);
}

.mm-wf-alert-text {
  flex: 1 1 12rem;
  min-width: 0;
  margin: 0;
}

.mm-wf-alert--warning {
  color: hsl(var(--mm-warning-text));
  background-color: hsl(var(--mm-warning) / 0.1);
  border-color: hsl(var(--mm-warning) / 0.35);
}

.mm-wf-alert--danger {
  color: hsl(var(--mm-danger));
  background-color: hsl(var(--mm-danger) / 0.1);
  border-color: hsl(var(--mm-danger) / 0.35);
}

.mm-wf-alert--note {
  color: hsl(var(--mm-text-muted));
  background-color: hsl(var(--mm-surface));
}

/* -- Adjust draft allocations -------------------------------------------- */

/* The right-hand column, which is two regions rather than one: the computed
   waterfall, and the draft boxes below it. They are separate elements because a
   reply to a keystroke in a draft box must not rewrite that box (see
   templates/paychecks/_plan.html), and this wrapper is what keeps them reading as
   one column — it is the grid item, so the pair sticks together instead of the
   plan pinning itself over the table underneath it. */
.mm-plan-output {
  min-width: 0;
}

/* -- The two meters ------------------------------------------------------
   docs/design/priority-tiers.md, decision 2. Dollars committed to fixed claims, and
   percent-of-deposit assigned to percentage ones — the second WITH ITS CEILING DRAWN.

   NEUTRAL INK, NOT A SERIES SLOT, and that is a decision rather than a shortage. The
   four usable steps of `--mm-series-*` are bound one-to-one and permanently on this
   screen (`out` 2, `stay` 4, `held` 5, `save` 6) and slots 1 and 3 collide with
   `--mm-danger` and `--mm-warning`. A meter is not a fifth category anyway: the
   committed one sums bills, minimums and the floor, which are three of those colours at
   once, so borrowing any of them would say the total is one of its parts. It takes
   `--mm-text-subtle` — the same "no judgement" grey `.mm-wf-bar-fill` and the goal
   meters already default to — and the only colour on the meter is the tone, which is
   the one thing it is here to say.

   The card sits above the waterfall in the sticky column: two figures about the strategy,
   over the row-by-row account of what this paycheck does with it. */

/* The running "how much of 100% is spoken for" line, on the destinations list, the bills
   list and both destination forms. It carries no layout of its own beyond this gap: it is
   dropped into four different containers and inherits each one's rhythm, which is the only
   way one partial can sit in all four without a variant per host.

   `margin-block-start` rather than `margin-top` because it is a flow-relative gap above a
   block, and the surrounding pages already use logical properties.

   ⚠ Do not give this or its state paragraphs a `display`. The partial renders all four
   sentences and hides three with `hidden`, and an author `display` beats the UA's
   `[hidden] { display: none }` at every specificity — which is exactly how the Portfolio
   field on destination_create.html stayed visible for months, and why `.mm-field[hidden]`
   had to be added. All four sentences would render at once, saying contradictory things
   about the same number. */
.mm-share-budget {
  margin-block-start: var(--mm-space-4);
}

.mm-claims {
  display: flex;
  flex-direction: column;
  gap: var(--mm-space-4);
  min-width: 0;
  margin-bottom: var(--mm-space-4);
}

.mm-claim {
  min-width: 0;
}

/* Name on the left, figure on the right, on one baseline — so `$3,471.42` and `23.6%`
   line up down the card even though one is money and the other is a percentage. */
.mm-claim-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--mm-space-2);
  margin: 0 0 var(--mm-space-2);
  min-width: 0;
}

.mm-claim-name {
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-bold);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-muted));
}

/* `font-variant-numeric` directly rather than `.mm-num`: that class is a table utility
   and brings `white-space: nowrap` with it. */
.mm-claim-figure {
  font-size: var(--mm-text-lg);
  font-weight: var(--mm-weight-bold);
  font-variant-numeric: tabular-nums;
}

/* Taller than the waterfall's 4px bars, because this one has a wall drawn across it and
   a 2px rule on a 4px track is half the bar. `position: relative` is what the ceiling
   overlay is positioned against; `overflow: hidden` keeps a 100% fill inside the
   rounded ends, and the wall stays inside it because everything here is border-box. */
.mm-claim-track {
  position: relative;
  display: block;
  height: 10px;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-text-subtle) / 0.18);
  overflow: hidden;
}

/* `width` is deliberately absent: it is declared once, further up, BEFORE the `.mm-w-*`
   utilities, because same-specificity rules are settled by source order and the default
   has to lose. */
.mm-claim-fill {
  display: block;
  height: 100%;
  border-radius: var(--mm-radius-full);
  background-color: hsl(var(--mm-text-subtle) / 0.6);
}

/* The ceiling. Painted OVER the fill — later in the DOM, absolutely positioned — so a
   bar that has run past the wall still shows where the wall was, which is the whole
   sentence that meter is making. */
.mm-claim-ceiling {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

/* An invisible spacer whose RIGHT EDGE is the ceiling. The position comes from a
   `.mm-w-N` class on this element and can come from nothing else: the enforced CSP is
   `style-src 'self' 'nonce-…'` with no 'unsafe-inline', so `style="left: 23%"` is
   dropped by the browser and the wall would render hard against the left of the track. */
.mm-claim-ceiling-mark {
  display: block;
  height: 100%;
  border-right: 2px solid hsl(var(--mm-text));
}

.mm-claim-note {
  margin: var(--mm-space-2) 0 0;
  font-size: var(--mm-text-xs);
  line-height: var(--mm-leading-snug);
  color: hsl(var(--mm-text-muted));
  /* It quotes destinations the member named, and a phone-width column is narrow. */
  overflow-wrap: break-word;
}

.mm-claim-note--warn {
  color: hsl(var(--mm-warning-text));
}

/* Tone, scoped to the card and written last, so a plan asking for more than the deposit
   turns both meters at once. Reading one of them and not the other would be reading half
   a fact: an over-commitment is fixed and percentage claims together exceeding the
   deposit, and neither meter alone can say it.

   This is a STATUS colour on a data mark, which is normally the collision
   moneymap/tests/test_series_palette.py exists to stop — and it is the one legitimate
   crossover that file names: under a `.mm-tone-*` scope the colour IS the meaning. */
.mm-claims.mm-tone-warning .mm-claim-track {
  background-color: hsl(var(--mm-warning) / 0.2);
}

.mm-claims.mm-tone-warning .mm-claim-fill {
  background-color: hsl(var(--mm-warning));
}

/* The wall stays the page's own ink when the bar has crossed it. Painting it amber too
   would merge it into the fill it is supposed to be measured against, on the one plan
   where where it sits matters most. */
.mm-claims.mm-tone-warning .mm-claim-figure {
  color: hsl(var(--mm-warning-text));
}

/* The draft region has no box of its own on purpose: it is emitted even when
   there is no draft to show, so that a later out-of-band swap has an element to
   replace, and an empty container must take up no room. The spacing belongs to
   the card inside it. */
.mm-plan-draft > .mm-wf-editor {
  margin-top: var(--mm-space-4);
}

/* Its own card now, where it used to be a section under a rule inside the
   waterfall's card. Same chrome as .mm-wf, because it is the same kind of thing —
   a panel in the right-hand column — and the alternative was a bordered block
   floating on the page background. */
.mm-wf-editor {
  min-width: 0;
  padding: var(--mm-space-4);
  background-color: hsl(var(--mm-surface-raised));
  border: var(--mm-border-width) solid hsl(var(--mm-border));
  border-radius: var(--mm-radius-lg);
  box-shadow: var(--mm-shadow-sm);
}

.mm-wf-toggle {
  cursor: pointer;
  font-size: var(--mm-text-xs);
  font-weight: var(--mm-weight-bold);
  letter-spacing: var(--mm-tracking-wide);
  text-transform: uppercase;
  color: hsl(var(--mm-text-muted));
}

.mm-wf-toggle:hover {
  color: hsl(var(--mm-text));
}

.mm-wf-editor-hint {
  margin: var(--mm-space-2) 0;
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
}

.mm-wf-editor-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mm-space-2);
  margin-top: var(--mm-space-3);
}

/* A numeric column header, right-aligned over the figures beneath it. Not `.mm-num`:
   that one is scoped to `.mm-table` and brings `white-space: nowrap` with it, which is
   wrong for "Net deposit" over a phone-width column. */
.mm-wf-th {
  text-align: right;
}

.mm-wf-td-auto,
.mm-wf-td-draft {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.mm-wf-td-auto {
  color: hsl(var(--mm-text-muted));
}

/* On the span inside the cell, never on the cell. Auto table layout leaves a `<td>`'s
   own width constraints undefined, so `min-width: 0` there reads as a constraint and is
   not one; `overflow-wrap: anywhere` on a block *inside* it does shrink the column's
   min-content floor, which is what a long destination name needs. */
.mm-wf-dest {
  display: block;
  min-width: 0;
  overflow-wrap: anywhere;
}

.mm-wf-table .mm-wf-input {
  width: 6.5rem;
}

/* -- Save, and what is already saved ------------------------------------- */

.mm-plan-history {
  margin-top: var(--mm-space-6);
}

.mm-plan-commit-label {
  font-weight: var(--mm-weight-medium);
  overflow-wrap: anywhere;
}

.mm-plan-commit-context {
  font-size: var(--mm-text-xs);
  color: hsl(var(--mm-text-muted));
  overflow-wrap: anywhere;
}

@media (min-width: 60rem) {
  .mm-plan {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  }

  /* Sticky only once there is a column beside it to stay level with. Stacked,
     the waterfall follows the inputs and pinning it would cover them.

     On the wrapper rather than on `.mm-wf` itself: the column is two regions now,
     and sticking only the first of them would pin the plan on top of the draft
     table as that scrolled up underneath it. */
  .mm-plan-output {
    position: sticky;
    top: var(--mm-space-4);
    align-self: start;
  }
}

/* ============================================================================
   ICONS AND MOTION
   ----------------------------------------------------------------------------
   Added by the brand/design pass. Everything above this line predates it.

   Two things live here because they are one thing: an icon is the smallest piece
   of the visual system that can move, and both were previously improvised — icons
   as Unicode characters, motion as four tokens nothing referenced.

   See docs/brand/README.md. Nothing in this section may use a `style=` attribute,
   a raw colour, or an unwrapped token; see the guards in moneymap/tests/.
   ========================================================================== */

/* ---- Icon primitives ------------------------------------------------ */

/* The sprite itself. `hidden` already removes it from layout and from the
   accessibility tree, but a stylesheet that loads late must not let it flash as
   thirty stacked symbols first, and `display: none` on the <svg> does not stop
   <use> resolving into it. */
.mm-icon-sprite {
  display: none;
}

/* An icon is sized in `em`, not in pixels, so it tracks the type it sits beside.
   A 16px icon next to 14px small-caps and the same icon next to a 20px heading
   are two different relationships; one number cannot serve both, and hard-coding
   a `width` attribute per call site is how an icon set drifts.

   `vertical-align` rather than a flex parent: an icon appears inside running
   text, inside buttons and inside grid cells, and only the first of those can be
   relied on to centre it. -0.125em puts the optical centre of a square box on the
   x-height midline of the text beside it. */
/*
   Size comes through a custom property rather than from `width` directly, and
   that is a correctness fix rather than a style. This section is appended at the
   end of the file, so `.mm-icon` is the *last* single-class rule in it — and every
   other single-class selector that wants a different size (.mm-tone-icon,
   .mm-account-caret) is (0,1,0) too and declared thousands of lines above. Source
   order decides, `.mm-icon` wins, and every one of them silently rendered at
   1.15em: the account caret came out half again its intended size and the verdict
   chip shrank from 20px to 18.4px. That is the collision
   moneymap/tests/test_css_dead_declarations.py exists to name, reintroduced by an
   append.

   A custom property does not collide, because the two rules are then setting
   different properties. A context sets --mm-icon-size; .mm-icon reads it wherever
   it is declared in the file.
*/
.mm-icon {
  display: inline-block;
  width: var(--mm-icon-size, 1.15em);
  height: var(--mm-icon-size, 1.15em);
  flex: none;
  vertical-align: -0.125em;
  /* Presentation attributes on the <symbol> set fill to currentColor; making the
     inheritance explicit here means a container's `color:` is the one control for
     an icon's colour, in the same way it is for the text around it. */
  fill: currentColor;
  /* An icon is never the thing a pointer is aimed at — the link or button around
     it is — and a pointer event landing on the SVG makes `:hover` on the parent
     flicker as the cursor crosses the boundary. */
  pointer-events: none;
}

.mm-icon--sm {
  --mm-icon-size: 0.9em;
}

.mm-icon--lg {
  --mm-icon-size: 1.5em;
}

/* A control that is a fixed box — the 44px theme toggle — sizes its icon in rem,
   not em. `<button>` does not inherit the page's type: the user-agent sheet hands
   it about 13px, so `1.15em` resolved to a 15px moon adrift in a 44px square,
   which is the same defect the old ☾ glyph had for the same reason. An em is
   right for an icon beside text and wrong for an icon inside a box. */
.mm-theme-toggle .mm-icon {
  --mm-icon-size: 1.25rem;
}

/* An icon beside a label needs a gap, and the call site declares it.
   Two selector-only attempts failed, both silently:

   * `.mm-icon + *` — in `{% icon "arrow-left" %}{% translate "Portfolios" %}` the
     next sibling is a *text node*, which no selector can reach, so every back
     link rendered as "←Portfolios" with the glyph touching the word.
   * `.mm-icon:first-child:not(:only-child)` — in that same markup the <svg> is
     the only *element* child, because text nodes are not children for
     `:only-child` either. The guard meant to protect icon-only buttons excluded
     precisely the case it was written for.

   There is no selector for "this icon is followed by text". So the template says
   so. An explicit modifier is also the honest interface: an icon leading a label,
   an icon trailing a label and an icon alone in a button are three different
   components, and the first two are the ones that need spacing. */
.mm-icon--lead {
  margin-right: 0.4em;
}

.mm-icon--trail {
  margin-left: 0.4em;
}

/* ---- Motion vocabulary ---------------------------------------------- */
/*
   Four movements, and a rule for each about what it is allowed to say.

     enter   content that was not there is now there
     exit    content that was there is going
     settle  a value or state changed in place
     press   the interface received your input

   Everything animates `transform` and `opacity` only. Both are composited, so
   neither costs a layout pass — and more importantly, a movement that changes
   layout cannot be interrupted safely, and every movement here can be interrupted,
   because a person typing an amount into the planner will interrupt all of them.

   There is no decorative motion in this file. Nothing fades because a fade is
   nice; every rule below names the state change it exists to explain.
*/

@keyframes mm-enter {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}

/* A bar filling. scaleX rather than width: the width is already correct — it
   comes from a .mm-w-* class, because the CSP drops the `style="width:22.5%"`
   attribute this app once shipped, which rendered a goal 22.5% funded as a full
   bar. So the fill is drawn at its true width and scaled up from nothing, which
   runs on the compositor and cannot desync from the figure beside it. */
@keyframes mm-fill {
  from {
    transform: scaleX(0);
  }
}

@keyframes mm-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ---- enter: htmx swaps ---------------------------------------------- */
/*
   Opt-in, by class, and that is the whole design.

   htmx puts `.htmx-added` on new nodes for the length of the settle delay, so a
   region that declares a transition animates as the class is removed. It is
   tempting to apply that to every swap target at once. It is wrong here: the
   planning screen re-requests its preview on every keystroke, and a column that
   fades in four times a second while someone types an amount is not feedback, it
   is a strobe. So a template opts a region in when the swap is an event — a row
   added, a list reloaded, a panel replaced — and leaves it off when the swap is a
   recalculation of something already on screen.

   For a recalculation use .mm-stale below instead: the question there is not
   "what is new" but "is what I am reading current".
*/
.mm-enter {
  transition:
    opacity var(--mm-duration-base) var(--mm-ease-out),
    transform var(--mm-duration-base) var(--mm-ease-out);
}

.mm-enter.htmx-added {
  opacity: 0;
  transform: translateY(4px);
}

/* ---- settle: a region being recalculated ---------------------------- */
/*
   The delay is the feature. `transition-delay` is read from the state being
   transitioned *to*, so dimming waits --mm-stale-after and undimming does not: a
   reply that beats the delay never dims at all, and a reply that does not returns
   to full the moment it lands.

   Symmetric, this would be worse than no animation. Every keystroke would cost a
   visible dip even when the server answered in 30ms, and a slow reply would come
   back as lazily as it went.
*/
.mm-stale {
  opacity: 1;
  transition: opacity var(--mm-duration-fast) var(--mm-ease-out);
}

.mm-stale.htmx-request {
  opacity: 0.55;
  transition-delay: var(--mm-stale-after);
}

/* ---- enter: the conclusion ------------------------------------------ */
/*
   Every surface in this app opens with a verdict — that is the product's thesis,
   not a layout choice. It is the one element worth a beat of movement on load,
   because it is the one element the page exists to deliver.

   Staggered when there is more than one, so three findings read as three findings
   rather than as a block arriving. `nth-child` rather than a delay passed from
   markup, because a per-card delay would need a `style=` attribute and the CSP
   drops those.

   Capped at four steps. Past that the last card waits longer than the page took
   to load, which is where a stagger stops being polish.
*/
.mm-lens-verdict,
.mm-verdicts > * {
  animation: mm-enter var(--mm-duration-slow) var(--mm-ease-out) both;
}

.mm-verdicts > *:nth-child(2) {
  animation-delay: var(--mm-stagger);
}

.mm-verdicts > *:nth-child(3) {
  animation-delay: calc(var(--mm-stagger) * 2);
}

.mm-verdicts > *:nth-child(n + 4) {
  animation-delay: calc(var(--mm-stagger) * 3);
}

/* ---- settle: bars ---------------------------------------------------- */
/*
   A progress bar that is simply *there* on load asserts a number. The same bar
   growing to it shows the number being measured, which is what a goal's funded
   percentage actually is. One shot, on paint.
*/
.mm-goal-progress-fill,
.mm-meter-fill {
  transform-origin: left center;
  animation: mm-fill var(--mm-duration-slow) var(--mm-ease-out) both;
}

/* The diverging bar reads outward from a centre line — right of centre is over
   plan, left is under. Each half is absolutely positioned against that line
   (`right: 50%` / `left: 50%`), so its origin is the edge it is anchored by, not
   its own middle: a centre origin would grow it in both directions and push it
   across the axis it is measured from, briefly claiming the opposite sign. */
.mm-diverge-bar {
  animation: mm-fill var(--mm-duration-slow) var(--mm-ease-out) both;
}

.mm-diverge-bar--under {
  transform-origin: right center;
}

.mm-diverge-bar--over {
  transform-origin: left center;
}

/* ---- enter: menus and disclosures ------------------------------------ */
/*
   The account menu is <details>/<summary> — chosen because the CSP forbids inline
   handlers and the platform gives the disclosure for free. The cost is that its
   open state is not transitionable: the browser flips `display`, and there is
   nothing to interpolate between. An animation on the panel does work, because
   the panel enters the layout at that moment.

   Spring easing here and nowhere near a number. A small overshoot suits a surface
   that popped out of the control that opened it; on anything showing money it
   would mean displaying, for a few frames, a figure that is not the one intended.
*/
.mm-account[open] .mm-account-menu {
  animation: mm-enter var(--mm-duration-fast) var(--mm-ease-spring) both;
  transform-origin: top right;
}

/* ---- press ------------------------------------------------------------ */
/*
   Contact feedback. Scoped to a fine pointer: on touch the press is already
   confirmed by the finger covering the control, and a scale under it only moves
   the target while it is being hit.
*/
@media (hover: hover) and (pointer: fine) {
  .mm-btn,
  .mm-account-item {
    transition:
      background-color var(--mm-duration-fast) var(--mm-ease-out),
      border-color var(--mm-duration-fast) var(--mm-ease-out),
      transform var(--mm-duration-instant) var(--mm-ease-out);
  }

  .mm-btn:active {
    transform: scale(0.97);
  }
}

/* ---- the spinner ------------------------------------------------------ */
/*
   The only continuous animation in the app, and the only place a linear curve is
   right: an eased spinner appears to stall twice a turn.
*/
.mm-icon--spin {
  animation: mm-spin 800ms linear infinite;
}

/* ---- reduced motion --------------------------------------------------- */
/*
   The global reset earlier in this file collapses every duration, which handles
   the transitions and leaves the one-shot animations on their final frame. These
   are the two cases it does not reach, because neither is a duration: a spinner
   still turning once every 0.01ms, and a stagger whose delays outlive a zeroed
   duration and would hold the third verdict card invisible for 90ms.
*/
@media (prefers-reduced-motion: reduce) {
  .mm-icon--spin {
    animation: none;
  }

  .mm-verdicts > * {
    animation-delay: 0ms;
  }
}


/* ---- The sign on a money figure ---------------------------------------------
   Emitted by `money` / `signed_money` (accounts/templatetags/mm_money.py), which
   wrap the leading + or - in this class and nothing else.

   Money columns are `font-variant-numeric: tabular-nums` so a column of figures
   lines up on its decimal point. Under `tnum` Inter gives the hyphen a full DIGIT
   advance - 6.52px to 9.06px at 14px/600, a surcharge of 2.54px - which reads as a
   gap between the sign and the dollar: `-  $1,150.00`. All 36 of Inter's OpenType
   features were measured against it; not one changes it, and `tnum` alone
   reproduces it.

   The sign is the one character in the string whose width cannot disturb the
   column: these figures are right-aligned, so the decimal points are held from the
   right and the sign sits at the ragged left edge. Exempting it costs nothing that
   `tnum` is there to buy.

   `::first-letter` was the CSS-only alternative and it is why this needs markup at
   all: it selects correctly in Blink, but on a string with no sign it takes the
   leading digit AND its comma, because a digit is a letter unit per CSS Text. An
   engine reading the spec would then render that digit proportionally and leave the
   column ragged by ~3px a row. A span only exists when there is a sign to wrap.

   Invisible to `scripts/shoot_pages.py`, which renders from file:// with the
   webfont dropped in CORS - the surcharge is 0.00px in the fallback stack, so both
   the defect and any regression review as clean in a screenshot. Verified in a real
   browser against the dev server instead. */
.mm-minus {
  font-variant-numeric: normal;
}
