/* =============================================================================
   styles.css
   -----------------------------------------------------------------------------
   Aesthetic direction: navy and green, drawn directly from the CFFS logo
   (#001D42 and #199346, sampled from icons/logo-header-cropped.png) —
   replacing an earlier palette that was, on inspection, just Claude.ai's
   own terracotta accent rather than anything derived from this app's own
   brand. Cool light-gray surfaces (not the previous warm cream), navy as
   the structural/chrome color (header, primary text), green reserved for
   action and "this is good news" — positive amounts, primary buttons,
   the safe side of the cash flow chart's zero line. Plus Jakarta Sans +
   Inter unchanged; this was a palette problem, not a type problem.

   Token layers, in order: raw palette (the actual hex values, named
   after what they physically are) → semantic tokens (what a color
   MEANS, referencing the raw layer) → scales (spacing, type, shadow).
   Components should only ever reference the semantic layer — if a new
   screen needs a color, find the semantic token whose MEANING fits
   first; only add a new raw value if none of the existing ones are
   actually the right color.
============================================================================= */

:root {
  /* ---- Raw palette ------------------------------------------------------
     The actual brand colors and their tints/shades. Named for what they
     ARE, not what they're for — that mapping lives in the semantic layer
     below. Don't reference these directly from component CSS. */
  --navy-900: #001D42;    /* brand navy, exact logo color — header/nav bar, highest-emphasis chrome */
  --navy-800: #12203A;    /* near-navy ink — body text; softer than 900 for extended reading */
  --navy-600: #33445E;    /* secondary text, dashed reference lines, inactive toggle state */
  --navy-400: #5C6A80;    /* muted labels — metric labels, table headers */
  --navy-300: #61708A;    /* faintest text — axis labels, placeholders, least emphasis */
  --navy-200: #93A6C2;    /* inactive nav links sitting ON a navy-900 background */
  --navy-100: #45566F;    /* text on a pale tag/pill fill */

  --green-600: #0E7A3B;   /* darkened brand green — positive amounts and chart line, better contrast on white than the raw brand green */
  --green-500: #199346;   /* brand green, exact logo color — primary buttons, brand mark accents */
  --green-tint: #E7F2E9;  /* pale green fill/wash, e.g. under the positive side of the cash flow chart */

  --red-600: #B93636;     /* negative amounts, chart line below zero */
  --red-tint: #D64545;    /* used only as a low-opacity fill tint, never as text (too light on its own) */

  --gray-bg: #F3F5F7;     /* page background */
  --gray-surface: #FFFFFF;
  --gray-border: #EFF2F5;        /* hairline dividers — table rows, list rows */
  --gray-border-strong: #E1E6EC; /* card borders — needs to read as a border on its own, not just a divider */
  --gray-border-input: #808D9F;  /* input borders — the most visible of the three, since a field's edges need to read as interactive */
  --gray-pill: #EEF1F5;   /* category tag / pill backgrounds */
  --gray-row-line: #B8C2CF;  /* the line between register rows on a phone: a divider that can actually be seen */
  --gray-row-alt: #EAEEF3;   /* every other register row on a phone */

  /* ---- Semantic tokens ---------------------------------------------------
     What each color MEANS. Every component should reference these, not
     the raw layer above. */
  --color-bg: var(--gray-bg);
  --color-surface: var(--gray-surface);
  --color-border: var(--gray-border);
  --color-border-strong: var(--gray-border-strong);
  --color-border-input: var(--gray-border-input);
  --color-pill: var(--gray-pill);
  --color-row-line: var(--gray-row-line);
  --color-row-alt: var(--gray-row-alt);

  --color-text: var(--navy-800);
  --color-text-muted: var(--navy-400);
  --color-text-faint: var(--navy-300);
  --color-on-dark: #FFFFFF;         /* text/icons on a navy-900 surface (the header) */
  --color-on-dark-muted: var(--navy-200); /* de-emphasized text on a navy-900 surface (inactive nav links) */
  --color-on-accent: #FFFFFF;       /* text on a solid accent-colored button */

  --color-brand: var(--navy-900);        /* structural chrome: header/nav bar, brand mark */
  --color-accent: var(--green-600);      /* primary actions: buttons, links you want someone to click */
  --color-accent-hover: #0B6B33;
  --color-accent-soft: var(--green-tint); /* light accent tint — active/hover backgrounds */

  --color-positive: var(--green-600);
  --color-negative: var(--red-600);
  --color-negative-tint: var(--red-tint);

  /* ---- Typography --------------------------------------------------------
     Family unchanged. Size scale: consolidated from 12 ad hoc values
     down to 7 named steps — component CSS should reach for one of
     these rather than a new literal px value. (Existing declarations
     are migrated opportunistically as screens get touched, not in one
     disruptive pass — see the design audit.) */
  --font-display: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  --text-xs: 11px;    /* smallest hints, axis labels */
  --text-sm: 12.5px;  /* muted labels, table headers, secondary metadata */
  --text-base: 14px;  /* default body/UI text */
  --text-md: 16px;    /* emphasized body text */
  --text-lg: 20px;    /* section headings */
  --text-xl: 24px;    /* metric values */
  --text-display: 30px; /* auth screen title, biggest numbers */

  /* ---- Shape -------------------------------------------------------------- */
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;

  /* ---- Spacing -------------------------------------------------------------
     A 4px-based scale — new/touched CSS should reach for one of these
     instead of a new one-off px value, the same reasoning as the type
     scale above. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* ---- Shadow --------------------------------------------------------------
     Derived from navy-800 rather than a hardcoded rgb() triplet, so a
     future palette change carries these along automatically instead of
     leaving them quietly mismatched (this is exactly what happened to
     the previous set, calibrated to the near-black text color this
     redesign just replaced). */
  --shadow-sm: 0 1px 3px rgba(18, 32, 58, 0.08);
  --shadow-md: 0 8px 24px rgba(18, 32, 58, 0.16);
  --shadow-lg: 0 4px 14px rgba(18, 32, 58, 0.32);

  /* ---- Safe areas (notches / home indicator, standalone PWA mode) ------
     env() resolves to 0px wherever there's no inset to account for —
     virtually all desktop browsers, and any phone that isn't notched —
     so these are harmless to reference unconditionally rather than
     gating them behind a mobile media query. Actually take effect once
     viewport-fit=cover (see index.html's viewport meta) opts the page
     into drawing under those insets in the first place. */
  --safe-area-top: env(safe-area-inset-top, 0px);
  --safe-area-right: env(safe-area-inset-right, 0px);
  --safe-area-bottom: env(safe-area-inset-bottom, 0px);
  --safe-area-left: env(safe-area-inset-left, 0px);
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 16px;
  /* Prevents a scroll gesture that reaches the top/bottom of the page
     from "chaining" into the browser's own pull-to-refresh/rubber-band
     bounce — one of the more noticeable "this is a webpage" tells when
     running installed as a standalone PWA. Effectively a no-op on
     desktop, where that bounce/chaining behavior doesn't really exist
     the same way to begin with. */
  overscroll-behavior-y: contain;
  line-height: 1.45;
}

h1, h2, h3 {
  font-family: var(--font-display);
}

.hidden {
  display: none !important;
}

button {
  font-family: inherit;
  cursor: pointer;
}

/* Form controls don't inherit font-family from the page by default in
   most browsers' UA stylesheets — fix that globally here rather than
   repeating font-family on every input/select declaration. */
input,
select,
textarea {
  font-family: inherit;
}

/* Native date inputs can't be fully restyled cross-browser, but we can at
   least force the light color scheme (so the popup calendar isn't a
   jarring OS-dark-mode mismatch) and soften the picker icon. */
input[type="date"] {
  color-scheme: light;
}

input[type="date"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.55;
  border-radius: 4px;
}

input[type="date"]::-webkit-calendar-picker-indicator:hover {
  opacity: 0.9;
  background: var(--color-accent-soft);
}

.field-hint {
  font-size: 12px;
  color: var(--color-text-muted);
  margin: 4px 0 0 0;
}

/* =============================================================================
   INFO TOOLTIPS
   -----------------------------------------------------------------------------
   For explaining what a label/field/button actually means — distinct from
   .field-hint above, which is for things worth showing unprompted, every
   time. A tooltip is for "worth explaining only if someone's curious
   enough to ask" — hidden until hovered, focused, or tapped.

   Markup: <span class="info-tooltip">
             <button type="button" class="info-tooltip-trigger" aria-describedby="some-id">ⓘ</button>
             <span class="info-tooltip-bubble" id="some-id" role="tooltip">Explanation text.</span>
           </span>

   All showing/hiding/positioning is handled by js/tooltip.js, not CSS
   :hover — several triggers live inside a horizontally-scrollable
   .table-wrap, and a bubble positioned as a normal nested descendant
   there gets silently clipped by that same container's own overflow
   (see the full reasoning in tooltip.js). JS instead moves the active
   bubble to a direct child of <body> and positions it with
   `position: fixed`, computed from the trigger's own on-screen location
   — which is also why left/top aren't set here at all; JS always sets
   them inline before this becomes visible.
============================================================================= */
.info-tooltip {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.info-tooltip-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin-left: 5px;
  border-radius: 50%;
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: 11px;
  line-height: 1;
  padding: 0;
  cursor: help;
  flex-shrink: 0;
}
.info-tooltip-trigger:hover,
.info-tooltip-trigger:focus-visible {
  color: var(--color-on-accent);
  background: var(--color-accent);
  border-color: var(--color-accent);
}

.info-tooltip-bubble {
  position: fixed;
  z-index: 500; /* above every modal in the app (the highest existing z-index elsewhere is 200, for nested modals) since a tooltip can open from inside one */
  width: max-content;
  max-width: 230px;
  background: var(--color-brand);
  color: var(--color-on-dark);
  font-size: 12px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  line-height: 1.4;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  display: none;
  pointer-events: none; /* an on-demand label, never something to click through to */
}

.info-tooltip-bubble.info-tooltip-bubble-open {
  display: block;
}

/* =============================================================================
   AMOUNT INPUTS ($ prefix)
============================================================================= */
.amount-input-wrap {
  position: relative;
}

.amount-input-wrap .amount-prefix {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-text-muted);
  font-size: 15px;
  pointer-events: none;
}

.amount-input-wrap .amount-input {
  padding-left: 26px !important;
}

/* Remove the native up/down spinner arrows on amount fields — they add
   visual noise and aren't useful for currency entry. */
.amount-input::-webkit-outer-spin-button,
.amount-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

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

/* =============================================================================
   AUTH SCREEN
============================================================================= */
/* =============================================================================
   LOADING STATES
   -----------------------------------------------------------------------------
   One generic .spinner reused everywhere something is loading, rather than
   a different visual per screen — consistency matters more here than
   variety, since the whole point is for a person to instantly recognize
   "this means loading" wherever it shows up.
============================================================================= */
.spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--color-border-strong);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.app-loading-screen {
  min-height: 100vh; /* fallback for browsers without dvh support, same reasoning as .auth-screen below */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  background: var(--color-bg);
}

.app-loading-logo {
  height: 56px;
  width: auto;
}

.app-loading-message {
  margin: 0;
  color: var(--color-text-muted);
  font-size: 14px;
}

.overview-loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 20px 0;
  color: var(--color-text-muted);
  font-size: 14px;
}

/* Every other tab's title row wants its usual 16px of breathing room
   before the content below — Overview specifically was called out as
   feeling like too big a gap between the header and everything else, so
   this trims just this one tab's version of that shared row rather than
   changing panel-header-row's own margin everywhere it's used. */
[data-tab="overview"] > .panel-header-row {
  margin-bottom: 4px;
}

.overview-loading-state p {
  margin: 0;
}

/* Lives on the navy top bar — lights up whenever any tab load is in
   flight in the background (see CffsActivity in store.js). Deliberately
   subtle: this is reassurance for someone who's paying attention, not an
   attention-grabbing alert. */
.sync-indicator {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--color-on-dark-muted);
  margin-right: 4px;
}

.sync-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-accent);
  animation: sync-pulse 1.2s ease-in-out infinite;
}

@keyframes sync-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.25;
  }
}

