/* Integers — project styles layered on top of Bootstrap.
   Bootstrap handles layout/utilities; this file supplies the brand
   identity (palette, type, the wordmark) and a few component treatments. */

/* Self-hosted variable fonts (Latin subsets, served from /static/fonts —
   no third-party font CDN at runtime). Fraunces is variable on opsz and
   wght with SOFT/WONK pinned to 0; Inter on opsz and wght. */
@font-face {
  font-family: "Fraunces";
  src: url("/static/fonts/fraunces-latin-var.woff2?v=2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Inter";
  src: url("/static/fonts/inter-latin-var.woff2?v=2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --brand: #1f6f5c;
  --brand-dark: #164f42;
  --brand-light: #6fb3a1;
  --brand-soft: #e7f1ee;
  --ink: #1a2b28;
  --muted: #6b7d78;
  --paper: #fbfaf7;
}

body {
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--ink);
  background: var(--paper);
}

/* No underlined links anywhere, in any state, unless Chris asks for one
   in a specific place (his rule, 2026-09-18). A link shows it is a link
   by colour, and reacts to hover by deepening it. */
a,
a:hover,
a:focus,
.btn-link,
.btn-link:hover,
.btn-link:focus {
  text-decoration: none;
}

.font-display {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
}

/* The Integers lockup (mark + wordmark, an SVG in pkg/static/images).
   The file's own proportions are kept; the caller sets only the height,
   via .lockup-header / .lockup-hero. */
.lockup {
  display: block;
  width: auto;
}

.lockup-header {
  height: 30px;
}

.lockup-hero {
  height: 56px;
  margin-inline: auto;
}

/* Bootstrap's default placeholder color is nearly as dark as real input
   text, so a placeholder like "67" reads as an already-filled field. Lighten
   it so it's clearly a hint, not a value. */
.form-control::placeholder {
  color: #a6b3ae;
  opacity: 1;
}

/* A number input's spin-button UI reserves its width even while hidden
   (only shown on hover), silently shrinking the visible text area. On a
   narrow field (e.g. "As of year") that's enough to clip the last character
   of a 4-digit placeholder even though the box itself looks wide enough.
   None of these fields benefit from tiny up/down arrows, so remove them and
   reclaim the space everywhere. */
input[type="number"].form-control::-webkit-outer-spin-button,
input[type="number"].form-control::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"].form-control {
  -moz-appearance: textfield;
}

/* Brand color helpers (parallel Bootstrap's text-*and /bg-* utilities) */
.text-brand {
  color: var(--brand) !important;
}

.bg-brand {
  background: var(--brand) !important;
}

.bg-brand-soft {
  background: var(--brand-soft) !important;
}

/* Brand button variant */
.btn-brand {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.btn-brand:hover,
.btn-brand:focus {
  background: var(--brand-dark);
  border-color: var(--brand-dark);
  color: #fff;
}

/* Cards / chips */
.hero-card {
  border: 1px solid #ece7dd;
  border-radius: 1rem;
  box-shadow: 0 10px 40px -18px rgba(22, 79, 66, 0.35);
}

/* ---- Platform header: sticky, thin, identical on every page ---- */
.platform-header {
  position: sticky;
  top: 0;
  z-index: 1030;
  background: #fff;
  border-bottom: 1px solid #e4e9e7;
}

/* Header and footer run the full width of the screen and share one
   gutter, so the logo sits at the far left, the account menu at the
   far right, and the footer's two lines on the same edges. Content
   areas choose their own width below (see .dashboard-stage). */
.platform-header-inner,
.platform-footer-inner {
  padding: 0 1.5rem;
}

.platform-header-inner {
  height: 60px;
  display: flex;
  align-items: center;
  gap: 1rem;
}

.platform-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--ink);
}

.platform-brand img {
  display: block;
}

/* The tagline sits beside the lockup behind a grey rule, on every page,
   quiet enough not to compete with it; hidden on narrow screens. The
   pair is centered on each other's height. */
.platform-brand-group {
  display: inline-flex;
  align-items: center;
  gap: 1rem;
}

.platform-tagline {
  font-size: 0.85rem;
  line-height: 1.25;
  color: var(--muted);
  padding-left: 1rem;
  border-left: 1px solid var(--muted);
}

/* ---- Signed-out landing page ---- */
/* The landing page is held to one centered column — header, content, and
   footer alike (layout.Landing sets .layout-fixed on <body>) — where the
   signed-in layout runs edge to edge, so the two read as different
   places. */
.layout-fixed .platform-header-inner,
.layout-fixed .platform-footer-inner {
  max-width: 76rem;
  margin-inline: auto;
}

/* The header's Sign in link: ink, not link-blue, beside the brand button. */
.landing-signin {
  color: var(--ink);
  font-weight: 500;
  text-decoration: none;
}

.landing-signin:hover,
.landing-signin:focus {
  color: var(--brand);
}

.app-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 0.6rem;
  background: var(--brand-soft);
  color: var(--brand);
  flex-shrink: 0;
  font-size: 1.05rem;
}

.account-menu {
  min-width: 14rem;
}

@media (max-width: 575.98px) {
  .platform-header-inner,
  .platform-footer-inner {
    padding: 0 0.9rem;
  }

  .platform-header-inner {
    gap: 0.6rem;
  }

  .lockup-header {
    height: 26px;
  }
}

/* ---- No-access page (a feature the account doesn't include) ---- */
.launcher-shell {
  max-width: 52rem;
}

.launcher-note {
  max-width: 28rem;
}

.eyebrow {
  letter-spacing: 0.14em;
  font-size: 0.72rem;
}

/* Google sign-in button — follows Google's white-button brand style.
   Width capped per Google's Sign In branding guidelines (175–400px). */
.btn-google {
  background: #fff;
  border: 1px solid #dadce0;
  color: #3c4043;
  font-weight: 500;
  max-width: 320px;
}

.btn-google:hover,
.btn-google:focus {
  background: #f8f9fa;
  border-color: #d2d3d5;
  color: #3c4043;
}

/* Sign-in page (layout.Bare): one centered column on a soft gradient —
   a wash of brand green from the top, paper fading to a cooler grey-green
   at the foot — so the page has some depth without any chrome. */
.layout-auth {
  background:
    radial-gradient(70rem 36rem at 50% -8rem, var(--brand-soft) 0%, rgba(231, 241, 238, 0) 70%),
    linear-gradient(180deg, var(--paper) 0%, #eef2f0 100%);
}

.auth-main {
  padding: 3rem 1.5rem;
}

.auth-shell {
  max-width: 26rem;
}

/* The logo on the sign-in page is a link back to the landing page. */
.auth-brand {
  display: inline-block;
  text-decoration: none;
}

/* One line on a phone too: no width cap, the shell's own width is the
   limit. */
/* Vertical rhythm down the page: a wide gap under the tagline, a
   medium one between the button and the mode switch, and a long one
   before the acknowledgement so it reads as small print, apart from
   the action. */
.auth-tagline {
  font-size: 1.1rem;
  margin-bottom: 3.5rem;
}

.auth-switch {
  margin-top: 2rem;
}

.auth-demo {
  margin-top: 0.75rem;
  font-size: 0.9rem;
  color: var(--muted);
}

/* The Closed Beta page (docs/plans/closed-beta.md): the sign-in page's
   frame, a little wider for the pitch, with a badge that makes being
   turned away read as a waiting list for something worth waiting for. */
.auth-shell.beta-shell {
  max-width: 34rem;
}

.beta-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.85rem;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 1.25rem;
}

.beta-title {
  font-size: 2rem;
  margin-bottom: 0.75rem;
}

/* The page's opening line, set as a lead: the display face, larger, in ink,
   with a short brand rule under it to part the headline from the pitch. */
.beta-shell .auth-tagline.beta-lead {
  max-width: 24rem;
  margin-bottom: 1.75rem;
  color: var(--ink);
  font-size: 1.4rem;
  font-weight: 400;
  line-height: 1.35;
}

.beta-shell .auth-tagline.beta-lead::after {
  content: "";
  display: block;
  width: 3rem;
  height: 3px;
  margin: 1.5rem auto 0;
  border-radius: 2px;
  background: var(--brand-light);
}

.beta-pitch {
  max-width: 28rem;
  margin-bottom: 1.5rem;
  color: #4b5c57;
  font-size: 1.02rem;
  line-height: 1.7;
  text-wrap: pretty; /* no single word left alone on the last line */
}

/* What a beta user gets, as a card of its own between the pitch and the
   Apply button: a soft green panel with a gift, the words in ink, and the
   promise itself marked like a highlighter pass. */
.beta-offer {
  display: flex;
  align-items: center;
  gap: 1rem;
  max-width: 28rem;
  margin-bottom: 1.75rem;
  padding: 1.1rem 1.35rem;
  border: 1px solid #cfe3db;
  border-radius: 1rem;
  background: var(--brand-soft);
  text-align: left;
  line-height: 1.55;
}

.beta-offer-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-size: 1.2rem;
}

.beta-offer strong {
  font-weight: 700;
  background: linear-gradient(transparent 58%, rgba(111, 179, 161, 0.55) 58%);
}

.beta-apply {
  width: 100%;
  max-width: 22rem;
}

.beta-received {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  max-width: 30rem;
  padding: 1.1rem 1.25rem;
  border-radius: 0.9rem;
  background: #fff;
  border: 1px solid var(--brand-soft);
  text-align: left;
}

.beta-received .bi {
  font-size: 1.4rem;
  line-height: 1;
}

.beta-who {
  font-size: 0.9rem;
  color: var(--muted);
}

/* The application (pages.BetaForm): one screen at a time under Quick
   setup's band, the setup wizard's generous sizing, and a body that
   scrolls inside the modal when a screen is taller than the window. */
.beta-modal-content {
  overflow: hidden;
  border: 0;
  border-radius: 1rem;
}

.beta-form {
  display: flex;
  flex-direction: column;
  min-height: 0;
  max-height: 100%;
}

.beta-head .btn-close {
  opacity: 0.8;
}

.beta-body {
  padding: 2rem 2.25rem;
  overflow-y: auto;
}

.beta-screen-title {
  font-size: 1.75rem;
  margin-bottom: 1rem;
}

.beta-intro p {
  font-size: 1.05rem;
}

.beta-intro-points {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
  display: grid;
  gap: 0.9rem;
}

.beta-intro-points li {
  display: flex;
  gap: 0.85rem;
  align-items: flex-start;
}

.beta-intro-points .bi {
  flex: 0 0 auto;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--brand-soft);
  color: var(--brand-dark);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The progress line: brand green on the soft green, no browser chrome. */
.beta-progress {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  height: 0.4rem;
  border: 0;
  border-radius: 999px;
  background: var(--brand-soft);
  overflow: hidden;
}

.beta-progress::-webkit-progress-bar {
  background: var(--brand-soft);
}

.beta-progress::-webkit-progress-value {
  background: var(--brand);
  border-radius: 999px;
}

.beta-progress::-moz-progress-bar {
  background: var(--brand);
  border-radius: 999px;
}