.auth-screen {
  min-height: 100vh; /* fallback for browsers without dvh support — the line below overrides it where supported, never the reverse */
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.auth-box {
  width: 100%;
  max-width: 360px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 36px 28px;
  box-shadow: var(--shadow-sm);
}

.auth-title {
  margin: 0;
  font-size: 30px;
  letter-spacing: 0.5px;
  font-family: var(--font-display);
}

.auth-logo {
  display: block;
  height: 64px;
  width: auto;
  margin: 0 auto 12px auto;
}

.auth-subtitle {
  margin: 4px 0 24px 0;
  color: var(--color-text-muted);
  font-size: 14px;
}

.auth-form {
  display: flex;
  flex-direction: column;
}

.auth-form label {
  font-size: 13px;
  margin-top: 12px;
  margin-bottom: 5px;
  color: var(--color-text-muted);
}

.auth-form input {
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  padding: 11px 12px;
  font-size: 15px;
}

.auth-form input:focus {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-soft);
}

.auth-message {
  min-height: 18px;
  font-size: 13px;
  margin: 10px 0 0 0;
  color: var(--color-negative); /* default assumes an error; .is-success overrides */
}

.auth-message.is-success {
  color: var(--color-positive);
}

.auth-copy {
  font-size: 14px;
  color: var(--color-text-muted);
  line-height: 1.5;
  margin: 4px 0 20px 0;
}

.auth-copy strong {
  color: var(--color-text);
}

.auth-label-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 12px;
}

.auth-label-row label {
  margin-top: 0;
  margin-bottom: 0;
}

.auth-inline-link {
  background: none;
  border: none;
  padding: 0;
  font-size: 12px;
  color: var(--color-accent);
  cursor: pointer;
}

.auth-inline-link:hover {
  color: var(--color-accent-hover);
  text-decoration: underline;
}

.auth-full-width-btn {
  width: 100%;
  margin-top: 4px;
}

.email-verified-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: var(--color-accent-soft);
  color: var(--color-positive);
  font-size: 13px;
  font-weight: 600;
  padding: 10px 16px;
  text-align: center;
}

.email-verified-banner button {
  background: none;
  border: none;
  color: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}

.demo-seed-error-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  /* No existing light-red "soft" background token in the palette (unlike
     --color-accent-soft for green) — a low-opacity mix of --red-600 is a
     reasonable one-off rather than adding a token for a single use. */
  background: rgba(185, 54, 54, 0.12);
  color: var(--color-negative);
  font-size: 13px;
  font-weight: 600;
  padding: 10px 16px;
  text-align: center;
}

.demo-seed-error-banner button {
  background: none;
  border: none;
  color: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}

#auth-submit {
  margin-top: 18px;
  background: var(--color-accent);
  color: var(--color-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  padding: 12px;
  font-size: 15px;
  font-weight: 600;
}

#auth-submit:hover {
  background: var(--color-accent-hover);
}

.auth-toggle-link {
  display: block;
  width: 100%;
  margin-top: 16px;
  background: none;
  border: none;
  color: var(--color-text-muted);
  text-decoration: underline;
  font-size: 13px;
  padding: 4px;
}

.auth-demo-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 20px 0 14px 0;
  color: var(--color-text-muted);
  font-size: 12px;
}
.auth-demo-divider::before,
.auth-demo-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border);
}

/* =============================================================================
   APP SHELL
============================================================================= */
.app-shell {
  min-height: 100vh; /* fallback for browsers without dvh support — the line below overrides it where supported, never the reverse */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.top-bar {
  display: grid;
  grid-template-columns: 1fr minmax(240px, 480px) 1fr;
  align-items: center;
  gap: 20px;
  padding: 12px 20px;
  /* Containing block for .top-bar-search's mobile treatment below,
     where it breaks out of the header's own row entirely to sit as a
     full-width bar underneath it — harmless on desktop, where
     .top-bar-search stays a normal (position: relative, for its OWN
     dropdown) static grid child and never needs this. */
  position: relative;
  /* Stacks with the base padding rather than replacing it, so this is a
     no-op (adds 0) on anything without a real inset — desktop, or a
     non-notched phone — and only actually pushes content down/inward
     once running standalone on something like a notched iPhone. */
  padding-top: calc(14px + var(--safe-area-top));
  padding-left: calc(20px + var(--safe-area-left));
  padding-right: calc(20px + var(--safe-area-right));
  background: var(--color-brand);
}

.app-name {
  margin: 0; /* it is the page's <h1> now */
  font-weight: 700;
  letter-spacing: 0.5px;
  font-size: 18px;
  font-family: var(--font-display);
  color: var(--color-on-dark);
}

.top-bar-left {
  justify-self: start;
  display: flex;
  align-items: center;
  gap: 8px;
}

.header-logo {
  height: 44px; /* the cropped asset's native resolution — matches this exactly rather than upscaling further, which would visibly soften/blur it */
  width: auto;
  display: block;
}

.app-version {
  font-size: 11px;
  color: var(--color-text-muted);
  font-family: var(--font-body);
  font-weight: 400;
  margin-left: 8px;
}

.top-bar-right {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 12px;
}

/* =============================================================================
   GLOBAL SEARCH (header) — searches transactions/charges across the whole
   app by name/description text, amount, or date. Sits in the top bar's
   own middle grid column (see .top-bar's grid-template-columns), sized
   by minmax(240px, 480px) on that column rather than by anything on this
   element itself. That's deliberate, not incidental: this used to be
   flex:1 in a plain flex row, so its available width — and therefore its
   rendered position — depended directly on how wide .top-bar-left and
   .top-bar-right currently measured. Showing or hiding the sync
   indicator changed .top-bar-right's width, which visibly shifted and
   resized the search box every time it toggled. A grid column's size
   comes from its own track definition, not from its neighbors' content,
   so the search box now stays exactly centered and exactly sized
   regardless of what's currently in the side groups.
============================================================================= */
.top-bar-search {
  justify-self: center;
  width: 100%;
  position: relative;
}

.global-search-input {
  width: 100%;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 14px;
  background: rgba(255, 255, 255, 0.1);
  color: var(--color-on-dark);
}

.global-search-input::placeholder {
  color: var(--color-on-dark-muted);
}

.global-search-input:focus {
  border-color: var(--color-accent);
  background: rgba(255, 255, 255, 0.16);
}

.global-search-results {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  max-height: 420px;
  overflow-y: auto;
  z-index: 150;
}

.global-search-result-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
}

.global-search-result-row:last-child {
  border-bottom: none;
}

.global-search-result-row:hover {
  background: var(--color-accent-soft);
}

.global-search-result-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
}

.global-search-result-name {
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.global-search-result-meta {
  font-size: 12px;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.global-search-result-amount {
  font-size: 14px;
  white-space: nowrap;
}

/* Phone-only pieces; shown in the phone block below. */
.header-gear-btn,
.global-search-results-head,
.app-version-line,
.recurring-chips {
  display: none;
}

.global-search-empty,
.global-search-loading {
  padding: 14px;
  font-size: 13px;
  color: var(--color-text-muted);
  text-align: center;
}

@media (max-width: 860px) {
  .top-bar-search {
    display: none; /* the top bar is already tight on narrow/mobile widths — search still reachable once a per-tab or mobile-specific entry point exists */
  }
}

.user-email {
  font-size: 13px;
  color: var(--color-on-dark-muted);
}

.logout-btn {
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: var(--radius-sm);
  padding: 6px 14px;
  font-size: 13px;
  color: var(--color-on-dark);
}

.refresh-btn {
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: var(--radius-sm);
  width: 30px;
  height: 30px;
  font-size: 16px;
  line-height: 1;
  color: var(--color-on-dark-muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.refresh-btn:hover {
  color: var(--color-on-dark);
  border-color: var(--color-accent);
}

.refresh-btn.spinning {
  animation: refresh-spin 0.5s linear;
}

@keyframes refresh-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.logout-btn:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: var(--color-accent);
}

.tab-content {
  flex: 1;
  min-width: 0;
  padding: 24px 20px;
}

.tab-panel h2 {
  margin-top: 0;
  font-size: 22px;
}

.placeholder-note {
  color: var(--color-text-muted);
  font-size: 14px;
  max-width: 480px;
}

/* =============================================================================
   TAB NAVIGATION — always along the top, under the header, on every
   screen size (moved off the bottom per feedback).
============================================================================= */
.tab-nav {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
  padding: 8px 16px 0 16px;
  overflow-x: auto;
}

/* Hidden by default (desktop/tablet) — only actually shown inside the
   mobile media query further down, which also gives it its fixed
   bottom-bar positioning. Without this default, the <nav> falls back to
   its normal block-level flow and renders as an ordinary row directly
   under the top .tab-nav on every screen size, not just on phones. */
.bottom-tab-nav {
  display: none;
}

.tab-nav-btn {
  flex: none;
  background: none;
  border: none;
  padding: 10px 16px;
  font-size: 14px;
  color: var(--color-text-muted);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}

.tab-nav-btn:hover {
  background: var(--color-accent-soft);
  color: var(--color-text);
}

.tab-nav-btn.active {
  color: var(--color-accent);
  border-bottom: 2px solid var(--color-accent);
  font-weight: 600;
}

/* =============================================================================
   BUTTONS (shared)
============================================================================= */
.primary-btn {
  background: var(--color-accent);
  color: var(--color-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  padding: 9px 16px;
  font-size: 14px;
  font-weight: 600;
}

.primary-btn:hover {
  background: var(--color-accent-hover);
}

.secondary-btn {
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: 9px 16px;
  font-size: 14px;
}

.secondary-btn:hover {
  background: var(--color-accent-soft);
  border-color: var(--color-accent);
}

.danger-btn {
  background: var(--color-surface);
  color: var(--color-negative);
  border: 1px solid var(--color-negative);
  border-radius: var(--radius-sm);
  padding: 9px 16px;
  font-size: 14px;
  margin-right: auto; /* pushes Save to the right in modal-actions (every modal now closes via the top-right X, not a Cancel button) */
}

.danger-btn:hover {
  background: var(--color-negative);
  color: var(--color-on-accent);
}

/* =============================================================================
   PANEL HEADER (title + action buttons on one row)
============================================================================= */
.panel-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
}

.panel-header-row h2,
.panel-header-row h3 {
  margin: 0;
}

.panel-header-actions {
  display: flex;
  gap: 8px;
}

/* Selector-row + action-buttons row shared by Checking and Credit Card
   (index.html) — left group of filters/view selectors, right group of
   Import/Add buttons. Kept as its own class rather than reusing
   .panel-header-row directly: the mobile override below exists
   specifically to re-justify a row whose h2 just got hidden, and this
   row never contains an h2 in the first place, so sharing the class
   would wrongly pull both groups flush right on mobile instead of
   leaving them to wrap naturally. */
.tab-controls-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
}

.settings-section {
  border-top: 1px solid var(--color-border);
  padding-top: 16px;
  margin-top: 16px;
}

/* =============================================================================
   METRIC CARD (Budget tab "available to budget")
============================================================================= */
.metric-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 16px 20px;
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Reuses the same token the auth box uses — a card and the page
     background it sits on were close enough in value that plain
     spacing and an actual empty gap looked similar at a glance; this
     gives the eye something to anchor "card" against "background" with,
     on every tab, not just Overview. */
  box-shadow: var(--shadow-sm);
}

.metric-card-narrow {
  max-width: 280px;
}

/* Two metric cards side by side (Cash Flow tab: Credit Card Safe to
   Spend next to Checking Account Safe to Spend) — same flat-flex
   approach as overview-tiles-row, just without its max-width cap since
   there are only ever two cards here. */
/* Two metric cards side by side (Cash Flow tab: Credit Card Safe to
   Spend next to Checking Account Safe to Spend) — same fixed narrow
   width as Checking's and Credit Card's own Current Balance cards
   (.metric-card-narrow), rather than stretching to fill the row. */
.metrics-row {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  /* .metric-card's own margin-bottom is zeroed out below to avoid
     doubled spacing between cards sharing a row — so the row itself
     needs to carry that same spacing instead, or anything wrapped here
     (Credit Card and Cash Flow's Current-Balance-style cards) ends up
     flush against whatever follows, unlike a bare .metric-card used on
     its own (Checking). */
  margin-bottom: 20px;
}

.metrics-row .metric-card {
  margin-bottom: 0;
}

.overview-tiles-row {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  align-items: flex-start;
}

/* Pending Reimbursements' heading sits inside a .panel-header-row, whose
   `h3 { margin: 0; }` rule strips the browser's default h3 top margin —
   this tile's heading isn't wrapped in one (no button/action to justify
   the extra markup), so without this it carries that default margin and
   sits visibly lower than its sibling once they're side by side on
   desktop. On mobile the two tiles wrap and stack instead (see
   .overview-tiles-row > div's min-width above), so there's no row for
   the offset to be visible within — this fix is universal rather than
   media-query-gated, but only actually matters at desktop widths. */
/* Shared by "Remaining Budget This Month" and "Pending Reimbursements" —
   one has a View All button next to its heading, the other doesn't, and
   a plain text-only heading is naturally shorter than one sharing a row
   with a button. min-height pins both to the button row's real height so
   the list content below starts at the same vertical position in both
   cards, not just the same margin-bottom. */
.overview-tile-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 38px;
  margin-bottom: 16px;
}

.overview-tile-heading-row h3 {
  margin: 0;
}

.overview-tile-heading-row {
  flex-wrap: wrap;
  gap: 8px;
}

.overview-tile-heading-buttons {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

/* Smaller than a normal secondary button so a long heading ("Reimbursements &
   Refunds") and both buttons share one line in a tile at its widest (420px)
   instead of the buttons dropping beneath the text. */
.overview-reimbursements-section .overview-tile-heading-row {
  flex-wrap: nowrap; /* on a narrow tile the heading wraps beside the buttons rather than the buttons dropping below */
}

.overview-reimbursements-section .overview-tile-heading-row h3 {
  flex: 1 1 auto;
  min-width: 0;
}

.overview-tile-heading-buttons .secondary-btn {
  padding: 6px 11px;
  font-size: var(--text-base);
  white-space: nowrap;
}

.overview-tiles-row > div {
  flex: 1;
  min-width: 280px;
  max-width: 420px;
}

.overview-tiles-row .recurring-list-row {
  padding: 13px 16px;
  font-size: 15px;
}

/* "Remaining Budget This Month" is a plain grid of spans now (see
   renderOverviewBucketList), not clickable rows — no hover rule needed
   at all, unlike Pending Reimbursements right next to it. */
.overview-bucket-grid {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: baseline;
  row-gap: 10px;
  column-gap: 6px;
  font-size: 14px;
  padding: 14px 16px;
}

.overview-bucket-wrap {
  margin-bottom: 0;
}

.overview-bucket-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.overview-bucket-amount {
  text-align: right;
  white-space: nowrap;
}

.overview-bucket-of {
  text-align: center;
  color: var(--color-text-muted);
  padding: 0 2px;
}

.overview-bucket-total-cell {
  font-weight: 700;
  border-top: 1px solid var(--color-border-strong);
  padding-top: 8px;
  margin-top: 2px;
}

.overview-columns-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(280px, 1fr));
  gap: 20px;
  margin-top: 20px;
}

/* Column order (left to right): Credit Card, Checking, Cash Flow. The
   grid's children are flat siblings in DOM order (see the note just
   below on why), so without these, CSS Grid's default row-major
   auto-placement would just follow raw DOM order — which happens to put
   Cash Flow's pieces before Checking's. `order` repositions each piece
   into the desired column without needing to touch the DOM at all.
   Mobile's own order values (see the mobile media query further down)
   intentionally do NOT match this sequence — collapsing to a single
   column needs each column's own 4 pieces kept together as one group
   (metric, action, title, grid, then the next column's own four), which
   is a different sequence than "all 3 metrics, then all 3 actions..."
   that this row-major 3-column layout needs. */
#overview-cc-safe-metric {
  order: 1;
}
#overview-checking-balance-metric {
  order: 2;
}
#overview-checking-safe-metric {
  order: 3;
}
#overview-cc-action {
  order: 4;
}
#overview-checking-action {
  order: 5;
}
#overview-cashflow-action {
  order: 6;
}
#overview-cc-mini-title {
  order: 7;
}
#overview-checking-mini-title {
  order: 8;
}
#overview-cashflow-mini-title {
  order: 9;
}
#overview-cc-mini-grid {
  order: 10;
}
#overview-checking-mini-grid {
  order: 11;
}
#overview-cashflow-mini-grid {
  order: 12;
}

/* Tiles, button rows, and mini-grids are flat siblings within the grid
   above rather than nested per-column wrappers — CSS Grid's own
   row-tracking then aligns each "row" (all 3 tiles, all 3 button rows,
   all 3 mini-grid tops) to a consistent height across columns
   automatically, which flexbox has no equivalent for at the level of
   individual sub-sections within each column. */
.overview-columns-row .metric-card {
  margin-bottom: 0;
}

.overview-column-actions {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 6px 0;
}

.overview-mini-grid-title {
  margin: 0 0 6px 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text-muted);
}

.overview-mini-grid-title-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 0 0 6px 0;
}
.overview-mini-grid-title-row .overview-mini-grid-title {
  margin: 0;
}

/* Collapse/expand toggle for a mini-grid's table — a plain button, not
   a link or icon font, so it's keyboard-focusable and doesn't need an
   icon asset. Defaults to expanded everywhere; collapsing is a purely
   session-only display preference (resets on reload), same convention
   as this app's other view-state (e.g. Checking's own View selector). */
.overview-mini-grid-toggle {
  /* Desktop has plenty of room for every mini-grid already expanded —
     collapsing one only really earns its keep on a narrow phone screen
     where space is actually scarce. Restored for mobile in this file's
     existing @media (max-width: 640px) block below. */
  display: none;
  background: none;
  border: none;
  padding: 4px;
  color: var(--color-text-muted);
  font-size: 11px;
  cursor: pointer;
  line-height: 1;
}
.overview-mini-grid-toggle:hover {
  color: var(--color-text);
}

.table-wrap.overview-grid-collapsed {
  display: none;
}

.overview-mini-grid-empty {
  padding: 16px;
  text-align: center;
}

/* Sits below the value now, not beside the label — see the CC Safe to
   Spend card markup. A dropdown competing with the label for the same
   line meant its width (which varies with the card's own name) decided
   whether the row wrapped, which in turn decided how far down the VALUE
   text sat — misaligning it against the other, dropdown-less metric
   cards beside it. Below the value, the label→value spacing is now
   identical to every other metric card regardless of card name length
   or how many cards exist to choose from. */
.overview-cc-card-select {
  width: auto;
  max-width: 160px;
  margin-top: 8px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  font-size: 13px;
  background: var(--color-surface);
  color: var(--color-text);
}

.add-plus-btn .btn-plus {
  font-weight: 600;
}

.metric-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--color-text-muted);
}

.metric-value {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 600;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--color-text-muted);
  margin: 10px 0;
}

.checkbox-label input {
  width: auto;
}

/* Two mutually exclusive options (Reimbursable / Refund) on one line. */
.checkbox-pair {
  display: flex;
  flex-wrap: wrap;
  column-gap: 24px;
}

.field-success {
  min-height: 18px;
  color: var(--color-positive);
  font-size: 13px;
  margin: 10px 0 0 0;
}

.settings-form {
  margin-bottom: 28px;
  max-width: 420px;
}

/* =============================================================================
   TOGGLE SWITCHES (Notifications)
============================================================================= */
.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--color-border);
}

.toggle-row:last-of-type {
  border-bottom: none;
}

.toggle-row-label {
  font-size: 14px;
  color: var(--color-text);
}

.toggle-section-heading {
  margin: 18px 0 4px 0;
}

.toggle-switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
  flex-shrink: 0;
  cursor: pointer;
}

.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
}

.toggle-slider {
  position: absolute;
  inset: 0;
  background: var(--color-border-strong);
  border-radius: 999px;
  transition: background 0.15s ease;
}

.toggle-slider::before {
  content: "";
  position: absolute;
  height: 16px;
  width: 16px;
  left: 3px;
  top: 3px;
  background: #ffffff;
  border-radius: 50%;
  transition: transform 0.15s ease;
}

.toggle-switch input:checked + .toggle-slider {
  background: var(--color-accent);
}

.toggle-switch input:checked + .toggle-slider::before {
  transform: translateX(18px);
}

.toggle-switch input:focus-visible + .toggle-slider {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* =============================================================================
   CREDIT CARD BUDGET LIST
============================================================================= */
/* =============================================================================
   REGISTER TABLE
============================================================================= */
.table-wrap {
  overflow-x: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin-bottom: 20px;
  box-shadow: var(--shadow-sm);
}

/* Scoped to Balance's history table: scrolls internally instead of
   requiring the whole page to scroll, with a sticky header so column
   labels stay visible while scrolling through years of history. */
.scrollable-table-wrap {
  max-height: 65vh;
  overflow-y: auto;
}

.scrollable-table-wrap thead th {
  position: sticky;
  top: 0;
  background: var(--color-surface);
  z-index: 1;
}

.register-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.register-table th {
  text-align: left;
  border-bottom: 1px solid var(--color-border);
  padding: 10px 14px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--color-text-muted);
}

.register-table th.th-amount {
  text-align: right;
}

.register-table td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--color-border);
}

.cc-pending-cell {
  text-align: center;
  width: 1%;
  white-space: nowrap;
}

.register-table th.cc-pending-cell {
  text-align: center;
}

/* Credit Card charges table only: the Date column gets a fixed,
   consistent width, so the columns after it line up row to row. The
   Pending column is the last one, after Amount: it is a narrow checkbox
   column, so its padding is tightened rather than leaving it floating
   in a wide cell. */
.cc-charges-table td:first-child {
  width: 130px;
  white-space: nowrap;
}

.cc-charges-table .cc-pending-cell {
  padding: 0 14px 0 8px;
}

/* A fixed size, not height:100% + aspect-ratio:1/1 — that combination
   creates a genuine circular dependency (the hitbox's width depends on
   its height via aspect-ratio, while the cell's — and so the row's —
   height has nothing else forcing it, so it was effectively depending
   on the hitbox's own width right back). Chromium resolved that circle
   by inflating both to roughly double this table's normal ~41px row
   height, matching Checking's. A fixed size sidesteps the circularity
   entirely: 32px is comfortably larger than the native checkbox glyph
   for an easier tap target, while sitting well within a normal row's
   natural height instead of trying to dynamically match it. */
.cc-pending-hitbox {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  cursor: pointer;
}

.cc-pending-hitbox input.cc-pending-checkbox {
  cursor: pointer;
  width: 16px;
  height: 16px;
}

/* Shared by every table in the app that shows a monetary value.
   formatCurrency always produces exactly 2 decimal digits, so simple
   right-alignment of a single cell already lines up decimal points
   correctly across rows — the trailing ".XX" is always the same width,
   regardless of how many digits precede it. */
.amount-cell {
  text-align: right;
  white-space: nowrap;
}

.register-table tr:last-child td {
  border-bottom: none;
}

.register-row {
  cursor: pointer;
}

.register-row:hover {
  background: var(--color-accent-soft);
}

.amount-negative {
  color: var(--color-negative);
}

.amount-positive {
  color: var(--color-positive);
}

.register-divider-row td {
  padding: 4px 14px !important;
  border-bottom: none !important;
  text-align: center;
  position: relative;
}

.register-divider-row span {
  position: relative;
  display: inline-block;
  background: var(--color-surface);
  padding: 0 10px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--color-text);
  z-index: 1;
}

.register-divider-row td::before {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  top: 50%;
  border-top: 2px solid var(--color-border-strong);
}

/* =============================================================================
   BALANCE TAB — header row (starting balance + month selector) and the
   always-current audit row in the table footer
============================================================================= */
.balance-header-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
}

.balance-month-select-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--color-text-muted);
}

.balance-month-select-label select {
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  font-size: 13px;
  background: var(--color-surface);
  color: var(--color-text);
}

.balance-latest-balance {
  font-weight: 700;
}

/* Credit Card ledger: a "statement closed" line is the anchor every
   balance below it flows from, so it reads as a band rather than as one
   more transaction. */
.register-statement-row td {
  background: var(--color-accent-soft);
  font-weight: 600;
}

.cc-statement-edited-tag {
  font-weight: 400;
  font-size: 12px;
  color: var(--color-text-muted);
}