.beta-step-count {
  margin: 0.75rem 0 1.25rem;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.beta-question {
  margin: 0;
}

.beta-question-label {
  font-size: 1.4rem;
  font-weight: 600;
  line-height: 1.3;
  margin-bottom: 1.25rem;
}

.beta-question-hint {
  color: var(--muted);
  margin: -0.75rem 0 1rem;
}

.beta-choices {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.6rem;
}

.beta-choice.choice-card {
  padding: 0.85rem 1rem;
  min-height: 0;
}

/* Long answers read better in the body font than the headline serif
   the setup wizard's short choices wear. */
.beta-choice .choice-card-title {
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-weight: 500;
  font-size: 1rem;
}

.beta-question-invalid .beta-question-label {
  color: #a3261b;
}

.beta-actions {
  justify-content: space-between;
  padding: 1rem 2.25rem 1.5rem;
  border-top: 1px solid #e4e9e7;
}

.beta-actions-end {
  justify-content: flex-end;
}

.beta-received-screen {
  text-align: center;
  padding: 1rem 0;
}

.beta-received-screen p {
  max-width: 30rem;
  margin-left: auto;
  margin-right: auto;
}

.beta-received-icon {
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-size: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
}

@media (max-width: 575.98px) {
  .beta-body {
    padding: 1.5rem 1.25rem;
  }

  .beta-actions {
    padding: 0.85rem 1.25rem 1.25rem;
  }

  .beta-question-label {
    font-size: 1.2rem;
  }
}

/* The Google button spans about the width of the line above it (still
   inside Google's 175-400px branding range). */
.auth-shell .btn-google {
  width: 100%;
  max-width: 22rem;
}

.auth-legal {
  max-width: 20rem;
  margin-top: 5rem;
  font-size: 0.78rem;
}

/* Links on the sign-in page: brand green, never underlined. Hovering
   deepens the green instead. */
.auth-link,
.auth-legal a {
  color: var(--brand);
  text-decoration: none;
  transition: color 0.15s ease;
}

.auth-link {
  font-weight: 500;
}

.auth-link:hover,
.auth-link:focus,
.auth-legal a:hover,
.auth-legal a:focus {
  color: var(--brand-dark);
}

/* Account setup: the Google email, shown as a value rather than a field. */
.profile-email {
  color: var(--ink);
  font-size: 1.1rem;
  overflow-wrap: anywhere;
}

/* Legal documents (Terms of Service, Privacy Policy): a reading column,
   links in brand green, and a compact cookie table. */
.legal-shell {
  max-width: 44rem;
  line-height: 1.6;
}

/* A legal document is rendered from its archived Markdown: its own title
   and "Version N · Effective …" line come first, then numbered sections. */
.legal-shell h1 {
  font-family: "Fraunces", Georgia, serif;
  font-weight: 600;
  margin-bottom: 0.25rem;
}

.legal-shell h1 + p {
  color: var(--muted);
  margin-bottom: 1.5rem;
}

.legal-shell h2 {
  font-size: 1.25rem;
  font-weight: 600;
  margin: 1.5rem 0 0.5rem;
}

.legal-shell table {
  width: 100%;
  margin-bottom: 1rem;
  font-size: 0.9rem;
}

.legal-shell th,
.legal-shell td {
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid #e4e9e7;
  vertical-align: top;
}

.legal-shell a {
  color: var(--brand);
}

.legal-table th,
.legal-table td {
  font-size: 0.92rem;
}

.legal-table code {
  color: var(--ink);
}

/* Welcome / onboarding page */
/* Welcome and Welcome back: the setup wizard's front cards. */
.welcome-shell {
  max-width: 720px;
}

.welcome-title {
  font-size: clamp(1.75rem, 3.5vw, 2.25rem);
}

.welcome-subtitle {
  max-width: 34rem;
}

/* The Welcome form: generous room around the choice cards and before
   the button. */
.welcome-form {
  padding-block: 2.5rem;
}

.welcome-submit {
  margin-top: 2.5rem;
}

/* ---- Choice cards (components.ChoiceCard) ---- */
/* The wizard's one-of-N control: a radio input hidden inside a card that
   is its label. The selected card gets the brand border, a soft fill, and
   a check in the corner, all driven by the input's :checked — no script.
   Cards sit in a grid, two across from the sm breakpoint. */
.choice-grid {
  display: grid;
  gap: 1.5rem;
}

@media (min-width: 576px) {
  .choice-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.choice-card {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1.25rem 1.25rem 1.25rem 1.5rem;
  border: 2px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
}

.choice-card:hover {
  border-color: var(--brand-light);
}

.choice-card-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.choice-card:has(.choice-card-input:checked) {
  border-color: var(--brand);
  background: var(--brand-soft);
  box-shadow: 0 8px 24px -16px rgba(22, 79, 66, 0.5);
}

.choice-card:has(.choice-card-input:focus-visible) {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.choice-card-body {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
  padding-right: 1.75rem;
}

/* An optional large icon beside the words (the couple/individual
   choice), in the brand's soft tile. */
.choice-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 0.75rem;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 1.35rem;
}

.choice-card:has(.choice-card-input:checked) .choice-card-icon {
  background: var(--brand);
  color: #fff;
}

.choice-card-title {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
  font-weight: 600;
  font-size: 1.15rem;
  color: var(--ink);
}

.choice-card-meta {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--brand);
}

.choice-card-text {
  font-size: 0.92rem;
  color: var(--muted);
  margin-top: 0.35rem;
}

/* The check appears only on the selected card; an unselected card shows
   nothing in the corner. */
.choice-card-check {
  position: absolute;
  top: 0.9rem;
  right: 0.9rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-size: 0.85rem;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.choice-card:has(.choice-card-input:checked) .choice-card-check {
  opacity: 1;
  transform: scale(1);
}

/* CreateProfile page */
.profile-card-width {
  max-width: 560px;
}

.profile-intro-width {
  max-width: 28rem;
}

/* Profile setup's vertical rhythm: room between the two fields, more
   before the button, and more again before the privacy line. */
.profile-body {
  padding-top: 2rem;
  padding-bottom: 2rem;
}

.profile-field {
  margin-bottom: 2rem;
}

.profile-submit {
  margin-top: 2.5rem;
}

/* The delete-account modal: a kind lead, reason cards, and a text box
   that appears only when Other is chosen (no script). */
.delete-lead {
  font-size: 1.1rem;
  margin-bottom: 1.75rem;
}

.edit-modal-body .delete-reasons {
  gap: 1rem;
}

.edit-modal-body .delete-reasons .choice-card {
  align-items: center;
  padding: 1rem 1.1rem;
}

/* Each description stays on one line. */
.admin-plan-choices .choice-card-body {
  min-width: 0;
  width: 100%;
}

.admin-plan-choices .choice-card-text {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.edit-modal-body .delete-reasons .choice-card-icon {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1.2rem;
}

/* Reason labels are sentences, not headings: body font, one or two lines. */
.delete-reasons .choice-card-title {
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.35;
}

.delete-reasons .choice-card-body {
  padding-right: 1.5rem;
}

.delete-reason-error {
  margin-top: -1.25rem;
  margin-bottom: 1.5rem;
}

.delete-details {
  display: none;
  margin-bottom: 1.5rem;
}

.delete-form:has(input[name="reason"][value="other"]:checked) .delete-details {
  display: block;
}

/* What goes and what stays, in a quiet box under the reasons. */
.delete-warning {
  margin-bottom: 0;
  padding: 0.9rem 1rem;
  border-radius: 0.6rem;
  background: var(--paper);
  font-size: 0.875rem;
  color: var(--muted);
}

.delete-warning strong {
  color: var(--ink);
}

.delete-warning a {
  color: var(--brand);
  font-weight: 600;
}

/* The two agreement checkboxes above Create account. */
.profile-agree {
  margin-top: 2rem;
  display: grid;
  gap: 0.85rem;
}

.profile-agree-item .form-check-input {
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.15rem;
}

.profile-agree-item .form-check-label {
  padding-left: 0.35rem;
}

.profile-privacy {
  margin-top: 2rem;
}

/* Profile setup's inline links ("Not you?", the Privacy Policy): the
   surrounding small text's size, brand green, no underline until hover. */
.profile-switch {
  font-size: inherit;
  color: var(--brand);
  text-decoration: none;
}

.profile-switch:hover,
.profile-switch:focus {
  color: var(--brand-dark);
}

/* Onboarding Wizard */

.wizard-shell {
  max-width: 900px;
}

/* Quick setup's card is wider, with a wider rail column (col-lg-4 in
   the shell), so a label like "Savings & Debt" stays on one line. */
.wizard-shell-quick {
  max-width: 1000px;
}

/* Welcome back: the resume card naming the next screen. */
.resume-card {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 1.75rem;
  border: 2px solid var(--brand);
  border-radius: 0.9rem;
  background: var(--brand-soft);
}

.resume-card .info-card-icon {
  background: var(--brand);
  color: #fff;
}

/* ---- Quick setup ---- */
/* The rail: an icon node per screen on a line that fills in as the
   person moves down it, one word each. The aside is the same soft green
   as the Advanced stepper's, with a little more room. */
.quick-aside {
  padding-top: 2.5rem !important;
}

/* The rail column is only as wide as its longest label ("Savings &
   Debt") needs, not a third of the card (Chris, 2026-09-19). */
@media (min-width: 992px) {
  .quick-aside {
    flex: 0 0 auto;
    width: 15.5rem;
  }
}

.quick-rail {
  list-style: none;
  margin: 0;
  padding: 0;
}

.quick-rail-step {
  position: relative;
  padding-bottom: 2.25rem;
}

.quick-rail-step:last-child {
  padding-bottom: 0;
}

.quick-rail-step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 1.3125rem;
  top: 2.75rem;
  bottom: 0;
  width: 3px;
  border-radius: 2px;
  background: #cfdcd7;
}

.quick-rail-done:not(:last-child)::before {
  background: var(--brand);
}

.quick-rail-link {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  text-decoration: none;
  color: var(--ink);
}

.quick-rail-node {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: 3px solid #cfdcd7;
  background: #fff;
  color: var(--muted);
  font-size: 1.15rem;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.quick-rail-done .quick-rail-node {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.quick-rail-current .quick-rail-node {
  border-color: var(--brand);
  color: var(--brand);
  box-shadow: 0 0 0 4px rgba(31, 111, 92, 0.15);
}

.quick-rail-label {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
  font-weight: 600;
  font-size: 1.05rem;
  white-space: nowrap;
}

.quick-rail-todo .quick-rail-label {
  color: var(--muted);
  font-weight: 500;
}

a.quick-rail-link:hover .quick-rail-label {
  color: var(--brand);
}

/* The band across the top of the Quick setup card: mark + "Quick setup"
   on the dark green. */
.quick-head {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 1rem 1.75rem;
  background: var(--brand-dark);
  color: var(--paper);
}

.quick-head-mark {
  display: block;
}

.quick-head-title {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
}

/* A Quick screen's rhythm: room under the heading, between cards, and
   before the actions. */
.quick-screen-head {
  margin-bottom: 2rem;
}

.choice-grid-lg {
  gap: 1.75rem;
  margin-bottom: 2.25rem;
}

.choice-grid-lg .choice-card {
  padding: 1.75rem 1.75rem 1.75rem 2rem;
  gap: 1.1rem;
}

.choice-grid-lg .choice-card-icon {
  width: 3.25rem;
  height: 3.25rem;
  font-size: 1.6rem;
}

.choice-grid-lg .choice-card-title {
  font-size: 1.3rem;
}

.quick-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1.5rem;
  margin-top: 2.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid #e4e9e7;
}

/* ---- Edit modal (components.EditModal / ModalHead) ---- */
/* Roomy on purpose (Chris's standing rule: space things out): a wider
   dialog than Bootstrap's default, deep padding in every section, large
   controls, and generous gaps between fields and choice cards. */
.edit-modal .modal-dialog {
  max-width: 40rem;
}

/* A modal whose content needs more room than the usual 40rem. The shell
   is rendered once per page and its content is swapped in, so the width
   cannot be set on the dialog itself — the CONTENT asks for it, with
   .modal-wide on the form, and :has() carries it back up to the dialog.
   The same technique the stock-awards modal and the Edit plan panel use
   to react to their own contents.

   The pension modal wants it: "Who pays it?" is three choice cards
   across, and at 40rem "A government" wrapped onto two lines. */
.edit-modal .modal-dialog:has(.modal-wide) {
  max-width: 50rem;
}

/* Wider again, for the property modal: name, value and appreciation sit
   on one line, and the sale's age, price and account on another. */
.edit-modal .modal-dialog:has(.modal-wider) {
  max-width: 58rem;
}

/* The About box shares the look but not the width: it holds three short
   things, and at the edit modals' width it read as mostly empty. */
.about-modal .modal-dialog {
  max-width: 24rem;
}

/* One modal look across the app: the About box wears it too. */
.edit-modal .modal-content,
.about-modal .modal-content {
  border: 0;
  border-radius: 1.1rem;
  box-shadow: 0 24px 60px -24px rgba(22, 79, 66, 0.45);
  overflow: hidden;
}

.edit-modal-head {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 1.75rem 2.25rem;
  background: var(--brand-soft);
}

.edit-modal-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 0.85rem;
  background: var(--brand);
  color: #fff;
  font-size: 1.5rem;
}

.edit-modal-title {
  font-size: 1.6rem;
}

.edit-modal-body {
  padding: 2.25rem 2.25rem 1.5rem;
}

.edit-modal-body .field-row {
  row-gap: 1.5rem;
  column-gap: 1.5rem;
}

.edit-modal-body .form-label {
  margin-bottom: 0.6rem;
  font-size: 0.95rem;
}

.edit-modal-body .form-control,
.edit-modal-body .form-select,
.edit-modal-body .input-group-text {
  padding: 0.85rem 1.1rem;
  font-size: 1.15rem;
}

.edit-modal-body .form-text {
  margin-top: 0.5rem;
}

.edit-modal-body .choice-grid {
  gap: 1.5rem;
  margin-bottom: 2rem !important;
}

.edit-modal-body .choice-card {
  padding: 1.5rem;
}

.edit-modal-foot {
  border: 0;
  padding: 0.5rem 2.25rem 2.25rem;
  gap: 1rem;
}

/* A Quick screen's section title above a group of cards: the kind, then
   a hairline rule across the rest of the row, so the cards and the Add
   button under it read as belonging to that kind. */
.quick-section-title {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 2rem 0 1rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand);
}

.quick-section-rule {
  flex: 1;
  height: 1px;
  background: #e4e9e7;
}

.quick-screen-head + .quick-section-title {
  margin-top: 0;
}

/* The person modal: the Retired / Still working choice under the fields. */
.person-form .person-status {
  margin-top: 2rem;
}

/* ---- Info cards (components.InfoCard) ---- */
/* One full-width card per thing, showing its values; a click opens the
   edit modal. A button, so it focuses and activates from the keyboard. */
.info-cards {
  display: grid;
  gap: 1.25rem;
  margin-bottom: 1.25rem;
}

.info-card {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  width: 100%;
  padding: 1.5rem 1.75rem;
  border: 2px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.info-card:hover,
.info-card:focus-visible {
  border-color: var(--brand);
  box-shadow: 0 8px 24px -16px rgba(22, 79, 66, 0.5);
  outline: none;
}

.info-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 0.85rem;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 1.5rem;
}

.info-card-body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
  flex: 1;
}

.info-card-eyebrow {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand);
}

.info-card-title {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
  font-weight: 600;
  font-size: 1.35rem;
  line-height: 1.2;
}

.info-card-detail {
  color: var(--muted);
}

.info-card-error {
  color: var(--danger);
  font-size: 0.9rem;
}

.info-card-action {
  flex-shrink: 0;
  color: var(--muted);
  font-size: 1.1rem;
}

.info-card-empty {
  border-style: dashed;
}

.info-card-empty .info-card-title {
  color: var(--muted);
  font-weight: 500;
}

.info-card-empty .info-card-action {
  color: var(--brand);
}

.info-card-invalid {
  border-color: var(--danger);
}

/* A slot that holds a card only sometimes: no grid row while empty. */
.info-card-slot:empty {
  display: none;
}

.prior-why {
  margin-top: 1.5rem;
}

/* A group of cards of one kind (a Quick screen's account class, or its
   debts), with the add button under the cards once it has one. */
.quick-section {
  margin-bottom: 2rem;
}

.quick-section .info-cards {
  margin-bottom: 1.25rem;
}

.quick-add {
  padding-left: 1.5rem;
  padding-right: 1.75rem;
}

.quick-add .bi {
  margin-right: 0.35rem;
}

/* Three choice cards across (an account's owner: each person, Joint). */
@media (min-width: 576px) {
  .choice-grid-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Four short choice cards across (a pension's survivor share: None,
   50%, 75%, 100%), with a tighter gap since each holds one word. */
.choice-grid-4 {
  gap: 0.75rem;
}

.edit-modal-body .choice-grid-4 {
  gap: 0.75rem;
}

.choice-grid-4 .choice-card,
.edit-modal-body .choice-grid-4 .choice-card {
  padding: 1rem;
  text-align: center;
}

@media (min-width: 576px) {
  .choice-grid-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* An optional card: "Optional" beside the eyebrow, and never dashed. */
.info-card-optional.info-card-empty {
  border-style: solid;
}

.info-card-tag {
  display: inline-block;
  margin-left: 0.6rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: #f0f2f1;
  color: var(--muted);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
}

/* "Review assumptions": quiet, in the corner of every Quick screen. */
.quick-assumptions-link {
  font-size: 0.9rem;
  color: var(--muted);
  text-decoration: none;
}

.quick-assumptions-link:hover,
.quick-assumptions-link:focus {
  color: var(--brand);
}

/* The overlay groups assumptions by the screen that made them. */
.assumptions-group {
  margin-bottom: 1.25rem;
}

.assumptions-group-title {
  margin-bottom: 0.4rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand);
}

.assumptions-list {
  padding-left: 1.25rem;
  margin-bottom: 0.25rem;
}

.assumptions-list li {
  margin-bottom: 0.35rem;
}

/* A section on Advanced setup's Review screen: the setup cards' own look. */
.review-card {
  display: flex;
  align-items: flex-start;
  gap: 1.25rem;
  width: 100%;
  padding: 1.5rem 1.75rem;
  border: 2px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
}

.review-card-body {
  flex: 1;
  min-width: 0;
}

.review-card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.5rem;
}

.review-card-edit {
  font-weight: 600;
  color: var(--brand);
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--brand);
  color: #fff;
  font-weight: 600;
  font-size: 1rem;
  line-height: 1;
  text-transform: uppercase;
  user-select: none;
  flex: 0 0 auto;
  /* never let flex layout squish the circle out of round */
  padding-top: 1px;
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.user-avatar {
  cursor: pointer;
  border-radius: 50%;
}

.user-avatar:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.scenario-card {
  border: 1px solid #e4e9e7;
  border-radius: 0.75rem;
  background: #fff;
  transition: box-shadow .15s ease, border-color .15s ease;
}

.scenario-card:hover {
  border-color: var(--brand);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .05);
}

.scenario-actions {
  position: relative;
  z-index: 2;
}

/* --- Global error toasts (htmx failure feedback) --- */
:root {
  --danger: #b23b34;
  --danger-dark: #7f2a25;
  --warn: #c9861a;
  --warn-dark: #8a5a0e;
}

/* Corner stack. The region never eats clicks; individual toasts do. */
.toast-region {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 1090;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: min(22rem, calc(100vw - 2rem));
  pointer-events: none;
}

.app-toast {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.75rem 0.875rem;
  background: #fff;
  border: 1px solid #eccdc9;
  border-left: 4px solid var(--danger);
  border-radius: 0.75rem;
  box-shadow: 0 10px 30px -12px rgba(22, 79, 66, 0.28);
  color: var(--ink);
  pointer-events: auto;
  animation: toast-in 180ms ease-out;
}

.app-toast--hiding {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 220ms ease, transform 220ms ease;
}

.app-toast__icon {
  color: var(--danger);
  font-size: 1rem;
  line-height: 1.4;
  flex-shrink: 0;
}

.app-toast__text {
  flex: 1;
  line-height: 1.4;
}

.app-toast__close {
  flex-shrink: 0;
  margin-top: 0.125rem;
}

/* ---- Plan health ---- */
/* The chart takes the room it needs and the figures stack beside it; on
   a narrow screen they go one above the other. */
/* The two panels are the same height, whichever has more in it: a card
   that stops short of the one beside it reads as unfinished (Chris,
   2026-09-24). Stretch is the grid's own default; it was being turned
   off here. */
.health-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: stretch;
}

@media (min-width: 1200px) {
  .health-layout {
    grid-template-columns: minmax(0, 1.6fr) minmax(22rem, 1fr);
  }
}

.health-verdict {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin-bottom: 1rem;
}

.health-verdict-text {
  min-width: 0;
}

.health-meaning-text {
  font-size: 0.925rem;
  color: var(--ink);
  margin-bottom: 0.75rem;
}

/* Every headline figure as a row: name and number on one line, the
   range bar under them. */
.health-figures {
  border-top: 1px solid #eef1f0;
}

.figure-row {
  padding: 0.9rem 0;
  border-bottom: 1px solid #eef1f0;
}

.figure-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.figure-row-label {
  font-weight: 500;
}

.figure-row-value {
  font-size: 1.35rem;
  line-height: 1.2;
}

/* "Worth a look": one card per thing, each opening to the longer
   explanation. */
.health-notes {
  margin-top: 1.25rem;
}

.section-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.6rem;
}

.health-note {
  border: 1px solid #f0e4cd;
  border-radius: 0.75rem;
  background: #fdf8ef;
  margin-bottom: 0.6rem;
  overflow: hidden;
}

.health-note-head {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  width: 100%;
  padding: 0.85rem 1rem;
  border: 0;
  background: transparent;
  text-align: left;
  color: var(--ink);
}

.health-note-icon {
  flex-shrink: 0;
  color: var(--warn-dark);
  font-size: 1.05rem;
  line-height: 1.3;
}

.health-note-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  flex: 1;
}

.health-note-title {
  font-weight: 600;
}

.health-note-detail {
  font-size: 0.875rem;
  color: var(--muted);
}

.health-note-chevron {
  flex-shrink: 0;
  color: var(--muted);
  transition: transform 0.15s ease;
}

.health-note-head:not(.collapsed) .health-note-chevron {
  transform: rotate(90deg);
}

.health-note-body {
  padding: 0 1rem 0.9rem 2.75rem;
  font-size: 0.9rem;
  color: var(--ink);
}

/* The Overview's Plan health tile is a link to this page. */
a.stat-health {
  text-decoration: none;
  color: var(--ink);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

a.stat-health:hover {
  box-shadow: 0 6px 20px -12px rgba(22, 79, 66, 0.45);
}

/* ---- Statements: the year picker, and the statement itself ---- */
/* The picker heads every statement page: a step either side of the year
   being read, then the whole plan as small bars with that year lit. */
.year-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.75rem;
  padding: 0.75rem 1.25rem;
  margin-bottom: 1rem;
  border: 1px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
}

.year-picker-step {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.year-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  flex-shrink: 0;
  border: 1px solid #e4e9e7;
  border-radius: 0.6rem;
  background: #fff;
  color: var(--ink);
}

.year-step:hover {
  border-color: var(--brand);
  color: var(--brand);
}

.year-step:disabled {
  color: #c7d0cd;
  border-color: #eef1f0;
}

.year-picker-now {
  min-width: 7rem;
  text-align: center;
}

.year-picker-year {
  font-size: 1.6rem;
  line-height: 1.1;
}