/* Rows that exist in Checking rather than here (payments recorded before
   the ledger) aren't editable from this tab, so they shouldn't look
   clickable. */
.register-row-readonly {
  cursor: default;
}

/* The statement dialog's running "total due" line, and the small link-style
   button that puts a carried balance / amount paid back to its default. */
.statement-total-line {
  margin: 8px 0;
}

.reset-default-btn {
  align-self: flex-start;
  margin: -4px 0 8px 0;
  font-size: 12px;
  padding: 2px 10px;
}

.import-row-controls {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
}

.import-row-controls select {
  max-width: 260px;
}

.metric-note {
  font-size: 12px;
  color: var(--color-text-muted);
}

/* =============================================================================
   ACCOUNTS LIST (Settings)
============================================================================= */
.account-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 12px 14px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin-bottom: 8px;
  font-size: 14px;
  cursor: pointer;
}

.account-row:hover {
  background: var(--color-accent-soft);
}

.account-row-name {
  font-weight: 600;
}

.account-row-detail {
  color: var(--color-text-muted);
  margin-left: auto;
}

/* =============================================================================
   MODALS
============================================================================= */
.modal-overlay {
  position: fixed;
  inset: 0;
  /* Navy-tinted rather than a neutral black dim — matches the modal
     backdrop treatment validated in the design mockups, and reads as
     part of the same palette rather than a generic overlay color. */
  background: rgba(0, 29, 66, 0.55);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 48px 16px;
  overflow-y: auto;
  z-index: 100;
}

/* Can be opened FROM WITHIN another modal (e.g. the Transaction Series
   modal, or now the View All Reimbursements modal) — needs to always
   paint above any other modal-overlay, regardless of DOM order, rather
   than the two toggling visibility. */
#transaction-modal,
#reimbursement-edit-modal,
#reimbursement-receive-modal {
  z-index: 200;
}

.modal-box {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  width: 100%;
  max-width: 420px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 26px;
  box-shadow: var(--shadow-md);
  position: relative;
}

.modal-box-large {
  max-width: 900px;
  width: 92vw;
}

.modal-close-x {
  position: absolute;
  top: 14px;
  right: 14px;
  background: none;
  border: none;
  font-size: 22px;
  line-height: 1;
  color: var(--color-text-muted);
  cursor: pointer;
  padding: 4px 8px;
}

.modal-close-x:hover {
  color: var(--color-text);
}

#recurring-series-modal-title {
  padding-right: 36px;
}

.modal-box h3 {
  margin-top: 0;
}

.modal-form {
  display: flex;
  flex-direction: column;
}

.modal-form label {
  font-size: 13px;
  margin-top: 12px;
  margin-bottom: 5px;
  color: var(--color-text-muted);
}

.modal-form input,
.modal-form select,
.modal-form textarea {
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  padding: 10px 12px;
  font-size: 15px;
  width: 100%;
  font-family: inherit;
}

.modal-form input[type="checkbox"] {
  width: auto;
  padding: 0;
  border: none;
  background: none;
  flex-shrink: 0;
}

.modal-form input:focus,
.modal-form select:focus,
.modal-form textarea:focus {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-soft);
}

.modal-form textarea {
  resize: vertical;
}

/* .modal-form input's width:100% above is meant for text/number/date
   inputs — a radio button stretched to full-width looks broken, same
   reasoning as the existing checkbox exception just above. */
.modal-form input[type="radio"] {
  width: auto;
  padding: 0;
  border: none;
  background: none;
  flex-shrink: 0;
}

/* =============================================================================
   IMPORT CSV MODAL — amount-mode fieldset (single vs. debit/credit columns)
   and the preview table's duplicate-flag styling.
============================================================================= */
.import-amount-mode-fieldset {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 10px 12px 4px 12px;
  margin-top: 12px;
}

.import-amount-mode-fieldset legend {
  font-size: 13px;
  color: var(--color-text-muted);
  padding: 0 4px;
}

.import-amount-mode-fieldset .checkbox-label {
  margin: 6px 0;
}

#import-preview-table-body td.import-status-cell {
  font-size: 13px;
  white-space: nowrap;
}

.import-duplicate-flag {
  color: var(--color-accent);
}

.import-skip-checkbox-cell {
  width: 32px;
}

/* =============================================================================
   INLINE "SELECT + ADD NEW" ROW — reused by anywhere a dropdown needs a
   quick way to create a new option inline (currently: reimbursements).
============================================================================= */
.flag-select-row {
  display: flex;
  gap: 6px;
  align-items: stretch;
}

.flag-select-row select {
  flex: 1;
}

.flag-add-btn {
  flex-shrink: 0;
  width: 40px;
  padding: 0;
  font-size: 18px;
  line-height: 1;
}

.flag-new-wrap {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}

.flag-new-wrap input {
  flex: 1;
}

.modal-error {
  min-height: 18px;
  color: var(--color-negative);
  font-size: 13px;
  margin: 10px 0 0 0;
}

/* Above every other dialog: while an account is pending deletion this is the only thing that should be usable. */
#deletion-pending-overlay {
  z-index: 5000;
}

.modal-actions-start {
  justify-content: flex-start;
  flex-wrap: wrap;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 22px;
  /* Pinned to the bottom of the scrolling .modal-box so Save/Cancel
     stay visible no matter how long the form is — position:sticky keeps
     this in normal flow (so short forms are unaffected) but holds it
     against the box's bottom edge once the content is tall enough to
     scroll. Negative bottom margin + matching padding cancel the box's
     own 26px bottom padding so the footer's background reaches the true
     bottom edge rather than floating above a strip of gap; the surface
     background + top border visually separate it from the scrolling
     content sliding underneath. Only applies to a modal-actions that's
     an actual form footer — .modal-actions--start (report action rows,
     inline settings panels) opts out below, since those aren't inside a
     scrolling overlay and shouldn't stick. */
  position: sticky;
  bottom: 0;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  padding-top: 14px;
  margin-bottom: -26px;
  padding-bottom: 26px;
}

.modal-actions--start {
  position: static;
  border-top: none;
  background: none;
  margin-bottom: 0;
  padding-bottom: 0;
  padding-top: 0;
}

/* Modifier for a modal-actions row that isn't a form's final Save/Cancel
   pair — e.g. a single "Download CSV" action under a report's own
   fields, where flex-end would orphan the button awkwardly on the far
   right of a wide settings panel. The tighter top margin matches how
   close these action rows sit to the field just above them, vs. a full
   form's Save/Cancel pair which wants more separation. Replaces what
   used to be an identical inline style repeated at every report action
   row. Also opts out of the sticky-footer treatment above (see this
   class's own overrides there) — these live in inline settings panels,
   not scrolling overlay modals, so there's nothing for them to stick
   to. */
.modal-actions--start {
  justify-content: flex-start;
  margin-top: var(--space-3);
}

/* Undo-last-import banner (import.js) — a plain bordered row, same
   surface/border tokens as everything else, not a new visual language. */
.import-last-batch-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}

.import-last-batch-banner p {
  margin: 0;
}

/* Generic top-margin utilities for the handful of places a section
   needs a bit of extra separation from whatever's above it but doesn't
   warrant a whole new named component — e.g. a sub-section header
   inside a settings panel. Reach for one of these instead of a new
   inline style. */
.mt-sm {
  margin-top: var(--space-3);
}

.mt-md {
  margin-top: var(--space-4);
}

/* =============================================================================
   REGISTER SUB-NAV (Transactions / Recurring Items)
============================================================================= */
.sub-nav {
  display: flex;
  gap: 6px;
  margin-bottom: 16px;
}

.subtab-btn {
  background: none;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: 7px 14px;
  font-size: 13px;
  color: var(--color-text-muted);
}

.subtab-btn:hover {
  background: var(--color-accent-soft);
}

.subtab-btn.active {
  background: var(--color-text);
  color: var(--color-bg);
  border-color: var(--color-text);
}

.recurring-badge {
  display: inline-block;
  font-size: 11px;
  color: var(--color-text-muted);
  background: var(--color-accent-soft);
  border-radius: 999px;
  padding: 1px 8px;
  margin-left: 6px;
  vertical-align: middle;
}

/* =============================================================================
   RECURRING ITEMS LIST — condensed, single-line rows (built to stay
   scannable with 20-30+ items rather than large tiles)
============================================================================= */
.recurring-list-wrap {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.recurring-list-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
  font-size: 13px;
}

.recurring-series-btn {
  flex-shrink: 0;
  margin-left: auto;
  font-size: 12px;
  padding: 6px 10px;
}

.recurring-columnar-table tr {
  cursor: pointer;
}

.recurring-columnar-table tr:hover {
  background: var(--color-accent-soft);
}

/* table-layout:fixed + explicit widths on every non-Name column is what
   actually makes the 4 separate type-group tables (Expense/Income/
   Transfer/Debt Payment) line up with each other — each is genuinely a
   separate <table>, so their auto-layout column widths were being
   calculated independently per table with no relationship to one
   another. Scoped to .recurring-items-table specifically (not the
   shared .recurring-columnar-table, which Debts also uses with a
   different column structure) so Debts is unaffected. Name is left
   without an explicit width so it absorbs whatever space remains,
   which is also what pulls the other columns compact and toward the
   right rather than spread across the row. */
.recurring-items-table {
  table-layout: fixed;
}

.recurring-items-table td {
  padding: 8px 10px;
}

/* overflow:hidden on every fixed-width cell is a deliberate safety net:
   table-layout:fixed sets each column's target width, but doesn't by
   itself stop a cell's own content (e.g. the button below) from
   visually spilling past that width if it doesn't fit — which would
   push the whole table wider than its wrapper and trigger the
   wrapper's own overflow-x:auto scrollbar. Clipping here guarantees
   the table can never exceed 100% width, regardless of minor
   variance in font rendering or button sizing across browsers/OSes.
   Action cell specifically carries real margin (not just enough to
   fit in one measured environment) since font-rendering differences
   across browsers/OSes are exactly what caused the button to clip at
   a tighter width previously. */
.recurring-table-handle-cell {
  width: 24px;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

.recurring-table-name-cell {
  min-width: 0;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.recurring-table-amount-cell {
  width: 80px;
  min-width: 0;
  overflow: hidden;
}

.recurring-table-frequency-cell,
.recurring-table-target-cell {
  min-width: 0;
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recurring-table-frequency-cell {
  width: 230px;
}

.recurring-table-target-cell {
  width: 260px;
}

.recurring-table-action-cell {
  width: 190px;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-align: right;
}

.recurring-list-row:last-child {
  border-bottom: none;
}

.recurring-list-row:hover {
  background: var(--color-accent-soft);
}

/* =============================================================================
   REIMBURSEMENT ROWS
   -----------------------------------------------------------------------------
   Two different row shapes share the .reimbursement-overdue-row /
   .reimbursement-overdue-badge / .reimbursement-receive-btn classes below
   (each written standalone, not scoped to either row shape specifically):
   Overview's Pending Reimbursements card uses .reimbursement-row (below,
   two lines — see its own comment for why), while the View All
   Reimbursements modal uses a plain table row (.register-row), which
   already has enough width for everything on one line.
============================================================================= */
.reimbursement-row {
  padding: 9px 14px;
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
  font-size: 13px;
}
.reimbursement-row:last-child {
  border-bottom: none;
}
.reimbursement-row:hover {
  background: var(--color-accent-soft);
}

.reimbursement-row-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 4px;
}

.reimbursement-row-detail-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 6px 10px;
}

.reimbursement-row-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  color: var(--color-text-muted);
}

.reimbursement-overdue-row {
  border-left: 3px solid var(--color-negative);
}

.reimbursement-overdue-badge {
  display: inline-block;
  background: var(--color-negative);
  color: var(--color-on-dark);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.reimbursement-receive-btn {
  padding: 5px 10px;
  font-size: 12px;
  flex-shrink: 0;
}

/* Household member rows reuse .recurring-list-row's spacing/border but
   aren't clickable themselves (only the Remove button inside is) and
   need that button pushed to the far right rather than sitting
   immediately after the name — .recurring-list-row alone doesn't do
   either of those (cursor:pointer implies the whole row is clickable;
   no justify-content to separate a leading detail block from a
   trailing action). */
#household-members-list .recurring-list-row {
  cursor: default;
  justify-content: space-between;
}

#household-members-list .recurring-list-row:hover {
  background: none;
}

.recurring-list-main {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
  flex: 1;
}

.recurring-list-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recurring-list-detail {
  display: flex;
  gap: 14px;
  color: var(--color-text-muted);
  white-space: nowrap;
  flex-shrink: 0;
}

.recurring-group-header {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--color-text-muted);
  margin: 20px 0 8px 0;
}

.recurring-group-header:first-of-type {
  margin-top: 8px;
}

.drag-handle-gutter {
  display: inline-block;
  width: 30px;
  text-align: center;
}

.register-table th.th-date-offset {
  padding-left: 44px;
}

.drag-handle {
  cursor: grab;
  color: var(--color-text-muted);
  font-size: 16px;
  line-height: 1;
  padding: 4px 6px;
  user-select: none;
  touch-action: none;
}

.drag-handle:hover {
  color: var(--color-text);
}

.dragging {
  opacity: 0.4;
}

/* =============================================================================
   BUDGET GRID (multi-month spreadsheet-style table)
============================================================================= */
.budget-grid-table {
  border-collapse: collapse;
  font-size: 13px;
}

.budget-grid-table th,
.budget-grid-table td {
  width: 200px;
  min-width: 200px;
  padding: 6px 8px;
  text-align: right;
  white-space: nowrap;
  border-left: 1px solid var(--color-border);
}

.budget-grid-table th:first-child,
.budget-grid-table td:first-child {
  min-width: 160px;
  text-align: left;
  border-left: none;
}

.budget-grid-table thead th {
  border-bottom: 1px solid var(--color-border);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--color-text-muted);
}

.budget-grid-row-label {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
}

.budget-grid-income-row,
.budget-grid-total-row,
.budget-grid-surplus-row {
  border-bottom: 1px solid var(--color-border);
}

.budget-grid-income-row td,
.budget-grid-total-row td,
.budget-grid-surplus-row td {
  background: var(--color-bg);
}

.budget-grid-income-row .budget-grid-row-label,
.budget-grid-total-row .budget-grid-row-label,
.budget-grid-surplus-row .budget-grid-row-label {
  font-weight: 700;
  font-style: normal;
  color: var(--color-text);
}

.budget-grid-total-row {
  border-top: 1px solid var(--color-border-strong);
}

.budget-grid-header-row td {
  padding-top: 16px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--color-border-strong);
}

.budget-grid-header-row .budget-grid-row-label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--color-text);
  text-align: left;
}

.budget-grid-header-subtotal {
  font-weight: 700;
  text-align: right;
  padding-right: 12px;
}