.year-picker-ages {
  font-size: 0.8rem;
  color: var(--muted);
}

/* One bar per year, filling the rest of the row. Each is a link, so any
   year is one click away rather than a walk along the arrows. */
/* The year labels hang BELOW the bars, absolutely positioned (see
   .year-bar-label), so they take no room in the flow and were reaching
   past the card's own padding — the year sat on the edge or over it
   (Chris, 2026-09-24). This margin is the room they need, so the
   picker grows to hold them instead. */
/* No gap between the bars: each bar's own 1px of padding either side
   draws the same 2px space INSIDE the link, so the pointer is always
   over some year. With a real gap, moving along the row crossed a strip
   that was over none, and the year being read flashed back up between
   two years (Chris, 2026-10-05). */
.year-picker-bars {
  display: flex;
  align-items: flex-end;
  gap: 0;
  flex: 1 1 18rem;
  min-width: 0;
  height: 2.75rem;
  margin-bottom: 1.35rem;
}

.year-bar {
  position: relative;
  display: flex;
  align-items: flex-end;
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0 1px;
  border-radius: 3px;
}

.year-bar-fill {
  display: block;
  width: 100%;
  height: 55%;
  border-radius: 3px;
  background: #d8e6e0;
}

.year-bar:hover .year-bar-fill {
  background: var(--brand-light);
}

.year-bar.shown .year-bar-fill {
  height: 100%;
  background: var(--brand);
}

.year-bar-label {
  position: absolute;
  left: 50%;
  bottom: -1.15rem;
  transform: translateX(-50%);
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--brand);
  white-space: nowrap;
  visibility: hidden;
}

/* Every bar carries its year, and one shows at a time, in the room the
   picker already keeps under the bars: the year being read, or, while
   the pointer is on a bar (or a bar has the keyboard), that bar's year
   in its place, so the two never sit side by side and nothing can be
   covered at any width (Chris, 2026-10-05). A touch screen has no
   pointer to rest, so it only ever shows the year being read. */
.year-bar.shown .year-bar-label {
  visibility: visible;
}

.year-picker-bars:has(.year-bar:not(.shown):hover) .year-bar.shown .year-bar-label,
.year-picker-bars:has(.year-bar:not(.shown):focus-visible) .year-bar.shown .year-bar-label {
  visibility: hidden;
}

.year-picker-bars .year-bar:hover .year-bar-label,
.year-picker-bars .year-bar:focus-visible .year-bar-label {
  visibility: visible;
}

/* The first and last years would hang half their width past the end
   of the row, so they sit flush with it instead. */
.year-bar:first-child .year-bar-label {
  left: 0;
  transform: none;
}

.year-bar:last-child .year-bar-label {
  left: auto;
  right: 0;
  transform: none;
}

.year-bar:not(.shown):hover .year-bar-label,
.year-bar:not(.shown):focus-visible .year-bar-label {
  color: var(--ink);
}

/* The chart takes the room it needs and the statement reads beside it;
   on a narrow screen they stack. */
/* Same height as each other, for the same reason as .health-layout. */
.statement-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: stretch;
}

@media (min-width: 1200px) {
  .statement-layout {
    grid-template-columns: minmax(0, 1.5fr) minmax(23rem, 1fr);
  }
}

.statement-line,
.statement-total {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.5rem 0;
}

.statement-line + .section-label,
.statement-total + .section-label {
  margin-top: 1.1rem;
}

.statement-total {
  border-top: 1px solid #eef1f0;
  font-weight: 600;
}

.statement-label {
  flex: 1;
  min-width: 0;
}

.statement-amount {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.statement-tag {
  display: inline-block;
  margin-left: 0.4rem;
  vertical-align: middle;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--muted);
  background: #f1f4f3;
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
}

/* The dot beside a line is its bar's colour on the chart, so the two
   read as one picture. */
.statement-dot {
  flex-shrink: 0;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 2px;
  align-self: center;
}

.dot-earned {
  background: var(--brand);
}

.dot-spent {
  background: #c77b58;
}

.dot-growth {
  background: #4d9da6;
}

.dot-result {
  background: var(--ink);
}

/* The account palette, matching engine.AccountLabel's chart colours, so
   a balance sheet's dots line up with its own legend. */
.dot-acct-0 {
  background: #35617d;
}

.dot-acct-1 {
  background: #e0a458;
}

.dot-acct-2 {
  background: #8e6bb0;
}

.dot-acct-3 {
  background: #4d9da6;
}

.dot-acct-4 {
  background: #c77b58;
}

.dot-acct-5 {
  background: #7d8b3f;
}

.dot-acct-6 {
  background: #b56576;
}

.dot-acct-7 {
  background: #5c6bc0;
}

/* The answer the statement adds up to, in its own band. */
.statement-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1rem;
  padding: 1rem 1.25rem;
  border-radius: 0.75rem;
  background: var(--brand-soft);
}

.statement-bottom-label {
  font-size: 1.05rem;
}

.statement-bottom-caption {
  font-size: 0.8rem;
  color: var(--muted);
}

.statement-bottom-amount {
  font-size: 1.5rem;
  color: var(--brand-dark);
  white-space: nowrap;
}

.statement-note {
  margin: 0.9rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}

/* A group that exists but has nothing in it yet says so where a reader
   goes looking for it. */
.statement-group-note {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
}

/* ---- Balance sheet: the two columns beside net worth over time ---- */
.balance-charts {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
}

@media (min-width: 992px) {
  .balance-charts {
    grid-template-columns: minmax(10rem, 0.5fr) minmax(0, 1fr);
  }
}

.balance-chart {
  min-width: 0;
}

.chart-caption {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 0.4rem;
  text-align: center;
}

/* ---- The Overview's glance row: three statements of the picked year ---- */
/* One full-height link per year on the balance chart. Invisible until
   hovered, so the chart reads as a chart and still says it can be
   clicked. */
.chart-pick {
  fill: transparent;
  cursor: pointer;
}

.chart-pick:hover {
  fill: rgba(31, 111, 92, 0.10);
}

.chart-pick.shown {
  fill: rgba(31, 111, 92, 0.14);
}

.glance {
  margin-top: 1.5rem;
}

.glance-card {
  display: flex;
  flex-direction: column;
  border: 1px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
  padding: 1rem 1.1rem;
  /* The card is what the waterfall's size is measured against, not the
     window: a third of the content area is not a fixed share of the
     screen once the Edit plan panel opens and takes some of it. */
  container-type: inline-size;
}

.glance-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.glance-title {
  font-size: 1rem;
}

.glance-sub {
  font-size: 0.8rem;
  color: var(--muted);
}

.glance-open {
  flex-shrink: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--brand);
  white-space: nowrap;
}

.glance-body {
  flex: 1;
}

/* The card has room these bars were not using: they sat 14px tall and
   0.6rem apart and read as thin stripes (Chris, 2026-09-23). */
.glance-bar-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 0.8125rem;
  color: var(--muted);
  margin-top: 2rem;
}

.glance-bar-head:first-child {
  margin-top: 0.5rem;
}

.glance-bar-total {
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* The waterfall keeps its own size rather than stretching to the card.
   Scaling it would scale its words: on a wide screen its names and
   figures grew while the sibling cards' CSS text did not, which is where
   the three cards stopped agreeing (Chris, 2026-09-23). Shrinking is
   fine — a card narrower than the picture has no room anyway. */
.glance-waterfall {
  display: block;
  /* auto, not 100%: the picture keeps the width it was DRAWN at, so a
     unit stays a pixel and its words stay the size they were set. It
     shrinks on a card too narrow for it, and never stretches. */
  width: auto;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
}

/* Which of the drawn widths is shown — the widest that fits inside the
   card's padding. Container queries, not Bootstrap breakpoints: the
   question is how wide the CARD is, and guessing that from the window
   put a 470px picture in a 706px card on a 2560px screen, leaving a
   third of the card white (Chris, 2026-09-23).

   Each threshold is its picture's width (helpers.GlanceChartWidths) plus
   the card's own 2.2rem of side padding, rounded up. The narrowest shows
   by default, so a browser without container queries still gets a
   picture rather than none. */
.glance-chart-1,
.glance-chart-2,
.glance-chart-3,
.glance-chart-4 {
  display: none;
}

@container (min-width: 456px) {
  .glance-chart-0 {
    display: none;
  }

  .glance-chart-1 {
    display: block;
  }
}

@container (min-width: 546px) {
  .glance-chart-1 {
    display: none;
  }

  .glance-chart-2 {
    display: block;
  }
}

@container (min-width: 636px) {
  .glance-chart-2 {
    display: none;
  }

  .glance-chart-3 {
    display: block;
  }
}

@container (min-width: 726px) {
  .glance-chart-3 {
    display: none;
  }

  .glance-chart-4 {
    display: block;
  }
}

.glance-bar {
  display: block;
  width: 100%;
  height: 50px;
  border-radius: 4px;
  overflow: hidden;
  margin-top: 0.4rem;
}

/* The names of the colours in the bars above. Without these the card is
   a row of coloured blocks nobody can read. */
/* One size across the row: the names under the waterfall's bars, these
   names, and every dollar figure on all three cards (Chris, 2026-09-23). */
.glance-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem 1rem;
  margin-top: 1.1rem;
  color: var(--muted);
  font-size: 0.8125rem;
}

.glance-legend span {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.glance-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  border-top: 1px solid #eef1f0;
  font-size: 0.9rem;
}

.glance-amount {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---- Dashboard: full width, brand-green sidebar flush to the header ---- */
/* The sidebar runs from the header to the bottom of the window and the
   content fills the rest on the paper background. No frame around the
   two: the old rounded card (max-width 76rem) is gone. */
.dashboard-stage {
  flex: 1;
  display: flex;
  align-items: stretch;
  min-height: 0;
}

.dashboard-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  padding: 1.5rem 1.75rem 1rem;
}

#main-pane {
  flex: 1;
}

/* htmx's own indicator rules, here so they apply from the first paint.
   htmx adds the same rules itself (includeIndicatorStyles), but only once
   the page has loaded, so every full page load showed the "Calculating
   results" overlay for a moment first: in the Help center, whose topics
   are whole pages, on every click (Chris, 2026-10-07). */
.htmx-indicator {
  opacity: 0;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  opacity: 1;
  transition: opacity 200ms ease-in;
}

/* Dims the whole viewport with a centered card while a Baseline/scenario
   navigation is computing a fresh Monte Carlo simulation (see
   pages.resultsSpinner's doc comment for why this is position: fixed
   rather than absolute within the content pane — a tall, scrolled results
   page would otherwise center the card somewhere off screen). htmx's own
   injected style (includeIndicatorStyles) toggles opacity 0/1 via the
   .htmx-indicator class already on this element; this block supplies the
   positioning, the dark scrim, and keeps the invisible (opacity 0) overlay
   from blocking clicks while idle. z-index above Bootstrap's navbar (1030)
   so the dim covers it too. */
.results-spinner-overlay {
  position: fixed;
  inset: 0;
  z-index: 1050;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(26, 43, 40, 0.6); /* --ink, translucent */
  pointer-events: none;
}

.results-spinner-card {
  background: #fff;
  border-radius: 12px;
  padding: 2rem 2.5rem;
  text-align: center;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

.htmx-request.results-spinner-overlay {
  pointer-events: auto;
}

.dashboard-sidebar {
  width: 15.5rem;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--brand-dark) !important;
  color: #cfe3db;
  padding: 1.25rem 0.75rem;
  --bs-offcanvas-width: min(78vw, 17rem);
  /* mobile sheet width */
}

/* From tablet width up the sidebar stays put while the content scrolls:
   pinned under the 60px platform header, as tall as the rest of the
   window. Below that it is Bootstrap's slide-in sheet, untouched. */
@media (min-width: 768px) {
  .dashboard-sidebar {
    position: sticky;
    top: 60px;
    height: calc(100vh - 60px);
    overflow-y: auto;
  }
}

.sidebar-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #93b6a9;
  padding: 0 0.75rem 0.375rem;
}

.sidebar-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  text-decoration: none;
  color: #cfe3db;
  padding: 0.625rem 0.75rem;
  border-radius: 8px;
}

.sidebar-row:hover {
  background: rgba(255, 255, 255, 0.10);
  color: #fff;
}

.sidebar-row.active {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  font-weight: 600;
}

/* A plan row's success rate, in its tier's colour lightened for the dark
   sidebar. */
.sidebar-rate {
  flex-shrink: 0;
  font-size: 0.8rem;
  font-weight: 600;
}

.sidebar-rate.tier-good {
  color: #8fd6bf;
}

.sidebar-rate.tier-warn {
  color: #f0c878;
}

.sidebar-rate.tier-danger {
  color: #f0a59b;
}

/* The header's plan health pill: ring, rate, status word, tinted by tier
   (the ring strokes with currentColor). */
.health-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.9rem 0.4rem 0.5rem;
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 600;
  white-space: nowrap;
}

.health-pill.tier-good {
  background: var(--brand-soft);
  color: var(--brand-dark);
}

.health-pill.tier-warn {
  background: #f7eede;
  color: var(--warn-dark);
}

.health-pill.tier-danger {
  background: #f7e9e7;
  color: var(--danger-dark);
}

@media (max-width: 767.98px) {
  .dashboard-content {
    padding: 1rem 1rem 0.75rem;
  }
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

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

  .app-toast--hiding {
    transition: none;
  }
}

/* ---- Overview page (Baseline / scenario results) ---- */

/* Title block: text on the left, the page's actions on the right; wraps
   on narrow screens so the buttons drop below the title. */
/* The title row: the plan's name on the left, then its fact chips, then
   the page's actions pushed to the far right. Wraps on narrow screens. */
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem 1.25rem;
  margin-bottom: 1.25rem;
}

.page-head-text {
  min-width: 0;
}

.page-head .page-actions {
  margin-left: auto;
}

.page-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* Facts strip: a row of read-only chips beside the title. */
.fact-strip {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0;
  margin: 0;
}

.fact-strip li {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.75rem;
  border: 1px solid #e4e9e7;
  border-radius: 999px;
  background: #fff;
  font-size: 0.85rem;
  color: var(--ink);
}

.fact-strip .bi {
  color: var(--brand);
}

/* A chip that opens where its figure is changed: the "Balances as of"
   chip, which opens the Savings and debt group of the Edit plan panel. */
.fact-strip .fact-link {
  padding: 0;
}

.fact-strip .fact-link a {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  color: inherit;
}

.fact-strip .fact-link:hover {
  border-color: var(--brand-light);
}

/* Amber once the balances are more than three months old: the plan's
   first year still starts from them, so it is worth updating them. */
.fact-strip .fact-stale {
  border-color: #f0c878;
  background: #fff8e6;
}

.fact-strip .fact-stale .bi {
  color: #b7791f;
}

/* Panels: the bordered sections the results and charts sit in. */
.panel {
  border: 1px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
}

.panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid #eef1f0;
}

.panel-title {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
  font-weight: 600;
  font-size: 1.1rem;
  margin: 0;
}

.panel-sub {
  font-size: 0.85rem;
  color: var(--muted);
}

.panel-body {
  padding: 1.25rem;
}

/* Plan health: the verdict tier colors the panel's top accent (the
   verdict-* class is also what the Monte Carlo e2e spec reads from). */
.plan-health {
  border-top: 4px solid var(--brand);
}

.plan-health.verdict-funded {
  border-top-color: var(--brand);
}

.plan-health.verdict-warn {
  border-top-color: var(--warn);
}

.plan-health.verdict-short {
  border-top-color: var(--danger);
}

.tier-good {
  color: var(--brand);
}

.tier-warn {
  color: var(--warn-dark);
}

.tier-danger {
  color: var(--danger);
}

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
}

.status-pill.tier-good {
  background: var(--brand-soft);
  color: var(--brand-dark);
}

.status-pill.tier-warn {
  background: #f7eede;
}

.status-pill.tier-danger {
  background: #f7e9e7;
  color: var(--danger-dark);
}

.health-row {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.gauge {
  flex: 0 0 auto;
  text-align: center;
}

.gauge svg {
  display: block;
  margin: 0 auto;
}

.gauge-caption {
  font-size: 0.7rem;
  line-height: 1.2;
  color: var(--muted);
  margin-top: 0.3rem;
  max-width: 6rem;
}

.health-text {
  flex: 1;
  min-width: 0;
}

.health-expected {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: 0.9rem;
}

.health-expected .bi {
  color: var(--brand);
  margin-top: 0.15rem;
  flex-shrink: 0;
}

.health-meaning {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid #eef1f0;
}

.health-meaning-title {
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: 0.35rem;
}

/* Quiet callouts inside a panel (a note, a warning). */
.note {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.7rem 0.9rem;
  border-radius: 0.6rem;
  font-size: 0.875rem;
  margin-top: 0.75rem;
}

.note .bi {
  flex-shrink: 0;
  margin-top: 0.1rem;
}

.note-info {
  background: var(--paper);
  border: 1px solid #e4e9e7;
  color: var(--ink);
}

.note-info .bi {
  color: var(--muted);
}

.note-warn {
  background: #fbf4e6;
  border: 1px solid #efdcb8;
  color: #5f4310;
}

.note-warn .bi {
  color: var(--warn-dark);
}

/* A blocking note: something wrong that must be fixed before the step can
   finish (the Review step's "Fix these before finishing"), as opposed to
   .note-warn's "worth a look". */
.note-error {
  background: #fbeae7;
  border: 1px solid #f0c6bf;
  color: #6b1d12;
}

.note-error .bi {
  color: #b3301f;
}

/* Headline-figure tiles with a mini range bar (SVG, percentage lengths). */
.stat {
  border: 1px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
  padding: 1rem 1.1rem;
}

/* The Plan health tile: the ring beside the status word, with the
   verdict tier as its top accent (the verdict-* class is also what the
   Monte Carlo e2e spec reads from). */
.stat-health {
  display: flex;
  align-items: center;
  gap: 1rem;
  border-top-width: 4px;
}

.stat-health.verdict-funded {
  border-top-color: var(--brand);
}

.stat-health.verdict-warn {
  border-top-color: var(--warn);
}

.stat-health.verdict-short {
  border-top-color: var(--danger);
}

.stat-status {
  font-size: 1.25rem;
  line-height: 1.2;
  margin-bottom: 0.2rem;
}

.stat-label {
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: 0.35rem;
}

.stat-value {
  font-size: 1.5rem;
  line-height: 1.15;
}

.stat-range {
  display: block;
  width: 100%;
  margin: 0.5rem 0 0.35rem;
}

.stat-sub {
  font-size: 0.8rem;
  color: var(--ink);
}

.stat-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
}