.category-toggle-row {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.category-collapse-chevron {
  display: inline-block;
  width: 14px;
  font-size: 16px;
  line-height: 1;
  color: var(--color-text-muted);
}

.budget-grid-row-label.budget-grid-row-indented {
  padding-left: 28px;
}

.category-drop-target {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.drop-indicator-before {
  box-shadow: inset 0 2px 0 0 var(--color-accent);
}

.drop-indicator-after {
  box-shadow: inset 0 -2px 0 0 var(--color-accent);
}

.budget-grid-readonly-row td {
  color: var(--color-text-muted);
}

.budget-grid-readonly-row .budget-grid-row-label {
  color: var(--color-text);
}

/* =============================================================================
   EDIT BUCKETS MODE — inline reorder/edit/remove chrome on bucket rows
============================================================================= */
.budget-grid-row-label-editable {
  display: flex;
  align-items: center;
  gap: 6px;
}

.bucket-edit-name {
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--color-border-strong);
  text-underline-offset: 2px;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bucket-edit-name:hover {
  text-decoration-color: var(--color-accent);
  color: var(--color-accent);
}

.bucket-remove-btn {
  flex-shrink: 0;
  background: none;
  border: none;
  color: var(--color-negative);
  font-size: 16px;
  line-height: 1;
  padding: 2px 4px;
}

.bucket-remove-btn:hover {
  background: var(--color-negative);
  color: var(--color-on-accent);
  border-radius: var(--radius-sm);
}

/* =============================================================================
   BUDGET SCROLL ARROWS + fixed-position (non-free-drag) grid container
============================================================================= */
.budget-scroll-controls {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  margin-bottom: 8px;
  flex-wrap: wrap;
  gap: 10px;
}

.budget-edit-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.budget-scroll-arrows {
  display: flex;
  gap: 6px;
}

.budget-scroll-btn {
  width: 36px;
  padding: 6px 0;
  font-size: 16px;
  line-height: 1;
}

.budget-scroll-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.budget-grid-wrap-fixed {
  overflow-x: hidden;
  width: 100%;
}

.budget-grid-input {
  min-width: 90px;
  width: 100%;
  /* Right padding is deliberately tighter than left — the containing
     <td> already contributes its own 8px right padding (unchanged,
     since this cell has no override class), and the locked/subtotal
     cells in this same column land their text 12px from the cell's
     outer edge (via their own padding-right:12px). To make the input's
     text land at that same 12px, its own padding only needs to add 4px
     on top of the td's 8px — using the same 10px on both sides here,
     like the locked cells' plain padding would, produces a 6px
     rightward mismatch against them, which is what looked misaligned. */
  padding: 6px 4px 6px 10px !important;
  font-size: 13px;
  border: 1px solid transparent;
  background: transparent;
  border-radius: var(--radius-sm);
  text-align: right;
}

.budget-grid-input:hover {
  border-color: var(--color-border-strong);
}

.budget-grid-input:focus {
  border-color: var(--color-accent);
  background: var(--color-surface);
}

.budget-grid-locked-cell {
  color: var(--color-text-muted);
  font-style: italic;
  text-align: right;
  padding-right: 12px;
}

/* =============================================================================
   BUDGET GRID — scroll behavior + current-month highlight
============================================================================= */
.budget-grid-table th:first-child {
  position: sticky;
  left: 0;
  background: var(--color-surface);
  z-index: 2;
}

/* Sticky positioning is scoped to the .budget-grid-row-label CLASS rather
   than the td:first-child selector, deliberately — a few rows (section
   headers, the "+ Add Bucket" row) use a single colspanned <td> that is
   also technically :first-child. Making a colspanned cell sticky breaks
   its rendering (the browser can't reconcile "span 26 columns" with
   "pin to the left edge"), which is what was silently hiding the
   "+ Add Bucket" row entirely. Actual label cells all carry this class
   explicitly, so scoping to it here keeps sticky behavior only where
   it's meant to apply. */
.budget-grid-row-label {
  position: sticky;
  left: 0;
  background: var(--color-surface);
  z-index: 2;
}

.budget-grid-current-month {
  background: var(--color-accent-soft);
}

.budget-grid-table thead .budget-grid-current-month {
  background: var(--color-accent-soft);
  font-weight: 700;
}

/* =============================================================================
   CASH FLOW CHART + LOW WATER MARK LIST
============================================================================= */
.chart-wrap {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 16px;
  margin-bottom: 16px;
}

@media (max-width: 480px) {
  .recurring-list-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
  }

  .recurring-list-detail {
    flex-wrap: wrap;
    gap: 8px;
  }
}

@media (min-width: 768px) {
  .tab-content {
    padding: 24px 32px;
  }
}

/* =============================================================================
   MOBILE (PHONE) LAYOUT
   -----------------------------------------------------------------------------
   Everything below is gated behind max-width: 640px, chosen specifically
   for phones rather than the 860px cutoff used above for the header
   search box — a media query this drastic (bottom nav swap, full-screen
   modals) shouldn't also kick in on a merely
   narrow desktop window or a tablet in portrait. Nothing here changes
   anything above 640px — desktop's layout, spacing, and mouse-driven
   interactions are untouched.

   Companion changes live in:
   - index.html: bottom-tab-nav markup, the gear (Manage) beside the search box
   - utils.js: touch-equivalent drag-reorder handlers (enableDragReorder)
============================================================================= */
@media (max-width: 640px) {
  /* ---- App-like shell: header and bottom bar are ALWAYS on screen ---------------------
     The shell is exactly one screen tall, and only the content area between the header
     and the bottom bar scrolls. The page itself never scrolls, so nothing disappears
     when you scroll. (The bottom bar used to float over the page and the header scrolled
     away with it.) 100dvh follows the browser toolbar as it shows and hides. */
  html {
    overflow-x: hidden;
    -webkit-tap-highlight-color: transparent;
  }

  .app-shell {
    height: 100vh; /* fallback for browsers without dvh */
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
  }

  .top-bar {
    flex: none;
  }

  .tab-content {
    order: 2;
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain; /* no pull-to-refresh or rubber-banding of the page behind it */
  }

  /* No scrollbars on a phone; touch scrolling works everywhere without them. */
  * {
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  *::-webkit-scrollbar {
    display: none;
  }

  /* Taps act at once (no double-tap-to-zoom wait on controls). Pinch zoom still works. */
  button,
  a,
  input,
  select,
  label,
  summary,
  [role="button"],
  [role="menuitem"] {
    touch-action: manipulation;
  }

  /* ---- Not offered on a phone ------------------------------------------------------
     Import CSV (a desktop job), the Budget tab (the grid is unusable at this width), and
     the Cash Flow chart (unreadable; the Safe to Spend figures and the day-by-day list
     under it carry the same information). */
  #import-csv-checking-btn,
  #import-csv-cc-btn {
    display: none;
  }

  .tab-panel[data-tab="budget"],
  .cashflow-chart-section {
    display: none !important;
  }

  /* ---- Primary navigation: top tab-nav → bottom tab bar ---------------- */
  .tab-nav {
    display: none;
  }

  .bottom-tab-nav {
    display: flex;
    order: 3; /* after the content area, as the last row of the shell */
    flex: none;
    position: relative;
    z-index: 90;
    background: var(--color-surface);
    border-top: 1px solid var(--color-border);
    padding: 6px calc(4px + var(--safe-area-left)) calc(4px + var(--safe-area-bottom)) calc(4px + var(--safe-area-right));
  }

  .bottom-tab-nav-btn {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    background: none;
    border: none;
    color: var(--color-text-muted);
    font-size: 11px;
    font-family: inherit;
    padding: 6px 2px;
    border-radius: var(--radius-sm);
    /* Overrides .tab-nav-btn's own border-bottom "active" indicator
       (a horizontal underline made sense for a top tab strip; here the
       icon+label recoloring below is the indicator instead). */
    border-bottom: none;
  }

  .bottom-tab-nav-btn.active {
    color: var(--color-accent);
    font-weight: 600;
  }

  .bottom-tab-nav-icon {
    font-size: 20px;
    line-height: 1;
  }

  /* the icons that are drawn (not text glyphs) match the glyph size and take the button's colour */
  .bottom-tab-nav-icon svg {
    display: block;
    width: 20px;
    height: 20px;
  }

  /* The bottom bar is a row of the shell now, not a layer over the content, so the
     content area ends above it and needs no reserved space. */
  .tab-content {
    padding-bottom: 20px;
  }

  /* ---- Modals: floating dialog → full-screen sheet -----------------------
     Every modal already shares .modal-overlay/.modal-box, so this one
     override reaches all of them at once — no per-modal changes needed.
     Placed BEFORE the More sheet's own rules below: both target
     .modal-box (More's own box also carries that class), and CSS
     resolves equal-specificity conflicts by source order — More's
     compact-bottom-sheet sizing needs to come after this to win. */
  .modal-overlay {
    padding: 0;
    align-items: stretch;
  }

  .modal-box,
  .modal-box-large {
    max-width: none;
    width: 100%;
    max-height: none;
    height: 100%;
    border-radius: 0;
    border: none;
    padding: 20px 20px calc(20px + var(--safe-area-bottom)) 20px;
    padding-top: calc(20px + var(--safe-area-top));
  }

  /* Absolute positioning measures from the padding BOX's outer edge
     (i.e. from the border, ignoring how much padding the box itself
     has) — so without this override, the close button would sit at a
     flat 14px from the true top edge regardless of the safe-area
     padding just added above, landing it under a notch/status bar on
     a device that has one. */
  .modal-close-x {
    top: calc(14px + var(--safe-area-top));
    right: calc(14px + var(--safe-area-right));
  }

  /* ---- Form inputs: prevent iOS Safari's zoom-on-focus -------------------
     Anything under 16px triggers it; these were 13–15px, sized fine for
     a mouse/desktop but not for this. Desktop keeps its existing sizes
     since this whole block is behind the 640px query. */
  .modal-form input,
  .modal-form select,
  .modal-form textarea,
  .budget-grid-input,
  .global-search-input {
    font-size: 16px;
  }

  /* ---- Touch targets: buttons sized for a finger, not a cursor ---------- */
  .primary-btn,
  .secondary-btn,
  .danger-btn {
    padding: 12px 18px;
    font-size: 16px;
  }

  .subtab-btn {
    flex: none;
    min-height: 44px;
    padding: 12px 14px;
    font-size: 15px;
    white-space: nowrap;
  }

  /* Manage has more sub-tabs than fit: the row scrolls sideways by itself (not the page). */
  .sub-nav {
    position: relative;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 2px;
  }

  .app-version-line {
    display: block;
    margin: 0 0 12px;
    font-size: 13px;
    color: var(--color-text-muted);
  }

  /* Drag handles already had touch-action: none (see .drag-handle) even
     before touch dragging was actually implemented in enableDragReorder —
     just needed a bigger finger-sized target to grab, same reasoning as
     the Credit Card Pending checkbox's own enlarged hit box. */
  .drag-handle {
    padding: 10px 12px;
    font-size: 18px;
  }

  /* .metric-card-narrow's fixed 280px cap (see the base rule) exists to
     match Checking/Credit Card/Cash Flow's cards to each other on
     desktop, where they'd otherwise sit in a wide multi-column row
     alongside plenty of empty space. On a phone, everything else in
     these tabs already spans the full available width — a fixed-width
     card here would look like an unintentional gap beside it rather
     than a deliberate size match, so this goes back to filling the
     width the same way it did before that fix. */
  .metric-card-narrow {
    max-width: none;
  }

  /* ---- Overview: 3-column grid can't fit on a phone --------------------
     minmax(280px, 1fr) × 3 needs 840px minimum — this collapses it to a
     single column. Left at default DOM order, that would group by ROW
     instead of by column (all 3 metric cards together, then all 3
     action buttons, then all 3 titles, then all 3 mini-grids) — the
     explicit `order` values below re-sequence it so each of the three
     desktop COLUMNS' own pieces stay together instead: metric, action,
     title, and mini-grid for Credit Card as one group, then Checking's
     own four, then Cash Flow's own four — matching desktop's own
     left-to-right column order (see the base, non-mobile `order` rules
     above .overview-columns-row's definition). Cash Flow's own column
     is Checking Safe to Spend (a forward-looking projection, grouped
     with Cash Flow rather than Checking Balance's plain current-state
     figure) + Add Future Transaction + Upcoming Cash Flow + its
     mini-grid — that's how desktop's own column already groups it, not
     something introduced here; this only preserves whatever grouping
     desktop already has, just in the newly-swapped column order. */
  /* Tight base gap — this is now the "within a group" spacing (metric →
     its action → its title → its mini-grid). The bigger gap that
     separates one group from the next lives on each group's LAST piece
     (the mini-grid, below) rather than here, since a single flat `gap`
     value can't distinguish "related" from "new section" on its own.
     Two-tier spacing like this — small within a group, larger between
     groups — is standard practice (Material Design's spacing scale,
     Apple's HIG grouped-content spacing); one uniform gap everywhere is
     what made this tab read as evenly, endlessly spaced out rather than
     organized into clusters. margin-top bumped to match the "loose"
     tier too, separating this whole area from the tiles above it. */
  .overview-columns-row {
    grid-template-columns: 1fr;
    gap: 8px;
    margin-top: 28px;
  }

  #overview-cc-mini-grid,
  #overview-checking-mini-grid,
  #overview-cashflow-mini-grid {
    margin-bottom: 28px;
  }

  /* The two intro tiles (Remaining Budget, Pending Reimbursements) reuse
     .settings-section — a border-top + generous padding/margin divider
     style built for a long, scrollable Settings-style list. That reads
     as unnecessary "administrative" framing here, on a two-item
     dashboard intro, and it doesn't shrink for mobile at all — eating
     real space on the screen where it's scarcest. Lighter, tighter
     treatment for just these two on mobile; the border-top divider look
     stays exactly as-is everywhere it actually makes sense (Settings). */
  .overview-tiles-row {
    gap: 20px;
  }
  .overview-tiles-row > .settings-section {
    border-top: none;
    padding-top: 0;
    margin-top: 0;
  }

  /* De-emphasized, right-aligned action buttons instead of centered
     full-attention CTAs between every section — three big green buttons
     stacked down the page competed with each other for the same visual
     priority. All three stay, one under each section they belong to. */
  .overview-column-actions {
    justify-content: flex-end;
    margin: 4px 0;
  }
  .overview-column-actions .primary-btn {
    padding: 8px 14px;
    font-size: 14px;
  }
  /* The three numbers, in this order, each with its own Add button and mini-grid right under it:
     Credit Card Safe to Spend, Checking Safe to Spend, Checking Balance. */
  #overview-cc-safe-metric {
    order: 1;
  }
  #overview-cc-action {
    order: 2;
  }
  #overview-cc-mini-title {
    order: 3;
  }
  #overview-cc-mini-grid {
    order: 4;
  }

  #overview-checking-safe-metric {
    order: 5;
  }
  #overview-cashflow-action {
    order: 6;
  }
  #overview-cashflow-mini-title {
    order: 7;
  }
  #overview-cashflow-mini-grid {
    order: 8;
  }

  #overview-checking-balance-metric {
    order: 9;
  }
  #overview-checking-action {
    order: 10;
  }
  #overview-checking-mini-title {
    order: 11;
  }
  #overview-checking-mini-grid {
    order: 12;
  }

  /* Reimbursements & Refunds go BELOW the three numbers. The two tiles share a wrapper in the page, so the
     wrapper steps aside (display: contents) and the page's content becomes one column in which each tile
     takes its own place: Remaining Budget on top, then the numbers, then Reimbursements & Refunds. */
  #overview-content {
    display: flex;
    flex-direction: column;
  }

  .overview-tiles-row {
    display: contents;
  }

  .overview-bucket-section {
    order: 0;
  }

  .overview-columns-row {
    order: 1;
  }

  .overview-reimbursements-section {
    order: 2;
  }

  /* breathing room above it now that it follows the (collapsed) mini-grids */
  .overview-tiles-row > .overview-reimbursements-section {
    margin-top: 20px;
  }

  /* Mini-grids fetch/cache up to 10 rows (see overview.js), which is
     reasonable on desktop but too tall for a phone screen alongside
     everything else in this tab — caps what's actually DISPLAYED at 4
     without touching the fetch/cache logic itself (nth-child hides the
     rest purely visually, cheaper and simpler than re-slicing and
     re-rendering the same data for one breakpoint). */
  #overview-cc-mini-grid tbody tr:nth-child(n + 5),
  #overview-cashflow-mini-grid tbody tr:nth-child(n + 5),
  #overview-checking-mini-grid tbody tr:nth-child(n + 5) {
    display: none;
  }

  /* The collapse toggle earns its keep here, unlike on desktop (see its
     own base rule) — a phone screen is exactly the tight-on-space case
     where being able to collapse a mini-grid actually matters. */
  .overview-mini-grid-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    font-size: 20px;
    color: var(--color-text-muted); /* the same gray as the title beside it */
  }

  .overview-mini-grid-toggle:hover {
    color: var(--color-text-muted);
  }

  /* open: points down. closed: turned a quarter so it faces left. */
  .overview-mini-grid-toggle svg {
    width: 20px;
    height: 20px;
  }

  .overview-mini-grid-toggle[aria-expanded="false"] svg {
    transform: rotate(90deg);
  }

  /* title first, arrow right next to it (it used to be pushed to the far right edge) */
  .overview-mini-grid-title-row {
    justify-content: flex-start;
    gap: 2px;
    margin-bottom: 0;
  }

  .overview-mini-grid-title {
    font-size: 15px;
  }

  /* ---- Budget grid ------------------------------------------------------
     This one can't become a stacked-card list like the register tables —
     the whole point is comparing a month's numbers across rows, so it
     has to stay a real grid. The actual problem on mobile isn't the
     concept, it's the math: a fixed 200px month column + 160px sticky
     label column need 360px, more than fits after this page's own 40px
     of horizontal padding on most phones — and since
     .budget-grid-wrap-fixed deliberately disables free drag-scrolling
     (navigation is arrow-button-only, moving exactly one month via
     scrollLeft, so a stray finger-drag can't misalign it from a month
     boundary), there was no way to reveal whatever got clipped off the
     right edge. Sizing the month column to the ACTUAL remaining viewport
     width (rather than a guessed fixed one) makes "one month" mean
     "the one currently-aligned month, shown in full" the way the
     arrow-only navigation already intends, on any phone width. */
  .budget-grid-table th:not(:first-child),
  .budget-grid-table td:not(:first-child) {
    width: calc(100vw - 110px - 42px); /* 110px = this breakpoint's own shrunk label column below; 42px = .tab-content's 40px horizontal padding + .table-wrap's 2px border */
    min-width: calc(100vw - 110px - 42px);
  }

  .budget-grid-table th:first-child,
  .budget-grid-table td:first-child {
    width: 110px;
    min-width: 110px;
  }

  .budget-grid-table {
    font-size: 14px;
  }

  .budget-grid-table thead th {
    font-size: 12px;
  }

  /* .secondary-btn's own mobile touch-target padding (12px 18px) would
     otherwise conflict with this button's fixed 36px width — with
     box-sizing:border-box that padding alone exceeds the whole box,
     squishing the arrow glyph. Same idea (a properly-sized square tap
     target), sized to actually fit instead of fighting the generic
     button rule. */
  .budget-scroll-btn {
    width: 44px;
    height: 44px;
    padding: 0;
    font-size: 18px;
  }

  /* ---- Registers: table → stacked cards --------------------------------
     Checking, Credit Card, and Cash Flow's forecast table all share this
     class (Overview's own compact 3-column mini-grid previews
     deliberately don't — they're already narrow enough to stay real
     tables, and turning a short glance-able preview into multi-line
     cards would work against its "quick glance" purpose).

     Uses the data-label attribute every <td> in balance.js/creditcard.js/
     cashflow.js's render functions now carries — generic and robust to
     each table's different column set (Checking has a Balance column,
     Credit Card has Pending?, Cash Flow's forecast table has neither)
     without needing separate nth-child rules per table. */
  .mobile-stack thead {
    display: none;
  }

  .mobile-stack,
  .mobile-stack tbody,
  .mobile-stack tr {
    display: block;
    width: 100%;
  }

  .mobile-stack td {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 7px 0;
    border: none;
  }

  .mobile-stack td:not(:last-child) {
    border-bottom: 1px solid var(--color-border);
  }

  /* A real table cell with nothing in it (no envelope assigned, a
     future charge's blank Pending? cell) is visually unremarkable — but
     a stacked label:value row with a label and nothing after it looks
     broken. Hiding it entirely reads as "this field doesn't apply
     here," which is the more accurate story anyway. */
  .mobile-stack td:empty {
    display: none;
  }

  .mobile-stack td::before {
    content: attr(data-label);
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-muted);
    flex-shrink: 0;
  }

  /* The Name field reads as the card's own title — bold, no visible
     label prefix (its value is already self-descriptive), and no
     bottom divider since visually it IS the top of the card. */
  .mobile-stack td[data-label="Name"] {
    font-weight: 600;
    font-size: 15px;
    border-bottom: 1px solid var(--color-border);
  }

  .mobile-stack td[data-label="Name"]::before {
    content: none;
  }

  /* The drag handle gutter only made sense as a narrow fixed-width
     column sitting flush against a Date cell in a real table row — in
     the stacked layout each field is now its own full-width line, so
     the handle moves to float at the card's right edge instead (still
     inside the Date line, just no longer needing gutter spacing). */
  .mobile-stack .drag-handle-gutter {
    width: auto;
    order: 1;
  }

  .mobile-stack .amount-cell {
    font-weight: 600;
  }

  /* ---- Registers: ONE compact two-line row, the same in Checking, Credit Card and Cash Flow ----
     Line 1:  date              balance      amount
     Line 2:  name                           envelope
     Credit Card adds the Pending checkbox at the left, beside the date (it spans both lines so it's
     a big, easy target). Rows alternate in tone and are divided by a line you can see. The cells are
     placed by their data-label, so each table's own set of columns just works. */
  .mobile-stack tr.reg-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
      "date bal amount"
      "name name env";
    align-items: center;
    column-gap: 0;
    row-gap: 4px;
    margin: 0;
    padding: 9px 12px;
    border: none;
    border-bottom: 1px solid var(--color-row-line);
    border-radius: 0;
    background: var(--color-surface);
  }

  .mobile-stack tbody tr.reg-row:nth-child(even) {
    background: var(--color-row-alt);
  }

  .mobile-stack tr.reg-row:last-child {
    border-bottom: none;
  }

  /* Credit Card: a fixed column at the left for the Pending checkbox, so every date lines up whether or not a row has one */
  .cc-charges-table.mobile-stack tr.reg-row {
    grid-template-columns: 44px minmax(0, 1fr) auto auto;
    grid-template-areas:
      "lead date bal amount"
      "lead name name env";
    padding-left: 4px;
  }

  .mobile-stack tr.reg-row td {
    display: block;
    min-width: 0;
    width: auto;
    padding: 0;
    border: none;
    line-height: 1.35;
  }

  .mobile-stack tr.reg-row td:empty {
    display: none;
  }

  .mobile-stack tr.reg-row td::before {
    content: none;
  }

  .mobile-stack tr.reg-row td[data-label="Date"] {
    grid-area: date;
    font-size: 14px;
    color: var(--color-text);
    white-space: nowrap;
  }

  .mobile-stack tr.reg-row td[data-label="Name"] {
    grid-area: name;
    font-size: 15px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .mobile-stack tr.reg-row td[data-label="Envelope"] {
    grid-area: env;
    padding-left: 10px;
    max-width: 42vw;
    font-size: 13px;
    color: var(--color-text-muted);
    text-align: right;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .mobile-stack tr.reg-row td[data-label="Amount"] {
    grid-area: amount;
    padding-left: 10px;
    font-size: 16px;
    font-weight: 600;
    text-align: right;
    white-space: nowrap;
  }

  .mobile-stack tr.reg-row td[data-label="Card"] {
    grid-area: bal;
    padding-left: 10px;
    font-size: 12px;
    font-weight: 400;
    color: var(--color-text-muted);
    text-align: right;
    white-space: nowrap;
  }

  /* The running balance isn't shown on a phone: it made the row feel crowded. (It's still in the page; desktop shows it.) */
  .mobile-stack tr.reg-row td[data-label="Balance"] {
    display: none;
  }

  .mobile-stack tr.reg-row td.cc-pending-cell,
  .cc-charges-table.mobile-stack tr.reg-row td.cc-pending-cell {
    grid-area: lead;
    display: flex;
    align-items: center;
    justify-content: center;
    width: auto;
    padding: 0;
  }

  .cc-charges-table.mobile-stack tr.reg-row td:first-child {
    width: auto;
  }

  /* No drag handle on a phone, and nothing in its place: the order of same-date rows can't be changed there */
  .mobile-stack tr.reg-row .drag-handle-gutter {
    display: none;
  }

  /* the "Today" divider: a centred label on a line, as on desktop (the generic phone cell rules would turn it into a flex row) */
  .mobile-stack tr.register-divider-row {
    padding: 0;
    border: none;
    background: var(--color-surface);
  }

  .mobile-stack tr.register-divider-row td {
    display: block;
    width: auto; /* the Credit Card table gives its first cell a fixed 130px on desktop */
    padding: 4px 12px !important;
    text-align: center;
  }

  .mobile-stack tr.register-divider-row td::before {
    content: "";
    left: 12px;
    right: 12px;
  }

  /* a statement line is a band, not a transaction: tinted, and its long text may wrap */
  .mobile-stack tr.register-statement-row,
  .mobile-stack tbody tr.register-statement-row:nth-child(even) {
    background: var(--color-accent-soft);
  }

  .mobile-stack tr.register-statement-row td[data-label="Name"] {
    white-space: normal;
  }

  /* ---- Checking, Credit Card and Cash Flow: the screen is exactly one phone tall ----------------
     The summary and the controls stay pinned at the top, and ONLY the grid below them scrolls, inside its
     own frame (before, the page scrolled down to the grid and then the grid scrolled again). This is why
     the summary is slim and the controls are one tight row. */
  .tab-panel[data-tab="balance"],
  .tab-panel[data-tab="expenses"],
  .tab-panel[data-tab="cashflow"] {
    height: 100%;
  }

  #balance-content,
  #cc-content,
  #cashflow-content {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
  }

  #balance-content > .table-wrap,
  #cc-content > .table-wrap,
  #cashflow-content > .settings-section:last-child .table-wrap {
    flex: 1 1 0;
    min-height: 0;
    max-height: none;
    overflow-y: auto;
    margin-bottom: 0;
  }

  #cashflow-content > .settings-section:last-child {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    margin-top: 0;
    padding-top: 4px;
    border-top: none;
  }

  #cashflow-content > .settings-section:last-child .panel-header-row {
    flex-wrap: nowrap;
    justify-content: space-between;
    margin-bottom: 8px;
  }

  #cashflow-content > .settings-section:last-child .panel-header-row h3 {
    font-size: 16px;
  }

  /* ---- Slim summary: one line per figure ---------------------------------------------------- */
  #balance-content > .metric-card,
  .metrics-row .metric-card {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 2px 10px;
    margin: 0 0 8px;
    padding: 8px 14px;
    box-shadow: none;
    border-color: var(--color-border-strong);
  }

  #cc-content .metrics-row,
  #cashflow-content .metrics-row {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 0;
    margin-bottom: 0;
  }

  #balance-content > .metric-card .metric-label,
  .metrics-row .metric-card .metric-label {
    font-size: 11px;
  }

  #balance-content > .metric-card .metric-value,
  .metrics-row .metric-card .metric-value {
    font-size: 20px;
  }

  /* Credit Card: only the Current Balance (the statement shows as a line in the list). Recording a payment isn't offered here. */
  #cc-statement-card,
  #add-cc-payment-btn {
    display: none;
  }

  /* Cash Flow's card picker sits under its figure, full width, instead of making the card taller on the side */
  #cashflow-content .overview-cc-card-select {
    flex-basis: 100%;
    max-width: none;
    margin-top: 2px;
  }

  /* ---- Controls: dropdowns and a green + box on one row ------------------------------------- */
  .tab-controls-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 44px;
    align-items: center;
    gap: 8px;
    margin: 0 0 8px;
  }

  .tab-controls-row .balance-header-row,
  .tab-controls-row .panel-header-actions {
    display: contents;
  }

  /* The words "Type:", "View:" and so on aren't drawn on a phone (the choice in each box says it); they stay
     as the box's accessible name. */
  .tab-controls-row .balance-month-select-label {
    min-width: 0;
    gap: 0;
    font-size: 0;
  }

  .tab-controls-row .balance-month-select-label select {
    width: 100%;
    min-width: 0;
    min-height: 44px;
    padding: 4px 6px;
    font-size: 13px; /* small enough that "All Transactions" fits beside a second box and the + on a 360px phone */
  }

  #cc-card-select-wrap .info-tooltip {
    display: none;
  }

  /* Credit Card has three dropdowns, so two rows:   [ Card ............ ] [+]
                                                       [ Envelope ] [ View ....... ] */
  #cc-content .tab-controls-row #cc-card-select-wrap {
    grid-column: 1 / 3;
    grid-row: 1;
  }

  #cc-content .tab-controls-row #add-cc-expense-btn {
    grid-column: 3;
    grid-row: 1;
  }

  #cc-content .tab-controls-row #cc-envelope-select-wrap {
    grid-column: 1;
    grid-row: 2;
  }

  #cc-content .tab-controls-row #cc-cycle-select-wrap {
    grid-column: 2 / 4;
    grid-row: 2;
  }

  /* With a single card there is no Card picker, so the three controls share ONE row: [ Envelope ] [ View ] [+] */
  #cc-content .tab-controls-row:has(#cc-card-select-wrap.hidden) #cc-envelope-select-wrap {
    grid-column: 1;
    grid-row: 1;
  }

  #cc-content .tab-controls-row:has(#cc-card-select-wrap.hidden) #cc-cycle-select-wrap {
    grid-column: 2;
    grid-row: 1;
  }

  /* The green + box: no words on a phone (the label stays for screen readers) */
  .add-plus-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    font-size: 28px;
    line-height: 1;
  }

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

  .add-plus-btn .btn-plus {
    font-size: 28px;
    font-weight: 500;
  }

  /* ---- Future/Recurring: chips for the four groups, a list that scrolls inside, compact rows ----
     Same screen shape as Checking and Credit Card: controls pinned (Account, View, a + box), a row of chips
     choosing Expenses / Income / Transfers / Debt, and the chosen group's list filling the rest. */
  .tab-panel[data-tab="recurring"] {
    height: 100%;
    display: flex;
    flex-direction: column;
  }

  .recurring-chips {
    display: flex;
    gap: 6px;
    margin: 0 0 8px;
  }

  .recurring-chip {
    flex: 1;
    min-width: 0;
    min-height: 40px;
    padding: 0 6px;
    font-size: 14px;
    white-space: nowrap;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-border-input);
    border-radius: var(--radius-sm);
  }

  .recurring-chip[aria-pressed="true"] {
    color: var(--color-bg);
    background: var(--color-text);
    border-color: var(--color-text);
  }

  /* one group at a time; the heading is the chip's job now */
  .recurring-groups {
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
  }

  .recurring-group {
    display: none;
  }

  .tab-panel[data-chip="expense"] .recurring-group[data-type="expense"],
  .tab-panel[data-chip="income"] .recurring-group[data-type="income"],
  .tab-panel[data-chip="transfer"] .recurring-group[data-type="transfer"],
  .tab-panel[data-chip="debt_payment"] .recurring-group[data-type="debt_payment"] {
    display: block;
  }

  .recurring-group-header {
    display: none;
  }

  .recurring-groups .table-wrap {
    margin: 0;
    border: none;
    border-radius: 0;
    box-shadow: none;
  }

  .recurring-groups .placeholder-note {
    margin: 0;
    padding: 16px;
  }

  /* the row:   Name ................................. -$1,400.00
                Monthly on the 1st · Checking .......... [Series]   */
  .mobile-stack tr.rec-row {
    display: grid;
    grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
    grid-template-areas:
      "name name amount"
      "freq target series";
    align-items: center;
    column-gap: 0;
    row-gap: 4px;
    margin: 0;
    padding: 9px 12px;
    border: none;
    border-bottom: 1px solid var(--color-row-line);
    border-radius: 0;
    background: var(--color-surface);
  }

  .mobile-stack tbody tr.rec-row:nth-child(even) {
    background: var(--color-row-alt);
  }

  .mobile-stack tr.rec-row:last-child {
    border-bottom: none;
  }

  .mobile-stack tr.rec-row td {
    display: block;
    min-width: 0;
    width: auto;
    padding: 0;
    border: none;
    line-height: 1.35;
  }

  .mobile-stack tr.rec-row td:empty {
    display: none;
  }

  .mobile-stack tr.rec-row td::before {
    content: none;
  }

  /* no reordering on a phone, same as the registers */
  .mobile-stack tr.rec-row td.recurring-table-handle-cell {
    display: none;
  }

  .mobile-stack tr.rec-row td[data-label="Name"] {
    grid-area: name;
    font-size: 15px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .mobile-stack tr.rec-row td[data-label="Amount"] {
    grid-area: amount;
    padding-left: 10px;
    font-size: 16px;
    font-weight: 600;
    text-align: right;
    white-space: nowrap;
  }

  .mobile-stack tr.rec-row[data-type="expense"] td[data-label="Amount"],
  .mobile-stack tr.rec-row[data-type="debt_payment"] td[data-label="Amount"] {
    color: var(--color-negative);
  }

  .mobile-stack tr.rec-row[data-type="income"] td[data-label="Amount"] {
    color: var(--color-positive);
  }

  .mobile-stack tr.rec-row td[data-label="Frequency"] {
    grid-area: freq;
    font-size: 13px;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .mobile-stack tr.rec-row td[data-label="Target"] {
    grid-area: target;
    padding-left: 6px;
    font-size: 13px;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .mobile-stack tr.rec-row td[data-label="Target"]::before {
    content: "\00b7  ";
  }

  .mobile-stack tr.rec-row td.recurring-table-action-cell {
    grid-area: series;
    padding-left: 8px;
    text-align: right;
  }

  /* "Edit Transaction Series" is too long for a row: it reads "Series" (the full words are its accessible name) */
  .mobile-stack tr.rec-row .recurring-series-btn {
    min-height: 32px;
    padding: 0 12px;
    font-size: 0;
  }

  .mobile-stack tr.rec-row .recurring-series-btn::before {
    content: "Series";
    font-size: 13px;
  }

  /* ---- Tab headings: redundant on mobile, since the bottom nav already
     shows which tab you're on — hiding these frees up meaningful
     vertical space on a phone screen. Scoped to h2 specifically (every
     panel has exactly one, the tab's own title) rather than the whole
     .panel-header-row, since several of those rows also hold real
     controls (Add buttons, filters) that still need to show. */
  .tab-panel h2 {
    display: none;
  }

  /* .panel-header-row normally uses space-between to put its h2 on the
     left and any actions on the right — with the h2 gone, a single
     remaining flex child (e.g. just an Add button) would otherwise fall
     back to sitting flush left instead, which reads as unintentional
     rather than an intentional single-button layout. */
  .panel-header-row {
    justify-content: flex-end;
  }

  /* ---- Header: drop the least essential elements, tighten padding, and
     size the logo to actually use the header's height ---------------- */
  /* The header is two rows: logo and buttons, then the search bar as a full-width strip.
     (Before, the desktop's three-column grid was kept here with the search bar pulled out of
     it, which left a 240px empty column and pushed the Log Out button off the right edge:
     the page was wider than the screen, which is what made it sometimes open not quite
     full-screen.) */
  .top-bar {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 8px 12px;
    padding: calc(8px + var(--safe-area-top)) calc(16px + var(--safe-area-right)) 0 calc(16px + var(--safe-area-left));
  }

  .top-bar-left {
    grid-column: 1;
    grid-row: 1;
    min-width: 0;
  }

  .top-bar-right {
    grid-column: 2;
    grid-row: 1;
    gap: 8px;
  }

  .top-bar-search {
    display: flex;
    align-items: center;
    gap: 8px;
    position: relative;
    grid-column: 1 / -1;
    grid-row: 2;
    justify-self: stretch;
    width: auto;
    max-width: none;
    /* full-bleed: out to the header's own edges, with the header's side padding put back inside */
    margin: 0 calc(-16px - var(--safe-area-right)) 0 calc(-16px - var(--safe-area-left));
    padding: 8px calc(16px + var(--safe-area-right)) 8px calc(16px + var(--safe-area-left));
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    z-index: 100; /* above the bottom bar, so the full-screen search results can cover it */
  }

  /* The search strip is a light surface on a phone, but the base input was styled for the dark
     header (light text on a translucent white box): typed text was white on white. */
  .global-search-input {
    flex: 1;
    min-width: 0;
    width: auto;
    min-height: 44px;
    color: var(--color-text);
    background: var(--color-bg);
    border: 1px solid var(--color-border-input); /* the same input border every other field uses (3:1 against the page) */
  }

  .global-search-input::placeholder {
    color: var(--color-text-muted);
  }

  .global-search-input:focus {
    background: var(--color-surface);
    border-color: var(--color-accent);
  }

  /* Manage: a gear to the right of the search box. Lit when you're in Manage. */
  .header-gear-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 44px;
    height: 44px;
    padding: 0;
    background: none;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    color: var(--color-text);
  }

  .header-gear-btn.active {
    background: var(--color-accent-soft);
    border-color: var(--color-accent);
    color: var(--color-accent);
  }

  /* Search results: the whole screen under the header, not a small dropdown. */
  .global-search-results {
    position: fixed;
    top: var(--search-results-top, 120px);
    left: 0;
    right: 0;
    bottom: 0;
    max-height: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    z-index: 150;
    padding-bottom: var(--safe-area-bottom);
    background: var(--color-bg);
  }

  .global-search-results-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    position: sticky;
    top: 0;
    padding: 8px 16px;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    font-size: 14px;
    color: var(--color-text-muted);
  }

  .global-search-result-row {
    padding: 14px 16px;
    min-height: 56px;
    align-items: center;
    background: var(--color-surface);
  }

  .global-search-result-name,
  .global-search-result-amount {
    font-size: 16px;
  }

  .global-search-result-meta {
    font-size: 13px;
  }

  .tab-content {
    padding-top: 16px;
    padding-bottom: 12px;
  }

  .header-logo {
    height: 40px; /* paired with the cropped logo-header-cropped.png asset (see index.html) — sizing the ORIGINAL uncropped file this tall would just enlarge its own built-in transparent padding along with it, not fill the header any more than before */
  }

  /* hidden visually on a phone, but still the page's heading for screen readers */
  .app-name {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .user-email {
    display: none;
  }

  /* ---- Roomier rows everywhere else ---------------------------------------------------------
     The registers and the Recurring list got more padding in their own rules above; the rest of the
     app's rows get the same extra breathing room here. */
  #overview-cc-mini-grid td,
  #overview-cashflow-mini-grid td,
  #overview-checking-mini-grid td {
    padding-top: 13px;
    padding-bottom: 13px;
  }

  .account-row {
    padding: 16px 14px;
  }

  .recurring-list-row {
    padding: 14px 14px;
  }

  .reimbursement-row {
    padding: 13px 14px;
  }

  .mobile-stack td {
    padding-top: 9px;
    padding-bottom: 9px;
  }

  /* ---- Tap targets: at least ~44px for anything a thumb has to hit ---------------- */
  .refresh-btn {
    width: 44px;
    height: 44px;
    font-size: 20px;
  }

  .logout-btn {
    min-height: 44px;
    padding: 0 16px;
    font-size: 15px;
  }

  .bottom-tab-nav-btn {
    min-height: 52px;
  }

  .modal-close-x {
    min-width: 44px;
    min-height: 44px;
    font-size: 28px;
  }

  .cc-pending-hitbox {
    width: 44px;
    height: 44px;
  }

  /* The (i) icon stays small to look at; its tappable area grows around it without moving anything. */
  .info-tooltip-trigger {
    position: relative;
  }
  .info-tooltip-trigger::after {
    content: "";
    position: absolute;
    inset: -14px;
  }

  /* ---- Forms: Save / Cancel stay on screen while the form scrolls ----------------- */
  .modal-box .modal-actions {
    position: sticky;
    /* sticky measures from the dialog's content edge, not its padding edge: the negative offset
       (the dialog's own bottom padding) is what puts the bar flush with the bottom of the screen */
    bottom: calc(-20px - var(--safe-area-bottom));
    z-index: 2;
    margin: 22px -20px calc(-20px - var(--safe-area-bottom));
    padding: 12px 20px calc(12px + var(--safe-area-bottom));
    background: var(--color-surface);
    border-top: 1px solid var(--color-border);
  }
}