.stat-legend > span {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.stat-key {
  display: inline-block;
}

.stat-key-line {
  width: 2px;
  height: 12px;
  background: var(--ink);
}

.stat-key-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--brand);
}

.stat-key-band {
  width: 18px;
  height: 6px;
  border-radius: 3px;
  background: var(--brand);
  opacity: 0.28;
}

.help-toggle {
  font-size: 0.8rem;
  text-decoration: none;
}

.help-list {
  display: grid;
  gap: 0.35rem;
  margin: 0.75rem 0 0;
  font-size: 0.85rem;
}

.help-list > div {
  display: grid;
  grid-template-columns: minmax(8rem, 12rem) 1fr;
  gap: 0.75rem;
}

.help-list dt {
  font-weight: 600;
}

.help-list dd {
  margin: 0;
  color: var(--muted);
}

/* Charts: the SVG scales to the panel on desktop; on a phone it keeps a
   readable minimum width and scrolls sideways instead of shrinking its
   axis labels to a few pixels. */
/* The chart panel's two-button switch: a soft track with the chosen
   chart's button raised on white. */
.chart-switch {
  display: inline-flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.7rem;
  background: #eef2f0;
}

.chart-switch-btn {
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 500;
  padding: 0.45rem 1rem;
  border-radius: 0.5rem;
}

.chart-switch-btn:hover {
  color: var(--ink);
}

.chart-switch-btn.active {
  background: #fff;
  color: var(--ink);
  font-weight: 600;
  box-shadow: 0 1px 3px rgba(26, 43, 40, 0.12);
}

/* Chart 2's per-year hit areas: invisible, but they take the pointer so
   the browser shows each year's <title> as a tooltip. */
.chart-hover {
  cursor: crosshair;
}

.chart-scroll {
  overflow-x: auto;
}

.chart-scroll svg {
  display: block;
  width: 100%;
}

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 1rem;
  margin-top: 0.75rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.chart-legend > span {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
}

/* Plan inputs: a heading row, then a grid of per-step cards. */
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.section-title {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
  font-weight: 600;
  font-size: 1.1rem;
  margin: 0;
}

.input-card {
  border: 1px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
  padding: 1rem 1.1rem;
}

.input-card-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.75rem;
}

.input-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 0.55rem;
  background: var(--brand-soft);
  color: var(--brand);
  flex-shrink: 0;
}

.input-card-title {
  font-weight: 600;
}

/* The "adjust first, then run" callout on a scenario's edit view. */
.edit-callout {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1rem 1.1rem;
  border: 1px dashed #cfdad6;
  border-radius: 0.9rem;
  background: var(--paper);
  margin-bottom: 1.5rem;
}

.edit-callout .bi {
  color: var(--brand);
  font-size: 1.2rem;
}

/* ---- Branded error page ---- */
.error-shell {
  max-width: 34rem;
}

.error-art {
  width: 100%;
  max-width: 22rem;
  height: auto;
}

.error-note {
  max-width: 26rem;
}

/* Outline brand button (pairs with .btn-brand). */
/* The three tiers of a step's button row, standard for multi-step forms:
   Continue is the one solid button; Save is outlined in the brand color
   and only tints on hover (a secondary button never fills solid — that's
   the primary's look); Back is navigation, not an action, so it's a plain
   text link. */
.btn-outline-brand-soft {
  color: var(--brand);
  border-color: var(--brand);
  background: transparent;
}

.btn-outline-brand-soft:hover,
.btn-outline-brand-soft:focus {
  color: var(--brand-dark);
  border-color: var(--brand);
  background: var(--brand-soft);
}

/* Bootstrap's text-style button goes link-blue on hover and active; every
   such link here is ink instead, so nothing in the app ever flashes blue. */
.btn-link {
  --bs-btn-hover-color: var(--ink);
  --bs-btn-active-color: var(--ink);
}

.btn-back {
  color: var(--muted);
  text-decoration: none;
}

.btn-back:hover,
.btn-back:focus {
  color: var(--ink);
}

.btn-outline-brand {
  color: var(--brand);
  border-color: var(--brand);
}

.btn-outline-brand:hover,
.btn-outline-brand:focus {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

@media (max-width: 767.98px) {
  .chart-scroll svg {
    min-width: 640px;
  }

  .health-row {
    flex-direction: column;
    align-items: flex-start;
  }

  .help-list > div {
    grid-template-columns: 1fr;
    gap: 0.1rem;
  }
}

/* Caps a numeric input's width to a couple of digits, for a field next to a
   short label (e.g. a percentage) where the browser's default text-input
   width would otherwise dwarf both the label and the value — .form-control
   has no intrinsic width of its own (it fills its flex container), so
   without this a "col-auto" column sizes to the input's default width
   rather than to its actual content. */
.input-narrow {
  max-width: 4.5rem;
}

/* Ticked checkboxes, radios, and switches are brand green everywhere,
   not Bootstrap's blue; a switch stays Bootstrap's grey when off. */
.form-check-input:checked {
  background-color: var(--brand);
  border-color: var(--brand);
}

.form-check-input:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 0.25rem rgba(31, 111, 92, 0.25);
}

/* Form field rows — a CSS grid with subgrid, not Bootstrap's flex .row.
   Every field in a .field-row spans the same three shared tracks:
   label, control, message. So across a row the labels sit at the top,
   every control starts at the same height, and a message under one
   field (help text or a validation error) opens the message track for
   the whole row instead of pushing that one control up. Nothing about a
   wrap or an error can knock a row's inputs out of line.

   Widths use a 12-column vocabulary of our own (.span-N, .span-sm-N,
   .span-md-N — same breakpoints as Bootstrap's .col-*), because Bootstrap's
   column classes are flex-based and don't apply inside a grid.

   A field is any direct child of the row: label, control, then at most
   one more child (a .form-text, an .invalid-feedback, or a .field-msg
   wrapping both). A .field-side child has no label (a Remove button, a
   connector word) and puts its content on the control track. */
.field-row {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: 1rem;
  row-gap: 1rem;
}

.field-row > * {
  display: grid;
  grid-template-rows: subgrid;
  /* One column clamped to the cell: a field's own grid would otherwise
     size its lone auto column to the control's intrinsic width (a number
     input's is wider than a two- or three-column cell) and spill past
     the cell's edge — and past the row's, when nothing sits to its right. */
  grid-template-columns: minmax(0, 1fr);
  grid-row: span 3;
  row-gap: 0;
  grid-column: span 12;
}

.field-row > * > .form-label {
  align-self: start;
}

.field-row > .field-side > * {
  grid-row: 2;
  align-self: center;
}

.field-row > .field-side-end > * {
  justify-self: end;
}

.span-1 { grid-column: span 1; }
.span-2 { grid-column: span 2; }
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-5 { grid-column: span 5; }
.span-6 { grid-column: span 6; }
.span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }

@media (min-width: 576px) {
  .span-sm-1 { grid-column: span 1; }
  .span-sm-2 { grid-column: span 2; }
  .span-sm-3 { grid-column: span 3; }
  .span-sm-4 { grid-column: span 4; }
  .span-sm-5 { grid-column: span 5; }
  .span-sm-6 { grid-column: span 6; }
  .span-sm-7 { grid-column: span 7; }
  .span-sm-8 { grid-column: span 8; }
  .span-sm-12 { grid-column: span 12; }
}

@media (min-width: 768px) {
  .span-md-6 { grid-column: span 6; }
  .span-md-12 { grid-column: span 12; }
}

/* A small "circle-i" icon that shows a plain-text tip on hover/focus — no
   Bootstrap JS tooltip init needed (onboarding pages don't load the bundle),
   and it survives HTMX swaps with no re-initialization required. */
.info-tip {
  position: relative;
  display: inline-flex;
  color: var(--muted);
  cursor: help;
  line-height: 1;
}

.info-tip::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 130%;
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: #fff;
  padding: .35rem .6rem;
  border-radius: .4rem;
  font-size: .72rem;
  font-weight: 400;
  line-height: 1.3;
  width: 15rem;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  transition: opacity .15s ease;
  pointer-events: none;
  z-index: 20;
}

.info-tip:hover::after,
.info-tip:focus-visible::after {
  opacity: 1;
  visibility: visible;
}

/* For an icon near the right edge of its container: the default centered
   bubble would run past that edge and get clipped by an ancestor's
   overflow-hidden (e.g. the onboarding card). Anchor the bubble's right edge
   to the icon instead, so it opens leftward and stays inside. */
.info-tip--end::after {
  left: auto;
  right: 0;
  transform: none;
}

/* This build of Bootstrap styles .form-control:disabled but has no matching
   [readonly] rule (confirmed by inspecting bootstrap.min.css directly) —
   without this, a readonly field (used instead of disabled wherever it
   must stay inert but keep submitting with the form, e.g. a locked
   risk-profile's Expected return/Volatility, or a sibling-toggled-off
   dollar field) looks pixel-identical to an editable one, with no visual
   cue it can't be typed into. Mirrors Bootstrap's own :disabled treatment. */
.form-control[readonly] {
  background-color: var(--bs-secondary-bg);
  opacity: 1;
}
/* ---------------------------------------------------------------------
   The Edit plan panel

   A plan is changed from here, beside whatever page is being read rather
   than in place of it: the panel slides in on the right and the content
   gives up the width, so nothing a person was looking at is covered.
   Bootstrap's offcanvas does the sliding; the narrowing is this one rule,
   which reads the panel's own open state rather than needing a script to
   mirror it onto the stage. Below lg there is not enough width to share,
   so the panel covers as an offcanvas normally does.
   --------------------------------------------------------------------- */
/* Two classes, to outrank Bootstrap's own .offcanvas-end (top: 0): the
   panel opens BENEATH the platform header, not over it, so Edit plan,
   the plan health pill and the account menu stay reachable while it is
   open. .offcanvas already pins the bottom. */
/* The account menu opens inside the header, so it can rise no higher
   than the header does, and the header's 1030 is below an open
   panel's 1045: the menu opened BEHIND the panel (Chris, 2026-10-04).
   While a panel is open the header goes above it. Only then, since the
   phone sidebar opens from the top of the screen and the header must
   not cover it. Still below a modal (1050 and up). */
body:has(.plan-panel.show) .platform-header,
body:has(.plan-panel.showing) .platform-header {
  z-index: 1046;
}

.offcanvas.plan-panel {
  --bs-offcanvas-width: 27.5rem;
  top: 60px; /* .platform-header-inner's height */
  border-left: 1px solid #e4e9e7;
  background: #fff;
}

@media (min-width: 992px) {
  .dashboard-stage:has(.plan-panel.show) .dashboard-content,
  .dashboard-stage:has(.plan-panel.showing) .dashboard-content {
    margin-right: var(--bs-offcanvas-width, 27.5rem);
  }

  .dashboard-content {
    transition: margin-right 0.3s ease-in-out;
  }
}

.plan-panel-body {
  padding: 1.5rem 1.5rem 3rem;
}

.plan-panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.75rem;
}

.plan-panel-count {
  margin-bottom: 1.25rem;
  color: var(--muted);
  font-size: 0.875rem;
}

.plan-panel-groups {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.plan-group {
  border: 1px solid #e4e9e7;
  border-radius: 0.75rem;
  overflow: hidden;
}

.plan-group-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.875rem 1rem;
  border: 0;
  background: #fff;
  color: var(--ink);
  text-align: left;
}

.plan-group-head:hover {
  background: var(--brand-soft);
}

.plan-group-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 2rem;
  width: 2rem;
  height: 2rem;
  border-radius: 0.5rem;
  background: var(--brand-soft);
  color: var(--brand);
}

.plan-group-text {
  flex: 1;
  min-width: 0;
}

.plan-group-label {
  display: block;
  font-weight: 600;
  font-size: 0.9375rem;
}

.plan-group-summary {
  display: block;
  color: var(--muted);
  font-size: 0.8125rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.plan-group-assumed {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.75rem;
}

.plan-group-caret {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.75rem;
  transition: transform 0.15s ease;
}

.plan-group-head:not(.collapsed) .plan-group-caret {
  transform: rotate(180deg);
}

.plan-group-body {
  padding: 0.25rem 1rem 1rem;
  border-top: 1px solid #f0f2f1;
}

/* The panel shows the wizard's own cards, only smaller: the same markup
   in a column half the width, so a card behaves identically in both
   places and only its size changes. */
.plan-panel .info-cards {
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.plan-panel .info-card {
  gap: 0.75rem;
  padding: 0.75rem 0.875rem;
  border-width: 1px;
  border-radius: 0.65rem;
}

.plan-panel .info-card-icon {
  flex: 0 0 2rem;
  width: 2rem;
  height: 2rem;
  font-size: 1rem;
}

.plan-panel .info-card-title {
  font-size: 0.9375rem;
}

.plan-panel .info-card-eyebrow,
.plan-panel .info-card-detail {
  font-size: 0.75rem;
}

.plan-panel .quick-section {
  margin-bottom: 1rem;
}

.plan-panel .quick-section-title {
  margin-bottom: 0.5rem;
  font-size: 0.75rem;
}

.plan-panel .quick-add {
  padding: 0.35rem 0.9rem;
  font-size: 0.8125rem;
}

/* The receipt: what the last save moved, low on the left where it is out
   of the panel's way and out of the chart's. It shows for a few seconds
   and then goes (Chris, 2026-09-29): fades in, holds, fades out, and
   ends hidden so it can take no clicks. Done in CSS alone; the element
   stays in the page until the next swap replaces the pane. */
.plan-receipt {
  animation: plan-receipt-life 5s ease forwards;
  position: fixed;
  left: 1.25rem;
  bottom: 1.25rem;
  z-index: 1035;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  max-width: 22rem;
  padding: 0.875rem 1.25rem;
  border-radius: 0.75rem;
  background: #16211f;
  color: #fff;
  box-shadow: 0 16px 40px -20px rgba(0, 0, 0, 0.6);
}

@keyframes plan-receipt-life {
  0% { opacity: 0; transform: translateY(0.5rem); visibility: visible; }
  6% { opacity: 1; transform: none; }
  88% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: none; visibility: hidden; }
}

/* Without motion it still leaves on time; it just does not slide. */
@media (prefers-reduced-motion: reduce) {
  .plan-receipt { animation-name: plan-receipt-life-still; }
  @keyframes plan-receipt-life-still {
    0%, 94% { opacity: 1; visibility: visible; }
    100% { opacity: 0; visibility: hidden; }
  }
}

.plan-receipt > .bi {
  color: var(--brand-light);
  font-size: 1.25rem;
}

.plan-receipt-what {
  font-weight: 600;
  font-size: 0.9375rem;
}

.plan-receipt-health {
  color: #b9c6c1;
  font-size: 0.8125rem;
}

/* A statement line that has an input behind it opens it, and says so
   only on hover: a page of figures should read as figures first, not as
   a row of controls. The row is not itself a button, so its ? can sit
   after the label: a transparent button covers it instead
   (.statement-line-cover), and the ? sits above that. */
.statement-line-open {
  position: relative;
  border-radius: 0.4rem;
  cursor: pointer;
}

.statement-line-open:hover,
.statement-line-open:has(.statement-line-cover:focus-visible) {
  background: var(--brand-soft);
}

.statement-line-cover {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  border-radius: inherit;
}

.statement-line-cover:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

.statement-line-pencil {
  margin-left: 0.4rem;
  color: var(--brand);
  font-size: 0.75rem;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.statement-line-open:hover .statement-line-pencil,
.statement-line-open:has(.statement-line-cover:focus-visible) .statement-line-pencil {
  opacity: 1;
}

/* The Overview's tiles fit themselves to the space they are given, not
   to the window: the Edit plan panel narrows the content area without
   the window changing at all, so a width breakpoint would leave four
   tiles squeezed into the room for two. */
.overview-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
  margin-bottom: 1rem;
}

.overview-tiles > .stat {
  height: 100%;
}

/* "More detail" — the fields a dashboard modal holds beyond the two or
   three Quick setup asks. Closed to begin with, so the modal still opens
   as the small thing it is. */
.more-detail {
  margin-top: 1.25rem;
  border-top: 1px solid #eef1f0;
  padding-top: 0.75rem;
}

/* The open/closed state is a real checkbox rather than a Bootstrap
   collapse, so it survives the modal re-fetching itself and can be set
   open by the server when a save is rejected (see components.MoreDetail).
   The input is hidden but NOT disabled, so it still submits. */
.more-detail-state {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.more-detail-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0;
  border: 0;
  background: none;
  color: var(--brand);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
}

.more-detail-toggle > .bi {
  font-size: 0.75rem;
  transition: transform 0.15s ease;
}

/* A heading that follows a row of fields inside "More detail" needs air
   above it, or it reads as part of the field it is sitting under — the
   pension and annuity modals both put "Survivor benefit" straight after
   the yearly-increase row, and it was crowding the help text above it
   (Chris, 2026-09-28). The same 1.5rem the field rows use between
   themselves, so the rhythm holds. */
.more-detail-body .field-row + .form-label {
  display: block;
  margin-top: 1.5rem;
}

/* Keyboard users land on the hidden checkbox, so its focus ring has to
   be drawn on the label they can actually see. */
.more-detail-state:focus-visible + .more-detail-toggle {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 0.2rem;
}

.more-detail-state:checked ~ .more-detail-toggle > .bi {
  transform: rotate(180deg);
}

.more-detail-body {
  display: none;
  padding-top: 1rem;
}

.more-detail-state:checked ~ .more-detail-body {
  display: block;
}

/* The tag on a figure the app chose rather than the person. It says
   something about this plan, not about the field, so it disappears the
   moment a value of their own is saved. */
/* A few words beside the Assumed tag naming which default was used, for
   a field whose default depends on another answer (the annuity payout
   rate, which follows the survivor benefit). Quieter than the tag: the
   tag is the flag, this is the detail. */
.assumed-note {
  margin-left: 0.35rem;
  color: var(--bs-secondary-color, #6c757d);
  font-size: 0.6875rem;
  font-weight: 400;
  vertical-align: middle;
}

.assumed-tag {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: 0.3rem;
  background: #f7eede;
  color: var(--warn-dark);
  font-size: 0.6875rem;
  font-weight: 600;
  vertical-align: middle;
}

/* The Social Security modal's estimate: what this person's own plan
   pays, worked out from the three figures on their statement. It is an
   answer rather than a question, so it reads as a quiet panel under the
   inputs instead of as another field. */
.ss-estimate {
  margin-top: 1.1rem;
}

.ss-estimate-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.6rem;
  padding: 0.8rem 1rem;
  border-radius: 0.6rem;
  background: var(--brand-soft);
  color: var(--ink);
}

.ss-estimate-label {
  font-size: 0.8125rem;
  color: var(--muted);
}

.ss-estimate-value {
  font-weight: 600;
}

/* Nothing to work out yet, so it holds the shape and says what is
   missing rather than collapsing and moving the fields around. */
.ss-estimate-empty {
  background: transparent;
  padding-left: 0;
  padding-right: 0;
  color: var(--muted);
  font-size: 0.875rem;
}

/* Each person's half of the modal, so two people read as two blocks
   rather than as one long column of fields. */
.ss-person + .ss-person {
  margin-top: 2rem;
  padding-top: 1.75rem;
  border-top: 1px solid rgba(26, 43, 40, 0.1);
}

/* The withdrawal plan inside a spending phase modal: one row per account
   class, in the order the money is drawn in. */
.withdraw-rows {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.withdraw-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.withdraw-row-label {
  flex: 0 0 11rem;
  font-size: 0.875rem;
}

/* Wide enough for "100" beside the % sign. */
.withdraw-row-share {
  flex: 0 0 8rem;
}

.withdraw-row-move {
  flex: 0 0 2rem;
  text-align: center;
  color: var(--brand);
}

/* Two field rows in a row need air between them, or the second row's
   labels read as part of the first row's help text. */
.more-detail-body .field-row + .field-row,
.edit-modal-body .field-row + .field-row {
  margin-top: 1rem;
}

/* The Assumptions panel: one sentence per figure the app chose, each
   with a way to take it over. */
.assumption-groups {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.assumption-line {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid #f0f2f1;
}

.assumption-line:last-child {
  border-bottom: 0;
}

.assumption-text {
  flex: 1;
  font-size: 0.875rem;
  line-height: 1.45;
}

.assumption-change {
  flex: 0 0 auto;
  padding: 0;
  font-size: 0.8125rem;
  font-weight: 600;
}

/* What the plan leaves out sits below the figures, behind a rule, since
   none of it can be changed and the sentence above them promises the
   opposite. */
.assumption-limits {
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid #e4e9e7;
}

.assumption-limits-note {
  margin-bottom: 0.25rem;
  font-size: 0.8125rem;
  color: var(--muted);
}

.assumption-limit {
  padding: 0.6rem 0;
  border-bottom: 1px solid #f0f2f1;
}

.assumption-limit:last-child {
  border-bottom: 0;
}

/* Amber, not brand green: the badge counts the figures wearing the
   "Assumed" tag, so it wears the same colour they do. It is the app's
   existing caution pill (.health-pill.tier-warn), not a colour of its
   own — a figure the app chose is worth a look, not an error. */
.assumptions-count {
  display: inline-block;
  min-width: 1.4rem;
  padding: 0 0.35rem;
  border-radius: 0.7rem;
  background: #f7eede;
  color: var(--warn-dark);
  font-size: 0.75rem;
  font-weight: 700;
  text-align: center;
}

/* The workplace plan modal's three switches, set apart from the fields
   above them. */
.workplace-switches {
  margin-top: 1.25rem;
  padding-top: 0.75rem;
  border-top: 1px solid #eef1f0;
}

/* ---------------------------------------------------------------------
   The About box — what the product says about itself: which build this
   is, the two legal documents, and the copyright. It is why there is no
   longer a footer strip on the signed-in pages. Its head and width come
   from the shared modal look above; only the body is its own.
   --------------------------------------------------------------------- */
.about-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.35rem 1.5rem;
  margin: 0 0 1.5rem;
  font-size: 0.9375rem;
}

.about-facts dt {
  color: var(--muted);
  font-weight: 500;
}

.about-facts dd {
  margin: 0;
  font-weight: 600;
}

.about-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid #eef1f0;
  font-size: 0.9375rem;
  font-weight: 600;
}

.about-copyright {
  margin: 1.5rem 0 0;
  color: var(--muted);
  font-size: 0.875rem;
}

/* The landing page keeps the only footer left: a visitor there has
   accepted nothing and has no account menu to find the About box in. */
.landing-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.35rem 1.5rem;
}

.landing-footer-links {
  display: flex;
  gap: 1rem;
}

/* "Median" is used on every figure above this line, so it is defined
   once, here, rather than in each of them. */
.health-figures-note {
  margin: 1rem 0 0;
  color: var(--muted);
  font-size: 0.8125rem;
}

/* ---- Scenarios measured against the baseline (dashboard chunk 7) ---- */
/* The chips saying how this plan differs, under the title. Same pill
   shape as the fact strip above, so the two rows read as one family;
   these are buttons, since each opens the field it names. */
.difference-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 1.25rem;
}

.difference-lead {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--muted);
}

.difference-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.75rem;
  border: 1px solid #d8e2de;
  border-radius: 999px;
  background: #f4f8f6;
  font-size: 0.85rem;
  color: var(--ink);
  text-align: left;
}

button.difference-chip:hover,
button.difference-chip:focus-visible {
  border-color: var(--brand);
  background: #eaf2ee;
}

/* A phrase covering several fields has no modal to open, so it is not a
   button and must not look like one. */
.difference-chip-plain {
  background: #fff;
  color: var(--muted);
}

/* One figure's gap from the baseline's, under that figure's tile. */
.against-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.5rem;
  font-size: 0.8125rem;
  font-weight: 600;
}

.against-tag.better {
  color: var(--brand);
}

/* Amber, not red: a scenario doing worse than the baseline is a finding,
   not a fault — the whole point of trying one is to see which way it
   moves. Red is kept for a plan that actually fails. */
.against-tag.worse {
  color: var(--warn-dark);
}

/* ---- The plan switcher (dashboard chunk 7) ---- */
/* Past helpers' MaxListedScenarios the Plans list becomes one button and
   a menu. The button wears .sidebar-row so it sits in the same rhythm as
   the rows it replaces. */
.plan-switcher {
  width: 100%;
}

.plan-switcher-button {
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: transparent;
  text-align: left;
}

.plan-switcher-button[aria-expanded="true"] {
  background: rgba(255, 255, 255, 0.1);
}

/* To the RIGHT of the sidebar, not below the button: below would cover
   New scenario, which is exactly what someone with this many plans is
   most likely to want next. Bootstrap places it (.dropend, with fixed
   positioning set on the button), so it floats over the page rather
   than being clipped by the scrolling sidebar; z-index keeps it above
   the sidebar and the panels. */
.plan-switcher-menu {
  --bs-dropdown-min-width: 15rem;
  max-height: min(70vh, 30rem);
  overflow-y: auto;
  z-index: 1060;
}

/* .dropend draws its own caret after the toggle; the button carries a
   chevron already. */
.plan-switcher .dropdown-toggle::after,
.plan-switcher-button::after {
  display: none;
}

.plan-switcher-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.plan-switcher-item.active {
  background: #eaf2ee;
  color: var(--ink);
  font-weight: 600;
}

/* ---- Manage scenarios (dashboard chunk 7) ---- */
.scenarios-lead {
  max-width: 62ch;
  margin-bottom: 1.5rem;
}

/* The baseline card runs the full width above the grid: everything else
   on the page is measured against it, so it is not one of the crowd. */
.scenario-card-baseline {
  margin-bottom: 1.5rem;
}

.scenario-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr));
  gap: 1.25rem;
}

.scenario-card {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  border: 1px solid #e4e9e7;
  border-radius: 0.9rem;
  background: #fff;
  padding: 1.25rem 1.35rem;
}

.scenario-card-head {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
}

.scenario-card-title {
  flex: 1;
  min-width: 0;
}

.scenario-card-title .eyebrow {
  font-size: 0.7rem;
  color: var(--muted);
  letter-spacing: 0.06em;
}

.gauge-sm {
  flex-shrink: 0;
}

.scenario-card-menu {
  flex-shrink: 0;
}

.scenario-card-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.scenario-card-chart {
  border-top: 1px solid #f0f2f1;
  border-bottom: 1px solid #f0f2f1;
  padding: 0.75rem 0;
}

/* The plot's own box. Its bottom edge IS the $0 line — the chart's zero
   sits exactly on the bottom of the cropped viewBox — and its left edge
   stands in for the axis, so the scale can be read without drawing one. */
.card-chart-plot {
  position: relative;
  border-left: 1px solid #e4e9e7;
  border-bottom: 1px solid #cfd8d4;
  padding-left: 0.25rem;
}

/* The card's picture is lines only, stretched to the card: there is no
   text inside it to distort, which is what lets this one scale freely
   where the full charts cannot. Everything it needs said is said around
   it, in HTML, at the size the rest of the card is set in. */
.scenario-card-svg {
  display: block;
  width: 100%;
  height: 90px;
}

.card-chart-top,
.card-chart-zero {
  position: absolute;
  left: 0.35rem;
  font-size: 0.6875rem;
  color: var(--muted);
  background: #fff;
  padding-right: 0.2rem;
}

.card-chart-top {
  top: 0;
}

.card-chart-zero {
  bottom: 0;
}

.card-chart-years {
  display: flex;
  justify-content: space-between;
  margin-top: 0.2rem;
  font-size: 0.6875rem;
  color: var(--muted);
}

.scenario-card-chart-label {
  margin-top: 0.35rem;
  font-size: 0.75rem;
  color: var(--muted);
}

.scenario-card-foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-top: auto;
}

.scenario-card-unknown {
  font-size: 0.875rem;
  color: var(--muted);
}

/* The dashed "add one" card, matching the Quick screens' own. */
.scenario-card-new {
  align-items: center;
  justify-content: center;
  text-align: center;
  border: 1px dashed #c6d3cd;
  background: transparent;
  color: var(--brand);
  font-weight: 600;
  min-height: 13rem;
}

.scenario-card-new:hover,
.scenario-card-new:focus-visible {
  border-color: var(--brand);
  background: #f4f8f6;
}

.scenario-card-new .bi {
  font-size: 1.5rem;
}

.scenario-card-new-sub {
  font-weight: 400;
  font-size: 0.8125rem;
  color: var(--muted);
  max-width: 22ch;
}

/* ---- The app's own "are you sure?" (see components.ConfirmDialog) ---- */
/* A native <dialog>, so it sits in the browser's top layer above an open
   modal without any z-index of its own. The browser still centres it;
   these rules only give it the app's surface, type and spacing. */
.confirm-dialog {
  width: min(31rem, calc(100vw - 2rem));
  padding: 0;
  border: 0;
  border-radius: 0.9rem;
  background: #fff;
  color: var(--ink);
  box-shadow: 0 1.5rem 3rem rgba(26, 43, 40, 0.28);
}

/* Matching the dimming behind the dashboard's own overlays rather than
   the browser's default near-black. */
.confirm-dialog::backdrop {
  background: rgba(26, 43, 40, 0.45);
}

.confirm-dialog-form {
  padding: 2rem 2.25rem 1.75rem;
}

/* The icon square the app's modals use, in the warning colours rather
   than the brand's: this is a question about losing something. */
.confirm-dialog-head {
  display: flex;
  align-items: flex-start;
  gap: 1.25rem;
}

.confirm-dialog-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 3rem;
  height: 3rem;
  border-radius: 0.75rem;
  background: #f7eede;
  color: var(--warn-dark);
  font-size: 1.25rem;
}

.confirm-dialog-title {
  margin: 0;
  font-size: 1.35rem;
  line-height: 1.3;
}

.confirm-dialog-body {
  margin: 0.5rem 0 0;
  color: var(--muted);
  font-size: 1rem;
}

.confirm-dialog-body[hidden] {
  display: none;
}

.confirm-dialog-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-top: 2rem;
}

/* The stock-awards modal: the amount, what happens at vest, and where
   the shares go, with room between the three. One person per modal
   since 2026-09-25, so there is nothing to separate from anything. */
.edit-modal-body > .form-label {
  margin-top: 0.75rem;
}

.edit-modal-body > .choice-grid {
  margin-bottom: 1rem;
}

/* A group of extra-pay cards (bonus, stock awards) with its Add button
   under it, in the same rhythm the account groups have. */
.pay-section + .pay-section {
  margin-top: 1.25rem;
}

/* The "Grows with your pay" switch and the line under it explaining
   what it means both ways round. */
.grows-switch {
  margin-bottom: 1rem;
}

.grows-switch .form-text {
  margin-top: 0.1rem;
}

/* Out-of-date simulated figures (Chris, 2026-09-26). A thousand runs is
   the one thing the app does not redo on every edit, so the figures it
   produced stand until the header's Update results button is pressed —
   muted, and chipped, so nobody reads a stale number as a current one.
   Everything from the expected path is recomputed every render and is
   never marked. */
.is-stale {
  opacity: 0.45;
  filter: grayscale(0.7);
}

.stale-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.1rem 0.5rem;
  border-radius: 0.7rem;
  background: #f7eede;
  color: #8a6d1f;
  font-size: 0.7rem;
  font-weight: 600;
  white-space: nowrap;
}

/* The chip itself is the one thing in a muted block that stays legible. */
.is-stale .stale-chip {
  opacity: 1;
  filter: none;
}

.health-verdict.is-stale {
  opacity: 1;
  filter: none;
}

.health-verdict.is-stale .gauge,
.health-verdict.is-stale .health-verdict-text {
  opacity: 0.45;
  filter: grayscale(0.7);
}

.stat-health.is-stale .gauge,
.stat-health.is-stale .stat-status,
.stat-health.is-stale .stat-sub {
  opacity: 0.45;
  filter: grayscale(0.7);
}

.stat-health.is-stale {
  opacity: 1;
  filter: none;
}

/* The header's Update results button: quiet while there is nothing to
   update, and the brand outline the moment there is. */
.refresh-button {
  white-space: nowrap;
}

.health-pill.is-stale {
  opacity: 0.6;
}

/* Stock awards: everything about KEEPING shares goes when every share
   is sold at vest (Chris, 2026-09-26). There is nothing to keep, so
   there is nowhere for it to go and no withholding rate deciding how
   much survives. Driven off the radio itself, the way the Edit plan
   panel makes room for itself, so the fields follow the choice with no
   round trip and no script. They stay in the form and keep submitting;
   the save clears the account when every share is sold. */
.edit-modal-body:has(input[name="handling"][value="sell_all"]:checked) .award-keep {
  display: none;
}

/* The annuity modal asks about a contract that is either still growing
   or already paying, and the two shapes share nothing: a balance, a
   growth rate and a conversion age against a cheque and a tax-free
   share. Both halves stay in the form and keep submitting — so changing
   the choice back does not lose what was typed — and CSS shows whichever
   the radio selects, the same :has() technique the stock-awards modal
   uses to follow a choice with no round trip. */
.edit-modal-body:has(input[name="stage"][value="paying"]:checked) .annuity-growing,
.edit-modal-body:has(input[name="stage"][value="growing"]:checked) .annuity-paying {
  display: none;
}

/* A sale or purchase marked under its year on the by-account chart. */
.chart-event {
  cursor: pointer;
}