/* =============================================================================
   ACCESSIBILITY (0.2.8.0)
   ============================================================================= */

/* Text for screen readers only. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* First thing a keyboard user can reach: jump past the header and navigation. */
.skip-link {
  position: absolute;
  top: -100px;
  left: 8px;
  z-index: 10000;
  padding: 10px 16px;
  background: var(--color-surface);
  color: var(--color-text);
  border: 2px solid var(--color-accent);
  border-radius: var(--radius-sm);
  font-weight: 600;
}
.skip-link:focus {
  top: 8px;
}

/* One clear focus ring for everything that can take focus. (Several controls used
   to remove the outline and rely on a pale tint, which is too faint to see.) */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--color-accent-hover);
  outline-offset: 2px;
}
/* On the dark header and nav bar the ring must be light to show. */
.top-bar :focus-visible,
.tab-nav :focus-visible,
.bottom-tab-nav :focus-visible {
  outline-color: #fff;
}
/* Rows and labels that act as buttons get an inset ring so it isn't clipped by the table. */
tr:focus-visible,
.global-search-result-row:focus-visible,
.reimbursement-row:focus-visible,
.account-row:focus-visible,
.recurring-list-row:focus-visible,
.category-toggle-row:focus-visible,
.bucket-edit-name:focus-visible,
.drag-handle:focus-visible {
  outline: 3px solid var(--color-accent-hover);
  outline-offset: -3px;
}
[tabindex="-1"]:focus {
  outline: none; /* headings/containers focused by the app itself, not a control */
}