.chart-event:hover polygon,
.chart-event:focus-visible polygon {
  stroke: var(--ink);
}

.chart-event:focus {
  outline: none;
}

/* The property modal's sale (Chris's design, 2026-09-30). Keep it or
   Sell it is a pair of choice cards, and the sale's fields follow the
   choice in CSS, the same way the stock-awards modal hides where kept
   shares go: no round trip, no script, and the fields keep submitting
   (the save ignores them when the property is kept). */
.property-plan:not(:has(input[name="sale_plan"][value="sell"]:checked)) .property-sale {
  display: none;
}

/* Owned today or a planned purchase, the same way: a purchase's fields
   show only while it is chosen, and a cost basis only while it is not,
   since a purchase's basis is worked out from its price. */
.edit-modal-body:not(:has(input[name="ownership"][value="buy"]:checked)) .property-buy,
.edit-modal-body:has(input[name="financing"][value="cash"]:checked) .property-financed,
.edit-modal-body:has(input[name="ownership"][value="buy"]:checked) .property-own-only {
  display: none;
}

/* A tax-deferred or Roth account's IRA-only fields (after-tax money,
   a yearly amount) show while IRA is the kind chosen. */
.edit-modal-body:not(:has(input[name="plan_kind"][value="ira"]:checked)) .acct-ira-only {
  display: none;
}

/* The Roth conversions modal: the fields follow how much is converted,
   in CSS like the property modal's sale. None shows nothing more. */
.roth-conv:has(input[name="mode"][value=""]:checked) .roth-conv-fields,
.roth-conv:not(:has(input[name="mode"][value="fixed"]:checked)) .roth-conv-fixed,
.roth-conv:not(:has(input[name="mode"][value="fillBracket"]:checked)) .roth-conv-fill {
  display: none;
}

/* Short choice cards with no icon, three across: tighter than the
   usual, so a two-word title stays on one line beside the tick. */
.edit-modal-body .choice-grid-compact {
  gap: 0.75rem;
}

.edit-modal-body .choice-grid-compact .choice-card {
  padding: 1rem 1.1rem;
}

/* A mortgage's refinance follows its choice the same way. */
.debt-refi:not(:has(input[name="refi_plan"][value="refinance"]:checked)) .refi-fields {
  display: none;
}

.buy-summary {
  margin-top: 0.75rem;
  color: var(--bs-secondary-color);
}

.buy-summary:empty {
  display: none;
}

.buy-hint {
  margin-top: 0.35rem;
  color: var(--brand);
}

.property-plan .choice-grid + .property-sale {
  margin-top: 1.25rem;
}

/* "Sell at age [62] in [April] 2035": the timing as one sentence. */
.sale-when {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.05rem;
}

.sale-when label {
  margin: 0;
}

.sale-when .sale-age {
  width: 4.5rem;
  text-align: center;
}

.sale-when .sale-month {
  width: auto;
  /* Room for "September" and the menu's arrow beside it. */
  min-width: 10rem;
}

.sale-year {
  color: var(--muted);
}

/* The sale read as a receipt: the price, each cost on its own line with
   what it comes to on the right, and what is left in a band at the foot. */
.sale-receipt {
  margin-top: 1rem;
  border: 1px solid #e4e9e7;
  border-radius: 0.75rem;
  overflow: hidden;
}

.sale-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 1.1rem;
  border-top: 1px solid #f0f3f1;
}

.sale-line-head {
  border-top: 0;
  padding-top: 0.9rem;
  padding-bottom: 0.9rem;
  font-weight: 600;
}

.sale-label {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}

/* A name stays on one line; on a phone its rate box moves under it
   rather than squeezing it into three. */
.sale-label > label {
  white-space: nowrap;
}

.sale-amount {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.sale-line-head .sale-amount {
  font-size: 1.05rem;
}

.sale-cost {
  color: var(--danger-dark);
}

/* The receipt's boxes are small, so a line reads as a line of text with
   a figure in it rather than as a form field; the modal's own inputs are
   large on purpose, so these have to say so more specifically. Neither
   group may wrap: a "%" or "−$" dropping under its box is what broke
   the first version. */
.edit-modal-body .sale-receipt .form-control,
.edit-modal-body .sale-receipt .input-group-text {
  padding: 0.3rem 0.6rem;
  font-size: 1rem;
}

.sale-rate,
.sale-dollars {
  flex-wrap: nowrap;
}

.sale-rate {
  width: 6.5rem;
  flex: 0 0 auto;
}

.sale-rate .form-control {
  text-align: right;
}

.sale-dollars {
  margin-left: auto;
  width: 10.5rem;
}

.sale-dollars .input-group-text,
.sale-dollars .form-control {
  color: var(--danger-dark);
}

.sale-dollars .form-control {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.sale-error {
  padding: 0 1.1rem 0.5rem;
}

.sale-line .sale-error {
  flex-basis: 100%;
  padding: 0;
}

.sale-total {
  display: flex;
  align-items: center;
  padding: 1rem 1.1rem;
  background: var(--brand);
  color: #fff;
  font-weight: 600;
  font-size: 1.05rem;
}

.sale-total-amount {
  margin-left: auto;
  font-family: "Fraunces", Georgia, serif;
  font-size: 1.5rem;
  font-variant-numeric: tabular-nums;
}

.sale-gain {
  margin: 0.75rem 0 0;
  font-size: 0.875rem;
  color: var(--muted);
}

/* From tablet width up every receipt line is two columns: the name
   (with its rate box, tag and tip right after it) and the amount, so
   the figures line up down the right of the receipt. On a phone the
   line wraps instead. */
@media (min-width: 576px) {
  .sale-line {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 10.5rem;
    column-gap: 1rem;
  }

  .sale-line .sale-amount,
  .sale-line .sale-dollars {
    grid-column: 2;
    justify-self: end;
    margin-left: 0;
  }

  .sale-line .sale-dollars {
    width: 100%;
  }

  .sale-line .sale-error {
    grid-column: 1 / -1;
  }
}

/* The income statement: a sideways waterfall beside the statement, row
   for row (Chris's call, 2026-09-30). Both columns are cells of ONE
   grid, a statement cell and a chart cell per row, which is what keeps
   every bar level with its line however tall a line grows. The
   statement cell comes first in the page, so on a narrow screen, one
   column, each bar falls under its own line; from desktop width the
   chart cells are placed in the first column and `dense` packs each
   beside its row. Each cell draws its column's share of a panel's
   border, so the two columns read as the two panels every statement
   page has. */
.side-board {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.side-cell {
  background: #fff;
  border-left: 1px solid #e4e9e7;
  border-right: 1px solid #e4e9e7;
  padding: 0 1.25rem;
}

.side-cell.side-top {
  display: flex;
  border-top: 1px solid #e4e9e7;
  border-radius: 0.9rem 0.9rem 0 0;
  padding: 0;
}

/* The two headings fill their row, so the rule under each runs level
   even though the chart's carries a legend and the statement's does not. */
.side-top .panel-head {
  flex: 1;
  align-items: flex-start;
}

.side-cell.side-last {
  border-bottom: 1px solid #e4e9e7;
  border-radius: 0 0 0.9rem 0.9rem;
  padding-bottom: 1.25rem;
}

.side-cell.side-first {
  padding-top: 1.25rem;
}

/* The statement's own spacing moves onto the row, so both cells of a
   row are the same height and the bar centres on the line. */
.side-cell.side-heading:not(.side-first) {
  padding-top: 1.1rem;
}

.side-cell .section-label {
  margin: 0;
  padding-bottom: 0.6rem;
}

.side-cell.side-bottom {
  padding-top: 1rem;
}

.side-cell .statement-bottom {
  margin-top: 0;
}

.side-chart {
  position: relative;
  min-height: 1.75rem;
}

.side-svg {
  display: block;
  overflow: visible;
}

/* The chart cell holds its picture alone: the svg fills the whole row,
   padding included, so the running total's dashed line meets the next
   row's with no gap. */
.side-chart .side-svg {
  position: absolute;
  inset: 0 1.25rem;
  width: calc(100% - 2.5rem);
  height: 100%;
}

/* The same size as the statement's own amounts beside it (Chris,
   2026-09-30): the chart's figures were smaller, and read as an
   afterthought next to the list. */
.side-value {
  font-size: 1.3rem;
  font-weight: 600;
  fill: var(--ink);
  font-variant-numeric: tabular-nums;
  /* A white edge, so the zero line or a dashed running total passing
     behind a figure never cuts through it. */
  paint-order: stroke;
  stroke: #fff;
  stroke-width: 4px;
  stroke-linejoin: round;
}

.side-value-result {
  font-size: 1.4rem;
  font-weight: 700;
}

.side-legend {
  margin-top: 0.5rem;
}

/* One column: the chart's own header has nothing to head, since each
   bar sits under its line; the bars stay. */
@media (max-width: 1199.98px) {
  .side-chart.side-top {
    display: none;
  }

  .side-chart.side-heading,
  .side-chart.side-note {
    display: none;
  }

  .side-chart.side-footer {
    min-height: 0;
  }

  /* Between two lines' bars there is a line of text, so the running
     total's dashes would only show in pieces; and the amount is written
     on the line right above, so the bar's own figure says it twice. */
  .side-svg line,
  .side-svg .side-value {
    display: none;
  }

  .side-chart.side-line,
  .side-chart.side-total {
    min-height: 1.5rem;
  }

  .side-chart.side-bottom {
    min-height: 2.5rem;
  }

  .side-list.side-last {
    border-bottom: 0;
    border-radius: 0;
    padding-bottom: 0;
  }
}

@media (min-width: 1200px) {
  .side-board {
    grid-template-columns: minmax(0, 1.25fr) minmax(23rem, 1fr);
    grid-auto-flow: row dense;
    column-gap: 1rem;
  }

  .side-chart {
    grid-column: 1;
  }

  .side-list {
    grid-column: 2;
  }
}

/* ---- A plan's notes (pages.NotesButton / NotesModal) ---- */
/* Beside the plan's title, as a labelled pill either way: an icon alone
   was easy to miss. Outlined "Add notes" with none; solid "Notes" with
   some, so whether there is something to read shows at a glance. */
.plan-notes-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: 0.35rem;
  padding: 0.4rem 0.85rem;
  border: 1.5px solid var(--brand);
  border-radius: 999px;
  background: #fff;
  color: var(--brand);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  transition: background-color 0.15s, color 0.15s, box-shadow 0.15s;
}

.plan-notes-btn .bi {
  font-size: 1rem;
}

.plan-notes-btn:hover,
.plan-notes-btn:focus-visible {
  background: var(--brand-soft);
  box-shadow: 0 2px 8px -2px rgba(31, 111, 92, 0.35);
}

.plan-notes-btn.has-notes {
  background: var(--brand);
  color: #fff;
}

.plan-notes-btn.has-notes:hover,
.plan-notes-btn.has-notes:focus-visible {
  background: var(--brand-dark);
  border-color: var(--brand-dark);
  color: #fff;
}

/* The plan's name, then one line on what notes are for. */
.notes-intro {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin-bottom: 1.25rem;
  font-size: 0.95rem;
}

.notes-plan {
  font-family: "Fraunces", serif;
  font-weight: 600;
  font-size: 1.15rem;
  color: var(--ink);
}

/* Written in, not filled in: a tall box on the page's warm paper, with
   room between lines, so it reads as somewhere to think rather than a
   field to complete. Two classes, to outrank the edit modal's own
   control sizing. */
.edit-modal-body .notes-area {
  min-height: 20rem;
  padding: 1.1rem 1.25rem;
  font-size: 1.05rem;
  line-height: 1.65;
  background: var(--paper);
  border-color: #e4e9e7;
  border-radius: 0.85rem;
  resize: vertical;
}

.edit-modal-body .notes-area:focus {
  background: #fff;
  border-color: var(--brand-light);
  box-shadow: 0 0 0 0.25rem rgba(31, 111, 92, 0.15);
}

.edit-modal-body .notes-area::placeholder {
  color: #9aa8a4;
}

/* A tax-deferred withdrawal row drawn up to a bracket carries no share,
   so its share box goes while the menu names a bracket. It keeps
   submitting (the Advanced step's arrays have to stay aligned); the
   server ignores it. */
[data-share-row]:has(select.withdraw-bracket option:checked:not([value=""])) .withdraw-row-share {
  visibility: hidden;
}

/* The phase modal asks about drawing tax-deferred first in a section of
   its own above the shares (Chris, 2026-10-03). Chosen, the tax-deferred
   row says "Drawn first" where its box was, the same width so the arrows
   stay in line. */
.withdraw-fill-block {
  padding: 0.85rem 1rem;
  margin-bottom: 1.1rem;
  border-radius: 0.6rem;
  background: rgba(31, 111, 92, 0.06);
}

.withdraw-fill-block .withdraw-fill {
  margin-top: 0;
  width: 100%;
  max-width: 27rem;
}

.withdraw-sub {
  margin-bottom: 0.15rem;
}

/* Laid over the hidden box rather than in its place, so the row keeps
   its height. A visible child of a hidden parent still shows. */
.withdraw-row-first {
  display: none;
  position: absolute;
  inset: 0;
  align-items: center;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--brand);
  visibility: visible;
}

.share-group:has(select.withdraw-fill option:checked:not([value=""])) [data-fill-row] .withdraw-row-share {
  visibility: hidden;
}

.share-group:has(select.withdraw-fill option:checked:not([value=""])) [data-fill-row] .withdraw-row-first {
  display: flex;
}

/* The Advanced step's phase card puts it under the class name. */
.withdraw-bracket {
  width: auto;
  display: block;
  margin-top: 0.25rem;
}

/* ---- The withdrawal order tool (docs/plans/withdrawal-optimizer.md) ---- */
.tool-page {
  max-width: 56rem;
}

.tool-form .choice-grid {
  margin-bottom: 1rem;
}

.tool-heirs {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

.tool-heirs .form-text {
  flex-basis: 100%;
}

.tool-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-top: 1.25rem;
}

.tool-running {
  color: var(--bs-secondary-color);
}

.tool-result {
  margin-top: 2rem;
}

.tool-phase,
.tool-phase-compare {
  display: flex;
  gap: 1rem;
  padding: 0.75rem 0;
  border-top: 1px solid var(--bs-border-color);
}

.tool-phase-from {
  flex: 0 0 8rem;
  font-weight: 600;
}

.tool-phase-plans {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.tool-phase-tag {
  display: inline-block;
  min-width: 3.25rem;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--bs-secondary-color);
}

.tool-phase-tag-best {
  color: var(--brand);
}

.tool-phase-why {
  color: var(--bs-secondary-color);
  font-size: 0.9rem;
}

.tool-headline {
  font-size: 1.5rem;
  line-height: 1.3;
  margin-bottom: 1.25rem;
}

.tool-compare th[scope="row"] {
  font-weight: 500;
}

/* Bold, not green: a different figure is not always a better one (a
   plan that lasts longer can pay more tax on the way). */
.tool-compare .tool-differs {
  font-weight: 600;
}

/* ---- The Roth conversions tool (docs/plans/roth-conversion-optimizer.md) ---- */
.conv-safety {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.conv-safety > .fw-semibold {
  flex-basis: 100%;
}

.conv-tolerance {
  width: auto;
}

.conv-why {
  padding-left: 1.1rem;
  color: var(--bs-secondary-color);
}

.conv-why li + li {
  margin-top: 0.25rem;
}

.conv-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin-top: 0.5rem;
  color: var(--bs-secondary-color);
}

.conv-key {
  display: inline-block;
  width: 1.5rem;
  margin-right: 0.4rem;
  vertical-align: middle;
}

.conv-key-bar {
  height: 0.75rem;
  background: #7fb8a4;
  border-radius: 2px;
}

.conv-key-with {
  border-top: 2.5px solid #1a2b28;
}

.conv-key-without {
  border-top: 2px dashed #b5654a;
}

.conv-shortlist {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.conv-pick {
  display: grid;
  grid-template-columns: auto 1fr repeat(3, 7.5rem);
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.75rem;
  cursor: pointer;
}

.conv-pick:has(input:checked) {
  border-color: var(--brand);
  box-shadow: 0 0 0 1px var(--brand);
}

.conv-pick-current {
  background: var(--bs-tertiary-bg);
}

.conv-pick .form-check-input {
  margin: 0;
}

.conv-pick-fig {
  text-align: right;
}

@media (max-width: 767.98px) {
  .conv-pick {
    grid-template-columns: auto 1fr 1fr;
  }

  .conv-pick-rule {
    grid-column: 2 / -1;
  }

  .conv-pick-fig {
    text-align: left;
  }

  .conv-pick-rule + .conv-pick-fig {
    grid-column-start: 2;
  }
}

/* ---- Read-only visitors (docs/plans/demo-accounts.md and its read-only
   addendum): a demo household, or an account whose trial has ended ---- */
/* Where the tagline sits: why it is read-only, a demo's plan name, and
   the way out. */
.readonly-notice {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-left: 0.25rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.readonly-notice-tag {
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: #f7eede;
  color: var(--warn-dark);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.readonly-notice-cta {
  white-space: nowrap;
}

/* Not an error, so not the error toast's red. --calm is the toast for
   something that went right or is only worth knowing (toast.js
   "success" and "info", e.g. the contact address being copied). */
.app-toast--readonly,
.app-toast--calm {
  border-color: #cfe3db;
  border-left-color: var(--brand);
}

.app-toast--readonly .app-toast__icon,
.app-toast--calm .app-toast__icon {
  color: var(--brand);
}

/* ---- The landing page (redesigned 2026-10-05) ----
   Shows rather than describes: every section pairs a few words with a
   screenshot from the demo households, framed as an app window. One
   column on phones, the words beside the picture from tablet width up,
   alternating sides down the page. */
/* The landing page's sections run the window's full width, so a
   section's background (the hero's wash, the idea band) reaches its
   edges; their contents keep to the centred column (.lp-wrap). */
.lp-wrap {
  max-width: 76rem;
  margin-inline: auto;
  padding-inline: 1.5rem;
}

.lp-title,
.lp-h2,
.lp-h3,
.lp-cta-title,
.lp-section-lead,
.lp-lead {
  text-wrap: balance;
}

.lp-eyebrow {
  display: inline-block;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brand);
  margin-bottom: 0.75rem;
}

.lp-h2 {
  font-size: clamp(1.75rem, 3.2vw, 2.4rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin-bottom: 1rem;
}

.lp-h3 {
  font-size: 1.1rem;
  font-weight: 600;
  margin-bottom: 0.4rem;
}

.lp-btn {
  padding-inline: 1.75rem;
}

.lp-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.lp-actions-center {
  justify-content: center;
}

/* The hero: the promise, two ways in, and the product itself, large,
   rising out of a soft green wash. */
.lp-hero {
  padding-block: 4.5rem 3rem;
  text-align: center;
}

.lp-hero {
  background:
    radial-gradient(60rem 26rem at 50% 0%, rgba(111, 179, 161, 0.22), transparent 70%),
    linear-gradient(180deg, var(--paper) 0%, #f2f7f5 60%, var(--paper) 100%);
}

.lp-hero-text {
  max-width: 46rem;
  margin: 0 auto 3rem;
}

.lp-title {
  font-size: clamp(2.4rem, 5.6vw, 4rem);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.02em;
  margin-bottom: 1.25rem;
}

.lp-lead {
  font-size: clamp(1.05rem, 1.6vw, 1.25rem);
  color: var(--muted);
  max-width: 38rem;
  margin: 0 auto 2rem;
}

.lp-hero .lp-actions {
  justify-content: center;
}

.lp-fineprint {
  margin: 1rem 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}

/* A screenshot in a window frame: a thin bar with three dots, rounded
   corners, and a long soft shadow so it lifts off the page. */
.lp-shot {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  text-align: left;
  cursor: zoom-in;
  overflow: hidden;
  border: 1px solid #dfe7e4;
  border-radius: 0.9rem;
  background: #fff;
  box-shadow:
    0 1px 2px rgba(22, 79, 66, 0.06),
    0 24px 60px -24px rgba(22, 79, 66, 0.35);
}

.lp-shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* A single panel is shown as itself, square: rounding clipped the text
   at its corners. */
.lp-shot-flat {
  border-radius: 0;
}

/* The enlarge mark, in the corner while the pointer is on a
   screenshot or it has the keyboard. */
.lp-shot-zoom {
  position: absolute;
  right: 0.75rem;
  bottom: 0.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: rgba(22, 79, 66, 0.88);
  color: #fff;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.lp-shot:hover .lp-shot-zoom,
.lp-shot:focus-visible .lp-shot-zoom {
  opacity: 1;
}

.lp-shot:focus-visible {
  outline: 3px solid var(--brand-light);
  outline-offset: 3px;
}

/* A screenshot enlarged: as large as the window allows, on a dark
   backdrop, and a click anywhere closes it. */
/* The frame is exactly the picture's size: as wide as the window allows
   and no taller than it, whichever binds first. */
.lp-zoom .lp-zoom-dialog {
  width: fit-content;
  max-width: none;
  margin-inline: auto;
  cursor: zoom-out;
}

.lp-zoom .lp-zoom-content {
  border: 0;
  border-radius: 0.5rem;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.6);
}

.lp-zoom .lp-zoom-content img {
  display: block;
  width: auto;
  height: auto;
  max-width: min(94vw, 1600px);
  max-height: 90vh;
}

body:has(.lp-zoom.show) .modal-backdrop.show {
  opacity: 0.75;
}

/* A hyphenated word in a heading stays on one line ("what-if"). */
.lp-nowrap {
  white-space: nowrap;
}

.lp-shot-bar {
  display: flex;
  gap: 0.4rem;
  padding: 0.6rem 0.8rem;
  border-bottom: 1px solid #edf1ef;
  background: #f8faf9;
}

.lp-shot-bar span {
  display: block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: #dbe3e0;
}

.lp-shot-hero {
  max-width: 68rem;
  margin-inline: auto;
}

/* Section headings, centred over what they introduce. */
.lp-section-head {
  max-width: 44rem;
  margin: 0 auto 2.5rem;
  text-align: center;
}

.lp-section-lead {
  font-size: 1.1rem;
  color: var(--muted);
  margin: 0;
}

/* The two demo households. */
.lp-demos {
  padding-block: 4.5rem 3rem;
}

.lp-demo-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
  max-width: 64rem;
  margin-inline: auto;
}

@media (min-width: 768px) {
  .lp-demo-grid {
    grid-template-columns: 1fr 1fr;
  }
}

.lp-demo-card {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1.1rem;
  align-items: start;
  padding: 1.6rem;
  border: 1px solid #dfe7e4;
  border-radius: 1rem;
  background: #fff;
  color: var(--ink);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.lp-demo-card:hover,
.lp-demo-card:focus-visible {
  border-color: var(--brand);
  box-shadow: 0 18px 40px -22px rgba(22, 79, 66, 0.45);
  transform: translateY(-2px);
  color: var(--ink);
}

.lp-demo-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
}

.lp-demo-body {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-width: 0;
}

.lp-demo-name {
  font-size: 1.3rem;
  font-weight: 600;
}

.lp-demo-summary {
  color: var(--muted);
}

.lp-demo-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.35rem;
}

.lp-demo-fact {
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-size: 0.8rem;
  font-weight: 500;
}

.lp-demo-open {
  grid-column: 2;
  margin-top: 0.25rem;
  color: var(--brand);
  font-weight: 600;
}

.lp-demo-card:hover .lp-demo-open i {
  transform: translateX(3px);
}

.lp-demo-open i {
  display: inline-block;
  transition: transform 0.15s ease;
}

/* A feature: the words and a screenshot, side by side from tablet width
   up, and the screenshot first on the flipped rows. */
.lp-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  align-items: center;
  padding-block: 4rem;
}

@media (min-width: 992px) {
  .lp-row {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: 4rem;
  }

  .lp-row-flip {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  }

  .lp-row-flip .lp-row-text {
    order: 2;
  }
}

.lp-row-lead {
  font-size: 1.1rem;
  color: var(--muted);
  margin-bottom: 1.5rem;
}

.lp-points {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0.7rem;
}

.lp-points li {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
}

.lp-points i {
  color: var(--brand);
  font-size: 1.1rem;
  flex-shrink: 0;
}

/* Two statements, one in front of the other. */
.lp-stack {
  position: relative;
  padding: 0 0 12% 18%;
}

.lp-stack-back {
  position: absolute;
  top: 8%;
  left: 0;
  width: 70%;
  opacity: 0.95;
}

.lp-stack-front {
  position: relative;
  width: 82%;
  margin-left: auto;
}

/* The business idea: four decisions on a green-tinted band. */
.lp-band {
  padding-block: 4.5rem;
}

.lp-band {
  background: #f1f6f4;
}

.lp-decisions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}

@media (min-width: 768px) {
  .lp-decisions {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1200px) {
  .lp-decisions {
    grid-template-columns: repeat(4, 1fr);
  }
}

.lp-decision {
  padding: 1.5rem;
  border: 1px solid #e1e9e6;
  border-radius: 1rem;
  background: #fff;
}

.lp-decision p,
.lp-step p {
  margin: 0;
  color: var(--muted);
}

.lp-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-bottom: 1rem;
  border-radius: 0.75rem;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 1.25rem;
}

/* Everything the projection covers, as a quiet grid. */
.lp-included {
  padding-block: 4.5rem 3rem;
}

.lp-included-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem 2rem;
}

@media (min-width: 768px) {
  .lp-included-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1200px) {
  .lp-included-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.lp-included-item {
  display: flex;
  gap: 0.9rem;
  align-items: flex-start;
}

.lp-included-item > i {
  color: var(--brand);
  font-size: 1.35rem;
  line-height: 1.2;
}

.lp-included-title {
  font-weight: 600;
}

.lp-included-text {
  color: var(--muted);
  font-size: 0.95rem;
}

/* How it works: three numbered steps. */
.lp-steps-section {
  padding-block: 3rem 4rem;
}

.lp-steps {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

@media (min-width: 768px) {
  .lp-steps {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1200px) {
  .lp-steps {
    grid-template-columns: repeat(4, 1fr);
  }
}

.lp-step {
  padding: 1.5rem;
  border-top: 3px solid var(--brand);
  background: #fff;
  border-radius: 0 0 1rem 1rem;
  box-shadow: 0 1px 2px rgba(22, 79, 66, 0.06);
}

.lp-step-num {
  display: block;
  margin-bottom: 0.5rem;
  font-family: "Fraunces", serif;
  font-size: 2rem;
  font-weight: 600;
  color: var(--brand);
  line-height: 1;
}

/* The closing invitation: a deep green band. */
.lp-cta {
  margin-block: 1rem 4.5rem;
  padding: 4rem 1.5rem;
  border-radius: 1.25rem;
  text-align: center;
  color: #fff;
  background:
    radial-gradient(40rem 18rem at 80% 0%, rgba(111, 179, 161, 0.35), transparent 70%),
    var(--brand-dark);
}

.lp-cta-title {
  font-size: clamp(1.9rem, 3.4vw, 2.6rem);
  font-weight: 600;
  margin-bottom: 0.75rem;
}

.lp-cta-note {
  color: rgba(255, 255, 255, 0.82);
  margin-bottom: 2rem;
}

@media (max-width: 575.98px) {
  .lp-wrap {
    padding-inline: 0.9rem;
  }

  .lp-hero {
    padding-block: 3rem 2rem;
  }

  .lp-row,
  .lp-demos,
  .lp-band,
  .lp-included {
    padding-block: 3rem;
  }

  .lp-actions .lp-btn {
    flex: 1 1 100%;
  }

  .lp-demo-card {
    padding: 1.25rem;
  }

  .lp-stack {
    padding: 0 0 10% 10%;
  }
}

/* ---- Out-of-date results (Chris, 2026-10-05) ---- */
/* The line at the top of a page whose simulated figures predate the last
   change, pointing to the header's Update results. Amber like the stale chip: not
   an error, something to do. */
.stale-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 0.9rem;
  margin-bottom: 1rem;
  padding: 0.7rem 1rem;
  border: 1px solid #ecd9b4;
  border-radius: 0.75rem;
  background: #fbf3e4;
  color: var(--warn-dark);
}

.stale-notice-text {
  flex: 1 1 18rem;
  font-size: 0.92rem;
}

/* The header's button the moment it has something to do: one soft pulse
   when it arrives, so the eye goes to it, and no more. */
.refresh-button-due {
  animation: refresh-due 1.6s ease-out 1;
}

@keyframes refresh-due {
  0% {
    box-shadow: 0 0 0 0 rgba(31, 111, 92, 0.55);
  }
  100% {
    box-shadow: 0 0 0 12px rgba(31, 111, 92, 0);
  }
}

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

/* ---- A demo's Things to try (docs/plans/demo-accounts.md) ---- */
.demo-try {
  padding: 1.1rem 1.25rem;
}

.demo-try-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.9rem;
  margin-bottom: 0.9rem;
}

.demo-try-sub {
  color: var(--muted);
  font-size: 0.9rem;
}

.demo-try-grid {
  display: grid;
  /* As many across as the space allows, not the window: with the Edit
     plan and Help panels both open the page is far narrower than the
     screen. */
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 0.6rem;
}

.demo-try-link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid #e1e9e6;
  border-radius: 0.65rem;
  background: var(--brand-soft);
  color: var(--ink);
  font-weight: 500;
  font-size: 0.92rem;
  line-height: 1.3;
}

.demo-try-link > .bi:first-child {
  color: var(--brand);
  font-size: 1.1rem;
}

.demo-try-link:hover,
.demo-try-link:focus-visible {
  border-color: var(--brand);
  color: var(--ink);
}

.demo-try-arrow {
  margin-left: auto;
  color: var(--brand);
}

.demo-try-note {
  margin-top: 0.9rem;
  color: var(--muted);
  font-size: 0.88rem;
}

/* ---- The Help system (docs/plans/help-system.md) ---- */
/* The Help row: alone at the foot of the sidebar, apart from the rows
   that navigate, with the key that opens it. */
.sidebar-help {
  margin-top: auto;
  padding-top: 0.75rem;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.sidebar-kbd {
  padding: 0.05rem 0.4rem;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 0.3rem;
  background: transparent;
  color: rgba(255, 255, 255, 0.75);
  font-size: 0.72rem;
}

/* The panel shares the other two panels' edge and width. */
.help-panel-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.help-search {
  position: relative;
}

.help-search .bi-search {
  position: absolute;
  top: 50%;
  left: 0.75rem;
  transform: translateY(-50%);
  color: var(--muted);
}

.help-search .form-control {
  padding-left: 2.1rem;
}

.help-crumbs {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--muted);
}

.help-crumbs .bi {
  font-size: 0.7rem;
}

/* The breadcrumb, the eyebrow and the title, each with room above the
   next: the eyebrow belongs to the title, so it sits closer to it than to
   the breadcrumb. */
#help-panel-content > .help-crumbs {
  margin-bottom: 1.25rem;
}

#help-panel-content > .eyebrow {
  margin-bottom: 0.5rem;
}

.help-title {
  margin: 0 0 1rem;
}

.help-article {
  font-size: 0.95rem;
  line-height: 1.55;
}

/* A section heading: clearly larger than the text and the step headings
   under it, so a new section reads as one (Chris, 2026-10-07). */
/* The Glossary page: every term a row that opens to its definition,
   as Plan health's "Worth a look" cards do, in the help's own colors. */
.help-glossary {
  border-top: 1px solid #e0e6e3;
  margin: 1rem 0 1.5rem;
}

.help-glossary-item {
  border-bottom: 1px solid #e0e6e3;
}

.help-glossary-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  padding: 0.75rem 0.25rem;
  border: 0;
  background: transparent;
  text-align: left;
  font-weight: 600;
  color: var(--ink);
}

.help-glossary-head:hover {
  color: var(--brand);
}

.help-glossary-chevron {
  flex-shrink: 0;
  color: var(--muted);
  transition: transform 0.15s ease;
}

.help-glossary-head:not(.collapsed) .help-glossary-chevron {
  transform: rotate(90deg);
}

.help-glossary-body {
  padding: 0 0.25rem 0.9rem;
}

.help-glossary-body p:last-child {
  margin-bottom: 0;
}

.help-article h2 {
  margin: 1.75rem 0 0.5rem;
  font-size: 1.2rem;
  font-weight: 600;
}

/* A step under a section heading, such as Getting started's seven: one
   size down from the section, so the heading above reads as what they
   belong to. */
.help-article h3 {
  margin: 1rem 0 0.3rem;
  font-size: 0.95rem;
  font-weight: 600;
}

.help-article p,
.help-article ul,
.help-article ol {
  margin-bottom: 0.75rem;
}

.help-article a {
  color: var(--brand);
  font-weight: 500;
}

/* A glossary term: a dotted rule under it, and its popover under it. */
.help-term {
  border-bottom: 1px dotted currentColor;
}

.help-popover {
  display: none;
}

/* The popover floats over the text below the term rather than sitting in
   the line, which pushed the rest of the sentence down. Absolutely placed
   with no top, it starts on the term's own line, so it is moved down a
   line; it spans the article's width. */
.help-article {
  position: relative;
}

.help-popover.open {
  display: block;
  position: absolute;
  left: 0;
  right: 0;
  z-index: 20;
  margin-top: 1.6em;
  padding: 0.75rem 0.9rem;
  border: 1px solid #dfe7e4;
  border-radius: 0.6rem;
  background: #fff;
  box-shadow: 0 6px 18px rgba(26, 43, 40, 0.1);
  font-size: 0.88rem;
}