.drag-handle {
  cursor: grab;
}

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

/* "Refund · Visa" marker beside a refund's name in the reimbursement lists. */
.reimbursement-kind-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--color-pill);
  color: var(--navy-100);
  font-size: var(--text-xs);
  font-weight: 600;
  vertical-align: 1px;
}

/* =============================================================================
   Debt payment schedule (0.2.17.0): the "View payment schedule" button in the
   debt form, and the schedule modal (summary figures, extra-payment what-if,
   principal-vs-interest table).
============================================================================= */
.debt-schedule-btn {
  width: 100%;
  margin-bottom: 12px;
}

.debt-schedule-box {
  max-width: 600px;
}

.debt-schedule-box h3 {
  padding-right: 36px;
}

.debt-schedule-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px 16px;
  margin-bottom: 14px;
}

.debt-schedule-stat {
  margin: 0;
}

.debt-schedule-stat dt {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0;
}

.debt-schedule-stat dd {
  margin: 2px 0 0;
  font-weight: 600;
  color: var(--color-text);
}

.debt-schedule-empty,
.debt-schedule-saving {
  grid-column: 1 / -1;
  margin: 0;
}

.debt-schedule-saving {
  color: var(--color-positive);
  font-weight: 600;
}

.debt-schedule-warning {
  color: var(--color-negative);
  font-weight: 600;
}

.debt-schedule-table-wrap {
  max-height: 40vh;
  overflow: auto;
  margin: 8px 0 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
}

.debt-schedule-grid {
  width: 100%;
  font-variant-numeric: tabular-nums;
}

.debt-schedule-grid th {
  position: sticky;
  top: 0;
  background: var(--color-surface);
}

@media (max-width: 640px) {
  .debt-schedule-box {
    padding: 20px 16px;
  }

  .debt-schedule-grid {
    font-size: var(--text-xs);
  }

  .debt-schedule-grid th,
  .debt-schedule-grid td {
    padding: 8px 3px;
    white-space: nowrap;
  }

  .debt-schedule-grid th {
    letter-spacing: 0;
  }

  /* the payment number is the one column a phone can do without: it keeps the Balance column on screen */
  .debt-schedule-grid th:first-child,
  .debt-schedule-grid td:first-child {
    display: none;
  }

  .debt-schedule-table-wrap {
    max-height: 45vh;
  }
}

/* Debt form (0.2.17.1): the note and buttons that tie the Monthly Payment to the debt's recurring payment. */
.debt-link-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 8px 0 12px;
}

.debt-link-actions:empty {
  display: none;
}

.debt-link-btn {
  flex: 1 1 auto;
}

#debt-min-payment[readonly] {
  background: var(--color-bg);
  color: var(--color-text-muted);
}

/* =============================================================================
   Overview > Reimbursements & Refunds, DESKTOP only (0.2.19.1): the list is a
   fixed-size window that scrolls inside itself once there are more rows than fit
   (it used to grow with every pending item and stretch the tile), and the rows are
   a little more compact. The phone keeps its own layout (rows are untouched there).
   Its height always matches the Remaining Budget list beside it, never under 150px:
   overview.js (syncReimbursementWindowHeight) sets --reimbursement-window-height.
============================================================================= */
@media (min-width: 641px) {
  #overview-reimbursements-list .recurring-list-wrap {
    height: var(--reimbursement-window-height, 150px);
    min-height: 150px;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  #overview-reimbursements-list .reimbursement-row {
    padding: 6px 12px;
  }

  #overview-reimbursements-list .reimbursement-row-name {
    margin-bottom: 2px;
  }

  #overview-reimbursements-list .reimbursement-row-detail-line {
    gap: 4px 10px;
  }

  #overview-reimbursements-list .reimbursement-receive-btn {
    padding: 3px 9px;
  }
}