.help-popover-inner {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.help-popover-title {
  font-weight: 600;
  color: var(--ink);
}

.help-popover-body p {
  margin: 0;
}

.help-popover-yours {
  color: var(--brand-dark);
  font-weight: 600;
}

/* The worked table: the plan's own numbers, a note under each label,
   the amount on the right, each group's total ruled off. */
.help-worked {
  margin: 0.75rem 0 1rem;
}

.help-worked-table {
  width: 100%;
  margin-bottom: 0.75rem;
  font-size: 0.9rem;
}

.help-worked-table caption {
  caption-side: top;
  padding: 0 0 0.3rem;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.help-worked-table th {
  padding: 0.35rem 0.5rem 0.35rem 0;
  font-weight: 500;
}

.help-worked-table td {
  padding: 0.35rem 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.help-worked-note {
  display: block;
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 400;
}

.help-worked-memo {
  color: var(--muted);
}

.help-worked-total th,
.help-worked-total td {
  border-top: 1px solid var(--ink);
  font-weight: 700;
}

.help-related {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding-top: 0.75rem;
  border-top: 1px solid #e6ebe9;
}

.help-related-head,
.help-rail-head,
.help-contents-head {
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.help-related a,
.help-rail-link {
  color: var(--brand);
  font-size: 0.9rem;
}

.help-vote {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding-top: 0.75rem;
}

.help-vote-why {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  width: 100%;
}

.help-vote-why .btn {
  align-self: flex-start;
}

.help-foot {
  margin-top: auto;
  padding-top: 0.75rem;
  border-top: 1px solid #e6ebe9;
}

.help-ask {
  padding: 0.75rem;
  border-radius: 0.6rem;
  background: var(--brand-soft);
}

/* Search results. */
.help-hits,
.help-center-hits {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.help-hit {
  display: flex;
  gap: 0.6rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid #e6ebe9;
  border-radius: 0.6rem;
  background: #fff;
  color: var(--ink);
}

.help-hit:hover {
  border-color: var(--brand-light);
}

.help-hit .bi {
  color: var(--brand);
}

.help-hit-title {
  display: block;
  font-weight: 600;
}

.help-hit-snippet {
  display: block;
  color: var(--muted);
  font-size: 0.85rem;
}

/* The ? beside a figure: a small circle, quiet until the row is hovered,
   always shown on a touch screen. */
.help-hook {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-left: 0.35rem;
  border: 1px solid #cdd8d4;
  border-radius: 50%;
  background: #fff;
  color: var(--muted);
  font-family: "Inter", sans-serif;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  vertical-align: middle;
}

.help-hook:hover,
.help-hook:focus-visible {
  border-color: var(--brand);
  color: var(--brand);
}

/* A line's ? follows its label, shown when the line is pointed at. It
   sits above an editable line's cover button, so it opens its article
   rather than the line's form. */
.statement-row .help-hook {
  position: relative;
  z-index: 2;
  margin-left: 0.4rem;
  vertical-align: middle;
  opacity: 0;
}

.statement-row:hover .help-hook,
.statement-row .help-hook:focus-visible {
  opacity: 1;
}

@media (hover: none) {
  .statement-row .help-hook {
    opacity: 1;
  }
}

.stat-with-help {
  position: relative;
}

.stat-with-help > .help-hook {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
}

/* The Help center: contents, article, rail; stacked below md. */
.help-center {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

/* The three columns share the space in proportion, the article four
   times the contents and the rail three quarters of them (Chris, 2026-10-07), so the contents and the rail grow with the window
   too; each side keeps a floor so its links never wrap tightly (Chris,
   2026-10-06: fixed sides left them narrow beside a wide article). */
@media (min-width: 768px) {
  .help-center {
    grid-template-columns: minmax(15rem, 1fr) minmax(0, 4fr);
  }
}

@media (min-width: 1200px) {
  .help-center {
    grid-template-columns: minmax(15rem, 1fr) minmax(0, 4fr) minmax(16.5rem, 0.75fr);
  }
}

.help-contents {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.help-contents-group {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.help-contents-link {
  padding: 0.2rem 0.5rem;
  border-radius: 0.4rem;
  color: var(--ink);
  font-size: 0.9rem;
}

.help-contents-link:hover {
  color: var(--brand);
}

.help-contents-link.active {
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-weight: 600;
}

.help-center-main {
  min-width: 0;
  padding: 1.5rem;
  border: 1px solid #e6ebe9;
  border-radius: 0.9rem;
  background: #fff;
}

.help-switch {
  margin-bottom: 1rem;
}

.help-rail {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

/* The same space before a heading as the contents leave between one
   group and the next: the flex gap plus this comes to 1rem. */
.help-rail-head:not(:first-child) {
  margin-top: 0.65rem;
}

.help-rail .help-ask {
  margin-top: 1rem;
}

/* The Help row is a button, which brings a border and a background the
   sidebar's link rows do not have. */
.sidebar-help .sidebar-row {
  border: 0;
  background: transparent;
}

.sidebar-help .sidebar-row:hover,
.sidebar-help .sidebar-row.active {
  background: rgba(255, 255, 255, 0.10);
}

/* Help beside Edit plan or Assumptions on a wide screen: so a person can
   read about a field while looking at it, Help opens beside the panel
   already open rather than in its place, and the page makes room for
   both. Help always keeps the right edge (Chris, 2026-10-06): the other
   panel moves left to make room for it. Narrower, two panels do not fit
   and help.js closes the other one instead. */
@media (min-width: 1400px) {
  .dashboard-stage:has(.help-panel.show, .help-panel.showing) #plan-panel,
  .dashboard-stage:has(.help-panel.show, .help-panel.showing) #assumptions-panel {
    right: 27.5rem;
  }

  .dashboard-stage:has(#plan-panel.show, #plan-panel.showing, #assumptions-panel.show, #assumptions-panel.showing):has(.help-panel.show, .help-panel.showing) .dashboard-content {
    margin-right: 55rem;
  }
}

/* A help screenshot: the article's width, framed, opening full size in a
   new tab when clicked. */
.help-shot {
  display: block;
  margin: 0.5rem 0 1rem;
  border: 1px solid #e0e6e3;
  border-radius: 0.6rem;
  overflow: hidden;
  background: #fff;
  cursor: zoom-in;
}

.help-shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* In the side panel a screenshot is too small to read, so it is a line
   opening the article in the Help center instead. */
.help-shot-link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.5rem 0 1rem;
  padding: 0.7rem 0.9rem;
  border: 1px dashed #cbd6d1;
  border-radius: 0.6rem;
  background: #fff;
  font-weight: 600;
}

.help-shot-link .bi {
  font-size: 1.15rem;
}

/* The account menu is a small picture; it reads at its own size. */
.help-shot:has(img[src$="account-menu.webp"]) {
  max-width: 20rem;
}

/* The Edit plan panel is tall and narrow: at the article's full width it
   came out several screens tall. It is sized as a share of the column so
   its numbered bubbles show the same size as the full-page pictures' at
   any window width (Chris, 2026-10-09). A full-page picture is 1600
   pixels wide with 37.8-pixel bubbles; this one is 930 wide with 68:
   930 x 37.8 / (68 x 1600) = 32.3%. Retaking the pictures at another
   size means working this out again. */
.help-shot:has(img[src$="edit-plan.webp"]) {
  max-width: 60%;
}

/* From tablet width up the numbered list sits beside it rather than
   under it (Chris, 2026-10-09): the picture is tall and narrow, so the
   list fills the space at its side. The line holding the picture floats
   at the 32.3% above, and the list beside it keeps to its own column
   (overflow: hidden) rather than wrapping under the picture; the next
   heading starts below both. */
@media (min-width: 768px) {
  .help-article p:has(> .help-shot img[src$="edit-plan.webp"]) {
    float: left;
    width: 32.3%;
    margin: 0.5rem 1.75rem 1rem 0;
  }

  .help-article p:has(> .help-shot img[src$="edit-plan.webp"]) > .help-shot {
    max-width: 100%;
    margin: 0;
  }

  .help-article p:has(> .help-shot img[src$="edit-plan.webp"]) + ol {
    overflow: hidden;
  }

  .help-article h2 {
    clear: both;
  }
}

/* A numbered list under a screenshot: room between items, numbers in the
   screenshots' own orange so the two read as one. */
.help-article ol li {
  margin-bottom: 0.35rem;
}

.help-article ol li::marker {
  color: #c46a00;
  font-weight: 700;
}

/* The Admin section (docs/plans/admin.md): a working page under the
   platform header, in the dashboard's own panels, tiles and pills. */
.admin-shell {
  max-width: 76rem;
  margin: 0 auto;
  padding: 2.5rem 0 4rem;
}

/* Every Admin page: the menu down the left from large screens up, a
   row along the top below that, the page beside it. */
.admin-frame {
  max-width: 88rem;
}

@media (min-width: 992px) {
  .admin-frame {
    display: grid;
    grid-template-columns: 13rem minmax(0, 1fr);
    gap: 3rem;
    align-items: start;
  }

  .admin-menu {
    position: sticky;
    top: 5.5rem;
  }
}

.admin-main {
  min-width: 0;
}

.admin-menu {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 2rem;
}

@media (min-width: 992px) {
  .admin-menu {
    flex-direction: column;
    flex-wrap: nowrap;
    margin-bottom: 0;
  }
}

.admin-menu-title {
  width: 100%;
  margin-bottom: 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brand);
}

.admin-menu-item {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.6rem 0.85rem;
  border-radius: 0.6rem;
  color: var(--ink);
  font-weight: 500;
  transition: background-color 0.15s ease;
}

.admin-menu-item .bi {
  color: var(--muted);
}

.admin-menu-item:hover,
.admin-menu-item:focus {
  background: #eef1f0;
  color: var(--ink);
}

.admin-menu-item.active {
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-weight: 600;
}

.admin-menu-item.active .bi {
  color: var(--brand);
}

.admin-head {
  margin-bottom: 2rem;
}

.admin-head h1 {
  font-size: 2.25rem;
}

.admin-account-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem;
  margin-top: 1.25rem;
}

/* The counts over the list, and an account's facts: a row of tiles. */
.admin-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
  margin-bottom: 2rem;
}

@media (min-width: 768px) {
  .admin-stats {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

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

.admin-stat {
  background: #fff;
  border: 1px solid #e4e9e7;
  border-radius: 0.9rem;
  padding: 1.1rem 1.25rem;
}

.admin-stat-value {
  font-size: 2rem;
  font-weight: 600;
  line-height: 1.1;
  color: var(--brand-dark);
}

.admin-stat-warn .admin-stat-value {
  color: var(--warn-dark);
}

.admin-stat-label {
  margin-top: 0.25rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.admin-fact-value {
  margin-top: 0.4rem;
  font-size: 1.05rem;
  font-weight: 600;
}

/* The Accounts page's Active and Deleted tabs. */
.admin-tabs {
  display: inline-flex;
  gap: 0.25rem;
  margin-bottom: 1.25rem;
  padding: 0.3rem;
  border: 1px solid #e4e9e7;
  border-radius: 999px;
  background: #fff;
}

.admin-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 1.1rem;
  border-radius: 999px;
  color: var(--muted);
  font-weight: 600;
}

/* Three tabs (the Beta page's) are wider than a phone: tighter there,
   never wider than the page. */
@media (max-width: 575.98px) {
  .admin-tabs {
    max-width: 100%;
  }

  .admin-tab {
    padding: 0.45rem 0.7rem;
    gap: 0.35rem;
  }
}

.admin-tab:hover,
.admin-tab:focus {
  color: var(--ink);
}

.admin-tab.active {
  background: var(--brand);
  color: #fff;
}

.admin-tab-count {
  padding: 0.05rem 0.5rem;
  border-radius: 999px;
  background: #eef1f0;
  color: var(--ink);
  font-size: 0.8rem;
}

.admin-tab.active .admin-tab-count {
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
}

.admin-table {
  --bs-table-bg: transparent;
}

.admin-table th {
  padding: 0.9rem 1rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
  border-bottom-color: #eef1f0;
}

.admin-table td {
  padding: 1rem;
  border-bottom-color: #eef1f0;
  white-space: nowrap;
}

.admin-table tbody tr:last-child td {
  border-bottom: 0;
}

/* A whole row opens the account: the name's stretched link covers it. */
.admin-row {
  position: relative;
  transition: background-color 0.15s ease;
}

.admin-row:hover {
  background: var(--brand-soft);
}

.admin-who {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}

.admin-avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-weight: 600;
}

/* A deleted account wears gray, not the brand's green. */
.admin-avatar-gone {
  background: #eef1f0;
  color: var(--muted);
}

.admin-pill-gone {
  background: #eef1f0;
  color: var(--muted);
}

.admin-reason {
  max-width: 22rem;
  white-space: normal;
}

.admin-quote {
  margin: 0 0 1.25rem;
  padding: 1rem 1.25rem;
  border-left: 3px solid var(--brand-light);
  border-radius: 0 0.6rem 0.6rem 0;
  background: var(--brand-soft);
}

.admin-quote blockquote {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
  font-size: 1.1rem;
}

.admin-quote figcaption {
  margin-top: 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.admin-avatar-lg {
  width: 3.5rem;
  height: 3.5rem;
  font-size: 1.4rem;
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
}

.admin-name {
  color: var(--ink);
  font-weight: 600;
}

.admin-name:hover,
.admin-name:focus {
  color: var(--brand-dark);
}

.admin-email,
.admin-muted {
  color: var(--muted);
}

.admin-email {
  font-size: 0.875rem;
}

.admin-col-go {
  width: 2.5rem;
  text-align: right;
  color: var(--muted);
}

.admin-row:hover .admin-col-go {
  color: var(--brand);
}

.admin-label {
  display: block;
  margin-bottom: 0.6rem;
  font-size: 1rem;
  font-weight: 600;
}

/* Trial and Subscriber: one above the other on a phone, side by side
   from tablet width up, where each description fits on one line. */
.choice-grid.admin-plan-choices {
  grid-template-columns: minmax(0, 1fr);
  gap: 0.85rem;
}

@media (min-width: 768px) {
  .choice-grid.admin-plan-choices {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.admin-plan-choices .choice-card {
  padding: 1rem 1.1rem;
}

.admin-plan-choices .choice-card-body {
  min-width: 0;
  padding-right: 1.5rem;
}

.admin-plan-choices .choice-card-text {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.admin-date {
  max-width: 12rem;
}

.admin-shell .form-control:focus {
  border-color: var(--brand-light);
  box-shadow: 0 0 0 0.25rem rgba(31, 111, 92, 0.2);
}

/* An account's history: newest first, each change a step on a line. */
.admin-history {
  list-style: none;
  margin: 0;
  padding: 0;
}

.admin-history > li {
  position: relative;
  padding: 0 0 1.5rem 1.5rem;
  border-left: 2px solid var(--brand-soft);
  margin-left: 0.4rem;
}

.admin-history > li:last-child {
  padding-bottom: 0;
  border-left-color: transparent;
}

.admin-history > li::before {
  content: "";
  position: absolute;
  left: -0.45rem;
  top: 0.2rem;
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--brand);
}

.admin-history-when {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted);
}

.admin-history-change {
  margin-top: 0.3rem;
}

.admin-history-note {
  margin-top: 0.35rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  background: var(--paper);
  font-size: 0.9rem;
}

.admin-history-by {
  margin-top: 0.35rem;
  font-size: 0.8rem;
  color: var(--muted);
}

/* The Agreements panel: the versions on the account, then every consent
   record on the same line-and-dot history as plan changes. */
.admin-accepted {
  display: grid;
  gap: 1rem;
  margin-bottom: 2rem;
}

@media (min-width: 768px) {
  .admin-accepted {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.admin-doc-link {
  color: var(--brand);
  font-weight: 600;
}

.admin-doc-link:hover,
.admin-doc-link:focus {
  color: var(--brand-dark);
}

.admin-readonly-text,
.admin-owes {
  color: var(--warn-dark);
}

.admin-owes {
  margin-top: 0.5rem;
  font-size: 0.875rem;
  font-weight: 600;
}

.admin-subhead {
  margin-bottom: 1.25rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.admin-consent-docs {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
}

.admin-consent-docs li {
  margin-bottom: 0.75rem;
}

.admin-consent-check {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.75rem;
  margin-top: 0.2rem;
  font-size: 0.85rem;
}

.admin-check-good {
  color: var(--brand);
}

.admin-check-bad {
  color: var(--danger);
  font-weight: 600;
}

.admin-fingerprint {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
  color: var(--muted);
}

.admin-consent-who {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.3rem 1.25rem;
  margin: 0.75rem 0 0;
  padding: 0.85rem 1rem;
  border-radius: 0.6rem;
  background: var(--paper);
  font-size: 0.875rem;
}

.admin-consent-who dt {
  font-weight: 600;
  color: var(--muted);
}

.admin-consent-who dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.admin-browser {
  color: var(--muted);
}

/* The Admin section's Beta page (docs/plans/closed-beta.md): one row per
   email, its decisions on the right. */
.admin-beta-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.admin-beta-item {
  display: flex;
  gap: 1.5rem;
  align-items: flex-start;
  justify-content: space-between;
  padding: 1.25rem 1.5rem;
  border-top: 1px solid var(--brand-soft);
}

.admin-beta-item:first-child {
  border-top: 0;
}

.admin-beta-main {
  min-width: 0;
  flex: 1 1 auto;
}

.admin-beta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: flex-end;
  flex: 0 0 auto;
}

.admin-beta-answers {
  margin-top: 0.75rem;
}

.admin-beta-answers summary {
  cursor: pointer;
  color: var(--brand);
  font-size: 0.9rem;
  font-weight: 600;
}

.admin-beta-answers dl {
  margin: 0.75rem 0 0;
}

.admin-beta-answers dt {
  font-size: 0.85rem;
  color: var(--muted);
  font-weight: 500;
}

.admin-beta-answers dd {
  margin: 0.15rem 0 0.75rem;
}

@media (max-width: 767.98px) {
  .admin-beta-item {
    flex-direction: column;
  }

  .admin-beta-actions {
    justify-content: flex-start;
  }
}

.admin-confirm {
  max-width: 44rem;
}

.admin-confirm-list {
  margin: 0;
  padding-left: 1.25rem;
  max-height: 20rem;
  overflow-y: auto;
}
