/* =====================================================================
   MONTHLY MONEY TRACKER - STYLES

   Pure-black minimalist monochrome, 21 sections.

   Two conventions run through the whole file and explain most of what
   looks unusual here:

   1. THE UI IS GREYSCALE. The only colour anywhere is the donut chart and
      its legend dots, because same-shade slices are impossible to tell
      apart. Everything else carries meaning through brightness, fill, and
      shape instead - progress bars brighten rather than turning red, and
      a primary button is filled where a secondary one is outlined.

   2. MOTION IS OPTIONAL. Every animation in this file is switched off
      under prefers-reduced-motion at the bottom.
   ===================================================================== */

/* =====================
   Global Reset & Base
===================== */
* {
  box-sizing: border-box;
  /* Kills the grey flash iOS paints over any tapped element. The app has
     its own :active states, and the double feedback looks broken. */
  -webkit-tap-highlight-color: transparent;
}

:root {
  /* Native Fast: frequent interactions should finish before they feel like
     something the user has to wait through. These four tokens keep every
     surface on the same compact rhythm. */
  --motion-press: 60ms;
  --motion-fast: 120ms;
  --motion-standard: 180ms;
  --motion-emphasis: 220ms;
  --ease-native: cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-in-native: cubic-bezier(0.4, 0, 1, 1);
  --ease-spring: cubic-bezier(0.34, 1.5, 0.64, 1);
  --surface-page: #000;
  --surface-card: #111;
  --surface-raised: #171717;
  --surface-field: #1a1a1a;
  --border-subtle: #242424;
  --border-strong: #303030;
  --text-primary: #f5f5f5;
  --text-secondary: #a0a0a0;
  --text-tertiary: #929292;
  --surface-action: #e8e8e8;
  --text-on-action: #0a0a0a;
  --focus-ring: #b0b0b0;
  --radius-control: 10px;
  --radius-card: 16px;
  --radius-sheet: 20px;
  --space-page: 20px;
  --space-card: 16px;
  --shadow-card: 0 1px 0 rgba(255, 255, 255, 0.025) inset;
  --shadow-sheet: 0 24px 70px rgba(0, 0, 0, 0.72), 0 1px 0 rgba(255, 255, 255, 0.04) inset;
  --shadow-nav: 0 -12px 34px rgba(0, 0, 0, 0.45);
  --semantic-in:#7fae8c;
  --semantic-out:#c78181;
}

/* Light is intentionally a quiet paper version of the same monochrome system,
   not a separate visual identity. System leaves the existing dark PWA chrome
   untouched and follows the device where the browser supports it. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="light"] body { background:#f5f5f3; color:#171717; }
:root[data-theme="light"] .app,
:root[data-theme="light"] #history-view { background:#f5f5f3; color:#171717; }
:root[data-theme="light"] .card,
:root[data-theme="light"] .chart-container,
:root[data-theme="light"] .settings-panel,
:root[data-theme="light"] .modal-card { background:#fff; color:#171717; border-color:#ddd; box-shadow:0 1px 0 rgba(0,0,0,.04); }
:root[data-theme="light"] input,
:root[data-theme="light"] select,
:root[data-theme="light"] textarea,
:root[data-theme="light"] .setting-select,
:root[data-theme="light"] .setting-input { background:#f2f2f0; color:#171717; border-color:#d0d0cc; color-scheme:light; }
:root[data-theme="light"] .settings-btn,
:root[data-theme="light"] .setting-row,
:root[data-theme="light"] #tab-bar { background:rgba(255,255,255,.92); border-color:#ddd; }
:root[data-theme="light"] .setting-btn-action { background:#e8e8e5; color:#222; }
:root[data-theme="light"] .settings-group { background:#fafaf8; border-color:#deded9; }

/* Complete light palette. The app started dark-only, so a few individual
   components intentionally used explicit dark fills instead of tokens. Keep
   those overrides together here; light mode must never leave dark text on a
   dark surface, or white text on a white surface. */
:root[data-theme="light"] {
  --surface-page:#f5f5f3;
  --surface-card:#ffffff;
  --surface-raised:#f6f6f4;
  --surface-field:#f3f3f1;
  --border-subtle:#deded9;
  --border-strong:#c9c9c3;
  --text-primary:#171717;
  --text-secondary:#62625f;
  --text-tertiary:#656560;
  --surface-action:#202020;
  --text-on-action:#fff;
  --focus-ring:#575751;
  --shadow-card:0 1px 2px rgba(0,0,0,.05);
  --shadow-sheet:0 24px 70px rgba(0,0,0,.18),0 1px 0 rgba(0,0,0,.04) inset;
  --shadow-nav:0 -6px 24px rgba(0,0,0,.08);
  --semantic-in:#327048;
  --semantic-out:#9b3f3f;
}
:root[data-theme="light"] .card,
:root[data-theme="light"] .chart-container,
:root[data-theme="light"] .history-row { background:#fff; }
:root[data-theme="light"] #priority-list { background:#fff; }
:root[data-theme="light"] .second-form,
:root[data-theme="light"] .wallet-form { background:#f7f7f5; }
:root[data-theme="light"] #priority-list li { background:#fff; border-color:#e4e4e0; color:#1a1a1a; }
:root[data-theme="light"] .second-form input,
:root[data-theme="light"] .second-form select,
:root[data-theme="light"] .wallet-card input,
:root[data-theme="light"] .wallet-form input { background:#fff; border-color:#d2d2cd; color:#1a1a1a; }
:root[data-theme="light"] .second-form select option { background:#fff; color:#1a1a1a; }
:root[data-theme="light"] .second-form input::placeholder,
:root[data-theme="light"] .second-form select:invalid,
:root[data-theme="light"] .date-placeholder { color:var(--text-tertiary); }
:root[data-theme="light"] .second-form input[type="date"]::-webkit-calendar-picker-indicator { filter:none; }
:root[data-theme="light"] .table-toggle,
:root[data-theme="light"] thead,
:root[data-theme="light"] .wallets-count { color:#6a6a66; border-color:#d9d9d4; }
:root[data-theme="light"] tbody tr { border-color:#deded9; }
:root[data-theme="light"] #take-money,
:root[data-theme="light"] #save-priority,
:root[data-theme="light"] .actions [data-role="take-btn"] { border-color:#bdbdb8; color:#454541; }
:root[data-theme="light"] .transfer-btn { border-color:#bdbdb8; color:#5d5d59; }
:root[data-theme="light"] .transfer-btn:active { background:#ecece8; }
:root[data-theme="light"] .spend-bar-track { background:#e1e1dd; }
:root[data-theme="light"] .spend-bar-fill { background:#30302e; }
:root[data-theme="light"] .spend-bar-limit { background:#1d1d1c; }
:root[data-theme="light"] .spend-bar-label,
:root[data-theme="light"] .projection-line { color:#60605c; }
:root[data-theme="light"] .chart-head-value,
:root[data-theme="light"] .legend-amount { color:#1a1a1a; }
:root[data-theme="light"] .legend-item { color:#5d5d59; }
:root[data-theme="light"] .legend-divide { border-color:#d4d4d0; }
:root[data-theme="light"] .chart-bar-empty { background:#e2e2de; }
:root[data-theme="light"] .lock-badge { background:#e7e7e3; border-color:#d0d0ca; color:#5d5d59; }
:root[data-theme="light"] .transfer-dest-btn,
:root[data-theme="light"] .stat-cell { background:#f4f4f1; border-color:#deded9; color:#20201e; }
:root[data-theme="light"] .transfer-dest-sub,
:root[data-theme="light"] .stat-label,
:root[data-theme="light"] .stat-sub,
:root[data-theme="light"] .history-sub,
:root[data-theme="light"] .history-meta { color:#72726d; }
:root[data-theme="light"] .stat-value { color:#20201e; }
:root[data-theme="light"] .history-detail { border-color:#deded9; }
:root[data-theme="light"] .history-delete { border-color:#cacac5; color:#666661; }
:root[data-theme="light"] .history-delete:active { background:#ecece8; }
:root[data-theme="light"] .empty-state,
:root[data-theme="light"] .empty-state-rich,
:root[data-theme="light"] .chart-empty,
:root[data-theme="light"] .trend-empty { color:var(--text-secondary); }
:root[data-theme="light"] .empty-state-rich { border-color:#cacac5; }
:root[data-theme="light"] .tab-btn.is-active svg { background:#e7e7e3; box-shadow:none; }
:root[data-theme="light"] .settings-btn:active { background:#e7e7e3; color:var(--text-primary); }
:root[data-theme="light"] .toggle-slider { background:#d5d5d0; }
:root[data-theme="light"] .toggle input:checked + .toggle-slider { background:#202020; box-shadow:none; }
:root[data-theme="light"] .toggle input:checked + .toggle-slider::before { background:#fff; }
:root[data-theme="light"] .spend-bar-projected { background:#bcbcb6; }
:root[data-theme="light"] .activity-search-controls select option { background:#fff; color:#171717; }
:root[data-theme="light"] .activity-search-controls input[type="date"]::-webkit-calendar-picker-indicator { filter:none; }
:root[data-theme="light"] .skeleton { background:linear-gradient(90deg,#e9e9e5 25%,#f3f3f0 50%,#e9e9e5 75%); background-size:220% 100%; }

.wallet-settings-row small { display:block; margin-top:3px; color:var(--text-tertiary); font-size:11px; }
.recurring-form { margin-top:14px; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--surface-page);
  color: var(--text-primary);
  /* System font stack - no webfont, so nothing to download and nothing to
     fail offline. */
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
  /* Fixed-width digits, globally. Money figures count up when they change,
     and without this every digit is a different width so the number jiggles
     and reflows mid-animation. */
  font-variant-numeric: tabular-nums;
}

/* Prevent accidental double-tap enlargement while interacting with the app. */
html { touch-action: manipulation; }

body {
  min-height: 100vh;
  min-height: 100dvh;
}

button,
input,
select,
textarea {
  font: inherit;
}

input,
select,
textarea {
  color-scheme: dark;
}

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

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  margin: 0;
  -webkit-appearance: none;
}

input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text-primary);
  box-shadow: 0 0 0 1000px var(--surface-field) inset;
  transition: background-color 9999s ease-out;
}

input[type="checkbox"] {
  /* Near-white tick, since the browser default is a bright blue that would
     be the only colour outside the chart. */
  accent-color: var(--surface-action);
}

/* Immediate tactile feedback for custom controls. The pressed state arrives
   in 60ms; release uses the normal fast token. Individual controls can still
   override colour without losing the shared physical response. */
button {
  transition: transform var(--motion-fast) var(--ease-native),
              opacity var(--motion-fast) ease,
              background-color var(--motion-fast) ease,
              color var(--motion-fast) ease;
}

button:active {
  transform: scale(0.97);
  transition-duration: var(--motion-press);
}

button:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Component focus rules remove the default outline to suppress browser-blue.
   This final class-independent ring restores a consistent keyboard affordance. */
:where(button, input, select, textarea, a, [tabindex]):focus-visible {
  outline: 2px solid var(--focus-ring) !important;
  outline-offset: 2px !important;
}

/* =====================
   PWA Update Banner
===================== */
.update-banner {
  position: fixed;
  z-index: 11000;
  top: max(12px, env(safe-area-inset-top, 0px));
  left: 50%;
  width: min(calc(100% - 24px), 460px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 12px;
  padding: 12px 12px 12px 14px;
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  background: var(--surface-raised);
  box-shadow: var(--shadow-sheet);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  transform: translateX(-50%);
  animation: notice-in var(--motion-standard) var(--ease-native);
}

.update-banner strong,
.update-banner span { display: block; }
.update-banner strong { color: var(--text-primary); font-size: 13px; }
.update-banner span { margin-top: 2px; color: var(--text-secondary); font-size: 11px; line-height: 1.35; }
.update-banner button { min-height: 44px; padding: 8px 12px; border: 0; border-radius: 9px; background: var(--surface-action); color: var(--text-on-action); font-size: 12px; font-weight: 700; }
.update-banner .update-dismiss { width: 44px; padding: 0; background: transparent; color: var(--text-secondary); font-size: 0; }
.update-banner .update-dismiss::before { content: "×"; font-size: 21px; font-weight: 400; }

@keyframes notice-in {
  from { opacity: 0; transform: translateX(-50%) translateY(-8px); }
}

/* =====================
   Pull to Refresh
===================== */
/* Sits behind the app at the top of the viewport and is revealed as the whole
   page is dragged down. pointer-events: none matters - it spans the full width
   under the finger, and without it the drag would land on this instead of the
   content. Opacity is driven from JS in step with the drag distance. */
.pull-indicator {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: #888;
  font-size: 13px;
  z-index: 500;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--motion-fast) ease;
}

.pull-spinner {
  animation: spin 0.8s linear infinite;
}

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

/* =====================
   App Container
===================== */
/* Both views share this. The 480px cap keeps the phone layout intact on a
   desktop browser rather than stretching the cards across a wide window -
   this is a phone app that happens to open anywhere. */
.app {
  max-width: 480px;
  margin: 0 auto;
  padding: var(--space-page);
}

/* Room for the fixed tab bar, so the last row of a list is never parked
   underneath it. env(safe-area-inset-bottom) covers the iPhone home
   indicator, which sits in the same strip. */
#app-view {
  /* Exactly the bar's own height plus a small margin, set by syncTabBarSpace()
     once it can be measured - the bar's height already includes the safe-area
     inset, so adding env() here again would double-count it and leave a strip
     of dead space that scrolls. The literal is the pre-measurement fallback. */
  padding-bottom: var(--tab-bar-space, 96px);
}

/* =====================
   Tab Bar
===================== */
/* One panel is visible at a time; the rest carry `hidden`. They stay in the
   DOM so every render function finds its elements regardless of which tab is
   open - nothing needs to know about tabs. */
.tab-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  max-width: 480px;
  margin: 0 auto;
  /* The extra bottom padding lifts the labels clear of the iPhone home
     indicator, which sits inside the safe-area inset rather than below it -
     inset alone left the words touching the bar. */
  gap: 2px;
  padding: 7px 8px calc(16px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(180deg, rgba(15, 15, 15, 0.94), rgba(0, 0, 0, 0.97));
  /* The blur is what stops content scrolling underneath from reading as a
     smear of grey behind the labels. */
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  border: 1px solid var(--border-subtle);
  border-bottom: none;
  border-radius: 18px 18px 0 0;
  box-shadow: var(--shadow-nav);
}

.tab-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-height: 50px;
  padding: 5px 0 4px;
  background: none;
  border: none;
  /* Dim by default; the active tab is the only near-white one. Greyscale, so
     brightness carries the selection the way colour would elsewhere. */
  color: var(--text-tertiary);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-family: inherit;
  cursor: pointer;
  touch-action: manipulation;
  transition: color var(--motion-fast) ease,
              transform var(--motion-fast) var(--ease-native),
              opacity var(--motion-fast) ease;
}

.tab-btn svg {
  width: 28px;
  height: 28px;
  padding: 3px;
  border-radius: 9px;
  transition: background-color var(--motion-fast) ease, transform var(--motion-fast) var(--ease-native);
}

.tab-btn.is-active {
  color: var(--text-primary);
}

.tab-btn.is-active svg {
  background: #202020;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.045) inset;
  transform: translateY(-1px);
}

.tab-btn:active {
  opacity: 0.72;
  transform: scale(0.96);
}

/* Empty state for the Wallets tab. Dim and centred - it is a prompt, not
   content, and should not compete with a real wallet card. */
.wallets-empty {
  text-align: center;
  padding: 40px 20px;
  color: #6a6a6a;
}

.wallets-empty p {
  margin: 0 0 6px;
  font-size: 15px;
  color: #9a9a9a;
}

.wallets-empty-note {
  font-size: 13px;
  line-height: 1.5;
  margin-bottom: 18px !important;
  color: #6a6a6a !important;
}

/* Outlined, not filled: adding a wallet is an offer, not the tab's primary
   action - the shape carries that the way colour would elsewhere. */
#wallets-empty-add {
  background: none;
  color: #ccc;
  border: 1px solid #2a2a2a;
  border-radius: 999px;
  padding: 10px 20px;
  font-size: 14px;
  font-family: inherit;
  cursor: pointer;
}

#wallets-empty-add:active {
  opacity: 0.6;
}

/* =====================
   Settings
===================== */
.settings-bar {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  position: relative;
}

.settings-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  touch-action: manipulation;
  border-radius: 10px;
  color: #888;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--motion-fast) ease,
              background var(--motion-fast) ease,
              transform var(--motion-fast) var(--ease-native);
}

.settings-btn:hover {
  color: #ccc;
}

.settings-btn:active {
  background: #1a1a1a;
  color: #fff;
}

@media (prefers-reduced-motion: reduce) {
  .settings-btn { transition: none; }
}

.settings-modal { align-items:center; justify-content:center; padding:20px; }
.session-blocker { --modal-layer:20000; }
.modal-card.settings-panel-card { width:min(100%, 440px); max-width:440px; max-height:min(100%, 720px); overflow:auto; padding:20px; }
.settings-modal-head { display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:18px; }
.settings-modal-head h3 { margin:4px 0 0; font-size:24px; }
.settings-modal-head .settings-btn { flex:0 0 auto; }
.settings-modal-actions { margin-top:18px; }
.settings-group { margin:0; padding:16px; border:1px solid var(--border-subtle); border-radius:var(--radius-card); background:rgba(255,255,255,.018); }
.settings-group + .settings-group { margin-top:12px; }
.settings-group h4 { margin:0 0 13px; color:var(--text-tertiary); font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; }
.settings-group .setting-row + .setting-row { margin-top:12px; }
.settings-group .setting-note { margin:7px 0 0; }
.settings-group .setting-note + .setting-row { margin-top:10px; }
.settings-group .wallets-settings-list { margin-top:10px; }
.settings-group .setting-btn-action { margin-top:10px; }

.setting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  color: #ccc;
  min-height:44px;
}

/* Every settings row after the first is spaced from the one above.

   The general sibling combinator (~), not the adjacent one (+). Rows are not
   always directly adjacent - a note, a wallet list, or a button can sit
   between two of them - and with + the spacing silently vanishes the moment
   anything is inserted, leaving two rows touching. That happened twice: once
   to the Add Wallet button, patched over with an inline margin, and again to
   the "Month starts" note.

   ~ matches any later row that has a row before it, so spacing survives
   whatever gets added between. */
.setting-row ~ .setting-row {
  margin-top: 10px;
}

/* A full-width action button that FOLLOWS a row rather than sitting inside
   one - "+ Add Wallet", "Edit Categories". Without this it butts straight up
   against the label above it and reads as part of it. Same ~ combinator and
   same reasoning as the rule above: a list or a note often sits between the
   label and its button. Export/Import are unaffected, since their buttons are
   children of a .setting-row rather than siblings of one. */
.setting-row ~ .setting-btn-action {
  margin-top: 10px;
}

.setting-select {
  background: #111;
  color: #fff;
  border: 1px solid #2a2a2a;
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 13px;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  min-height:44px;
  max-width:56%;
}

.setting-input {
  background: #111;
  color: #fff;
  border: 1px solid #2a2a2a;
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 13px;
  width: 80px;
  text-align: right;
  min-height:44px;
}

.setting-input::placeholder {
  color: #555;
}

.setting-btn-action {
  width: 100%;
  background: #2c2c2c;
  color: #ccc;
  border: none;
  border-radius: 8px;
  padding: 10px;
  font-size: 13px;
  cursor: pointer;
  min-height:44px;
}

.setting-btn-action:active {
  background: #3a3a3a;
}

/* Destructive actions must not look like routine Settings controls. This is
   intentionally the only red control in the panel. */
.setting-btn-action.danger {
  background: #7f1d1d;
  border: 1px solid #b83a3a;
  color: #fff;
  font-weight: 700;
}

.setting-btn-action.danger:active {
  background: #991f1f;
}

:root[data-theme="light"] .setting-btn-action.danger {
  background: #b42323;
  border-color: #951b1b;
  color: #fff;
}

.spend-bar-wrapper {
  margin-top: 10px;
}

/* overflow stays VISIBLE, unusually for a progress track: the budget-limit
   tick below is taller than the bar and has to overhang both edges to be
   readable. Positioning context for that tick. */
.spend-bar-track {
  position: relative;
  width: 100%;
  height: 6px;
  background: #2a2a2a;
  border-radius: 3px;
  overflow: visible;
}

/* Width and colour are both set from calculateRemaining(). The background is
   deliberately absent here - it is assigned in JS from a three-tier
   brightness ramp (dim grey under 50%, lighter to 75%, white past it), so the
   bar intensifies rather than turning red. Both properties transition, so a
   change animates in step with the counting figure above. */
.spend-bar-fill {
  position: relative;
  z-index: 1;
  height: 100%;
  border-radius: 3px;
  width: 0%;
  transition: width var(--motion-emphasis) var(--ease-native),
              background var(--motion-emphasis) ease;
}

/* Optional budget-limit marker. Its `left` is set in JS as a percentage.
   12px tall against a 6px track, pulled up 3px, so it straddles the bar
   evenly and reads as a notch rather than part of the fill. */
.spend-bar-limit {
  position: absolute;
  top: -3px;
  width: 2px;
  height: 12px;
  background: #fff;
  border-radius: 1px;
  opacity: 0.6;
}

.spend-bar-label {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  color: #666;
}

.budget-warning {
  color: #fff;
  font-weight: 600;
  font-size: 12px;
  text-align: center;
  margin-top: 4px;
}

.setting-version {
  text-align: center;
  font-size: 11px;
  color: #555;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid #2a2a2a;
}

/* Toggle switch */
.toggle {
  position: relative;
  display: inline-block;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
}

.toggle input {
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-slider {
  position: absolute;
  inset: 10px 0;
  background: #333;
  border-radius: 24px;
  cursor: pointer;
  transition: background var(--motion-standard) ease;
}

.toggle-slider::before {
  content: "";
  position: absolute;
  width: 18px;
  height: 18px;
  left: 3px;
  bottom: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform var(--motion-standard) var(--ease-native);
}

.toggle input:checked + .toggle-slider {
  background: #e8e8e8;
}

.toggle input:checked + .toggle-slider::before {
  background: #0a0a0a;
  transform: translateX(20px);
}

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

/* =====================
   Header
===================== */
.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

header {
  margin-bottom: 24px;
}

header h1 {
  font-size: clamp(27px, 7.4vw, 32px);
  font-weight: 750;
  margin: 0;
  line-height: 1.05;
  letter-spacing: -0.035em;
  min-width:0;
}

header p {
  margin: 7px 0 0;
  font-size: 14px;
  line-height: 1.4;
  color: var(--text-secondary);
}

#tab-home:not(.hidden) { display:flex; flex-direction:column; }
#tab-home .setup-month { order:0; }
#tab-home .summary { order:1; }
#tab-home #chart-section { order:3; margin:8px 0 22px; }
#tab-home .backup-reminder { order:2; }
#tab-home .recent-section { order:4; }

.setup-month {
  padding:22px;
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-card);
  background:linear-gradient(145deg,#171717,#0d0d0d);
  box-shadow:var(--shadow-card);
  margin-bottom:12px;
}
.setup-month h2 { margin:5px 0 7px; font-size:24px; letter-spacing:-.025em; }
.setup-month p { margin:0 0 18px; color:var(--text-secondary); font-size:13px; line-height:1.5; }
.setup-month button,
.empty-action { min-height:44px; padding:11px 16px; border:0; border-radius:var(--radius-control); background:var(--surface-action); color:var(--text-on-action); font-weight:700; }
:root[data-theme="light"] .setup-month { background:#fff; }

/* =====================
   Summary Cards
===================== */
.summary {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 12px;
}

.available-card { padding:22px 20px 18px; }
.available-card #remaining-money { font-size:clamp(34px,10vw,46px); letter-spacing:-.035em; }

/* The supporting figures, inside the hero card under a hairline. Four equal
   cells on one row; each figure appears on Home exactly once. */
.cycle-summary {
  display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));
  gap:0;
  margin:18px -4px 0;
  padding-top:14px;
  border-top:1px solid var(--border-subtle);
}
.summary-cell { min-width:0; padding:4px; text-align:left; }
.cycle-summary .summary-cell span,
.cycle-summary .summary-cell strong { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cycle-summary .summary-cell span { margin:0; color:var(--text-tertiary); font-size:10px; font-weight:650; text-transform:uppercase; letter-spacing:.08em; }
.cycle-summary .summary-cell strong { margin-top:5px; color:var(--text-primary); font-size:13px; font-weight:650; }
/* Income is the one editable figure, so it is a real button - reset to look
   like its neighbours, with a dotted underline as the only "tap me" hint. */
button.summary-cell { border:0; border-radius:8px; background:none; color:inherit; font:inherit; cursor:pointer; }
button.summary-cell strong { text-decoration:underline dotted var(--border-strong); text-underline-offset:3px; }
button.summary-cell:active { background:var(--surface-field); }
.income-input { width:100%; margin-top:14px; padding:12px; border-radius:var(--radius-control); }

.recent-section { margin-bottom:22px; }
.recent-activity { margin-top:10px; border:1px solid var(--border-subtle); border-radius:var(--radius-card); padding:0 var(--space-card); background:var(--surface-card); box-shadow:var(--shadow-card); }
.recent-row { display:flex; justify-content:space-between; gap:14px; padding:13px 0; }
.recent-row + .recent-row { border-top:1px solid var(--border-subtle); }
.recent-main,
.recent-side { min-width:0; }
.recent-main strong,
.recent-main span,
.recent-side strong,
.recent-side span { display:block; }
.recent-main strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14px; }
.recent-main span,
.recent-side span { margin-top:3px; color:var(--text-tertiary); font-size:11px; }
.recent-side { flex:0 0 auto; text-align:right; }
.recent-side strong { font-size:14px; }
.recent-activity .empty-state-rich { margin:var(--space-card) 0; }
.empty-action { margin-top:14px; font-size:12px; }

.amount-in { color:var(--semantic-in) !important; }
.amount-out { color:var(--text-primary); }
.budget-warning:not(.hidden),
.legend-overspent .legend-amount { color:var(--semantic-out) !important; }
.section-note { margin:-6px 0 12px; color:var(--text-secondary); font-size:12px; line-height:1.45; }

.card {
  background: linear-gradient(145deg, #141414, var(--surface-card));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: var(--space-card);
  box-shadow: var(--shadow-card);
}

.card span {
  display: block;
  font-size: 12px;
  color: var(--text-tertiary);
  margin-bottom: 7px;
}

.card > span {
  text-transform: uppercase;
  font-weight: 650;
  letter-spacing: 0.11em;
  font-size: 11px;
}

.card h2 {
  margin: 0;
  color: var(--text-primary);
  font-size: 23px;
  font-weight: 650;
  letter-spacing: -0.025em;
  line-height: 1.15;
}

/* Emphasize remaining money */
#remaining-money {
  font-size: 28px;
  font-weight: 720;
}

/* =====================
   Section Titles
===================== */
h3 {
  margin: 22px 0 12px;
  font-size: 19px;
  font-weight: 680;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

/* =====================
   Priority Section
===================== */
.bills-heading-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 22px 0 12px;
}

.bills-heading-row h3 {
  margin: 0;
}

.bills-heading-row + .section-note:not(.hidden) {
  margin: -4px 0 12px;
}

#priority-list {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: 10px var(--space-card);
  box-shadow: var(--shadow-card);
}

/* SWIPE TO DELETE - three layers stacked in one wrapper.

   The wrapper clips (overflow: hidden) so the row can slide out of frame
   without widening the page. It also collapses its own max-height to zero on
   delete, which is what closes the gap in the list.

   Underneath sits .swipe-delete-bg, permanently visible but covered. On top
   sits the row itself, opaque and raised, translated left by the gesture -
   which uncovers the background beneath progressively, so "Delete" appears to
   slide in from the right without anything actually moving. */
.swipe-wrapper {
  position: relative;
  overflow: hidden;
}

.swipe-delete-bg {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  background: #2e2e2e;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-right: 20px;
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  border-radius: 10px;
}

/* The sliding layer. background and z-index are what make the trick work:
   without an OPAQUE background the delete label shows through the row, and
   without the raised z-index the row sits behind it entirely. #0f0f0f rather
   than #000 so the row is still distinguishable from the page. */
#priority-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-subtle);
  font-size: 15px;
  /* Same fill as the list around it, so no darker strip shows along the
     row, but still opaque - the swipe-to-delete layer sits underneath. */
  background: var(--surface-card);
  position: relative;
  z-index: 1;
}

#priority-list li:last-child {
  border-bottom: none;
}

/* A normal bill row is horizontal, but the empty message is prose. Keep its
   heading and explanation centred and stacked instead of inheriting the row's
   space-between layout and squeezing the heading into two lines. */
#priority-list li.empty-state-rich {
  display: block;
  width: 100%;
  padding: 22px 16px;
  text-align: center;
}

#priority-list li.empty-state-rich strong {
  white-space: nowrap;
}

/* =====================
   Second Choice Form
===================== */
.second-form {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: var(--space-card);
  box-shadow: var(--shadow-card);
}

.second-form input,
.second-form select {
  width: 100%;
  padding: 12px;
  margin-bottom: 10px;
  background: var(--surface-field);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  color: #fff;
  font-size: 15px;
  -webkit-appearance: none;
  appearance: none;
  min-width:0;
}

.second-form select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23777' stroke-width='1.5' fill='none'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  cursor: pointer;
}

.second-form select option {
  background: #1a1a1a;
  color: #fff;
}

.second-form input::placeholder,
.second-form select:invalid {
  color: #777;
}

.second-form input:focus,
.second-form select:focus {
  outline: none;
  border-color: #3a3a3a;
}

.second-form .input-error {
  border-color: #e8e8e8;
  animation: shake var(--motion-standard) ease;
}

/* Optional date field. type="date" ignores the placeholder attribute and
   renders blank on iOS, so overlay our own label while the field is empty. */
.date-field {
  position: relative;
  margin-bottom: 10px;
}

.second-form .date-field input[type="date"] {
  display: block;
  margin-bottom: 0;
  text-align: left;
  min-height: 45px;
}

/* Covers the whole field, but pointer-events: none lets taps fall through to
   the real input underneath - otherwise this would swallow the tap that opens
   the date picker. */
.date-placeholder {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  padding-left: 13px;
  color: #777;
  font-size: 15px;
  pointer-events: none;
}

/* .is-empty is toggled by wireDateInput() on every input event, since CSS has
   no way to select an empty date field on its own - :placeholder-shown does
   not apply to type="date". */
.second-form .date-field input[type="date"].is-empty + .date-placeholder {
  display: flex;
}

/* Hide the browser's own dd/mm/yyyy text so it can't sit under ours.
   opacity rather than display, because hiding the edit region outright
   collapses the field's height and makes the row jump. */
.second-form .date-field input[type="date"].is-empty::-webkit-datetime-edit {
  opacity: 0;
}

/* The calendar glyph ships black and is invisible on a black background;
   inverting it is the only way to recolour a shadow-DOM icon. */
.second-form input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(0.6);
  cursor: pointer;
}

/* =====================
   Collapsible History
===================== */
.table-toggle {
  width: 100%;
  margin-top: 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: none;
  border: none;
  border-bottom: 1px solid #1f1f1f;
  padding: 8px 0;
  color: #7a7a7a;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  cursor: pointer;
  min-height:44px;
}

.table-toggle:active {
  color: #b0b0b0;
}

.table-toggle .chev {
  flex-shrink: 0;
  transition: transform var(--motion-fast) var(--ease-native);
}

.table-toggle.collapsed .chev {
  transform: rotate(-90deg);
}

.table-toggle + table {
  margin-top: 4px;
}

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

/* =====================
   Action Buttons
===================== */
.actions {
  display: flex;
  gap: 10px;
  margin-top: 6px;
}

.actions button {
  flex: 1;
  padding: 12px;
  border-radius: 12px;
  border: none;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

#add-money {
  background: var(--surface-action);
  color: var(--text-on-action);
}

#take-money {
  background: transparent;
  border: 1px solid #333;
  color: #b0b0b0;
}

.actions button:active {
  transform: scale(0.97);
}

/* =====================
   Second Choice Table
===================== */
table {
  width: 100%;
  margin-top: 16px;
  border-collapse: collapse;
  font-size: 13px;
  table-layout: auto;
}

th, td {
  padding-right: 8px;
}

th:last-child, td:last-child {
  padding-right: 0;
}

thead {
  color: #7a7a7a;
}

th {
  text-align: left;
  font-weight: 500;
  padding-bottom: 8px;
}

th:last-child,
td:last-child {
  text-align: right;
}

tbody tr {
  border-bottom: 1px solid #1f1f1f;
}

td {
  padding: 10px 0;
}

/* =====================
   Utility
===================== */
.hidden {
  display: none !important;
}

#income-card {
  cursor: pointer;
}

#total-income-input {
  background: var(--surface-field);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  font-size: 16px;
}

/* =====================
   Modal
===================== */
/* Sized to the visible viewport so an open keyboard never covers the card.

   --vv-top and --vv-height are written by the keyboard-aware IIFE in app.js
   from the visualViewport API. A plain `inset: 0` would size this to the
   LAYOUT viewport, which on iOS does not shrink when the keyboard appears -
   so the modal would centre behind it. That was the Add Wallet bug: the name
   field sat under the keyboard with no way to scroll to it.

   The fallbacks matter: on a browser without visualViewport the variables are
   never set, and this falls back to a normal full-screen overlay. */
.modal {
  position: fixed;
  left: 0;
  right: 0;
  top: var(--vv-top, 0px);
  height: var(--vv-height, 100%);
  padding: max(16px, env(safe-area-inset-top, 0px)) 16px max(16px, env(safe-area-inset-bottom, 0px));
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--modal-layer, 9999);
  pointer-events: auto;
}

.modal.hidden {
  display: none;
}

/* max-height + overflow-y keep long modals usable: the overspend and transfer
   pickers grow with the number of wallets, and on a short screen with the
   keyboard up they must scroll internally rather than overflow the card. */
.modal-card {
  background: linear-gradient(155deg, var(--surface-raised), #101010 70%);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sheet);
  padding: 20px;
  width: 100%;
  max-width: 360px;
  max-height: 100%;
  overflow-y: auto;
  box-shadow: var(--shadow-sheet);
  pointer-events: auto;
  z-index: calc(var(--modal-layer, 9999) + 1);
  overscroll-behavior:contain;
  overflow-wrap:anywhere;
  scroll-padding-block:20px;
}

/* The category editor holds two full lists, so it needs the height an alert
   does not. Still capped by the keyboard-aware --vv-height above it. */
.modal-card-tall {
  max-width: 400px;
  text-align: left;
}

.modal-hint {
  font-size: 12px;
  color: #777;
  margin-bottom: 14px;
  line-height: 1.45;
}

.category-group {
  margin-bottom: 18px;
}

.category-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #888;
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid #222;
}

/* Rename field. Sized down from .setting-input-wide so a row still fits the
   chip, the name and the delete button on a narrow phone. */
.category-name-input {
  font-size: 13px;
  padding: 6px 8px;
}

.modal-card h3 {
  margin: 0 0 10px;
  font-size: 20px;
  letter-spacing: -0.025em;
}

.modal-card p {
  color: var(--text-secondary);
  font-size: 14px;
  line-height: 1.45;
}

.modal-actions {
  display: flex;
  gap: 10px;
  margin-top: 22px;
  padding-top: 2px;
}

.modal-actions button {
  flex: 1;
  min-height: 44px;
  padding: 12px 14px;
  border-radius: var(--radius-control);
  border: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 650;
  cursor: pointer;
}

.modal-actions .danger {
  background: var(--surface-action);
  color: var(--text-on-action);
}

/* Resetting a cycle is more destructive than routine modal confirmation, so
   it repeats the dedicated red treatment used by the Settings reset control. */
.modal-actions #confirm-reset {
  background: #7f1d1d;
  border: 1px solid #b83a3a;
  color: #fff;
}

.modal-actions #confirm-reset:active {
  background: #991f1f;
}

:root[data-theme="light"] .modal-actions #confirm-reset {
  background: #b42323;
  border-color: #951b1b;
  color: #fff;
}

.modal-actions button:not(.danger) {
  background: var(--surface-field);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
}

/* =====================
   Guided onboarding
===================== */
body.tutorial-active {
  overflow: hidden;
  overscroll-behavior: none;
}

.onboarding-welcome-card {
  max-width: 390px;
}

.modal-actions .primary,
.tutorial-actions .primary {
  background: var(--surface-action);
  color: var(--text-on-action);
  border-color: transparent;
}

.tutorial-overlay {
  padding: 0;
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  align-items: flex-start;
  justify-content: flex-start;
  overflow: hidden;
}

.tutorial-spotlight {
  position: fixed;
  left: var(--spotlight-left, 16px);
  top: var(--spotlight-top, 16px);
  width: var(--spotlight-width, 120px);
  height: var(--spotlight-height, 52px);
  border: 2px solid rgba(255, 255, 255, .86);
  border-radius: var(--spotlight-radius, var(--radius-card));
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .78), 0 0 0 6px rgba(255, 255, 255, .08);
  pointer-events: none;
  z-index: 1;
  transition: left var(--motion-standard) var(--ease-native),
              top var(--motion-standard) var(--ease-native),
              width var(--motion-standard) var(--ease-native),
              height var(--motion-standard) var(--ease-native);
}

.tutorial-card {
  position: fixed;
  left: var(--tutorial-left, 16px);
  top: var(--tutorial-top, 16px);
  width: min(360px, calc(100vw - 32px));
  max-height: min(460px, calc(100vh - 32px));
  z-index: 2;
  animation: tutorial-card-in var(--motion-standard) var(--ease-native);
}

.tutorial-actions {
  display: flex;
  gap: 10px;
  margin-top: 20px;
}

.tutorial-actions button {
  flex: 1;
  min-height: 44px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface-field);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 14px;
  font-weight: 650;
}

.tutorial-actions button:disabled {
  visibility: hidden;
}

.tutorial-skip {
  display: block;
  width: 100%;
  min-height: 44px;
  margin-top: 8px;
  color: var(--text-secondary);
  text-align: center;
}

@keyframes tutorial-card-in {
  from { opacity: 0; transform: translateY(6px) scale(.99); }
}

@media (max-width: 680px), (max-height: 650px) {
  .tutorial-card {
    left: 12px !important;
    right: 12px;
    top: auto !important;
    bottom: max(12px, env(safe-area-inset-bottom, 0px));
    width: auto;
    max-height: min(46vh, 390px);
  }
}

:root[data-theme="light"] .tutorial-spotlight {
  border-color: rgba(255, 255, 255, .98);
  box-shadow: 0 0 0 9999px rgba(24, 24, 22, .62), 0 0 0 6px rgba(255, 255, 255, .2);
}

/* Prevent app from stealing clicks when modal is open */
.modal:not(.hidden) ~ main,
.modal:not(.hidden) ~ .app {
  pointer-events: none;
}

.item-enter {
  opacity: 0;
  transform: translateY(-4px) scale(0.99);
}

.item-enter-active {
  opacity: 1;
  transform: translateY(0) scale(1);
  transition: opacity var(--motion-standard) ease,
              transform var(--motion-standard) var(--ease-native);
}

.empty-state {
  color: #555;
  font-size: 13px;
  text-align: center;
  padding: 20px 0;
  border: none !important;
}

.date-stamp {
  font-size: 11px;
  color: #666;
}

/* An entry dated outside the current cycle. It still counts toward this
   month - see entryDateLabel() - so this is a note, not a warning: the year
   is already spelled out beside it, and the dotted underline is what invites
   the tooltip explaining why the two disagree. */
.date-stamp.is-outside {
  color: #8a8a8a;
  font-style: italic;
  text-decoration: underline dotted #4a4a4a;
  text-underline-offset: 2px;
}

.returned-mark {
  color: #777;
  font-size: 11px;
}

.budget-hint {
  color: #e8e8e8;
  font-size: 11px;
  line-height: 1.4;
  margin: 8px 0 0;
}

/* A <button> since v1.22.0 - tapping it unlocks the list. The reset of
   border/font is what keeps it looking like the badge it replaced, rather
   than a browser-styled button. */
.lock-badge {
  font-size: 11px;
  background: #2a2a2a;
  color: #888;
  padding: 3px 8px;
  border-radius: 6px;
  letter-spacing: 0.3px;
  border: 1px solid #383838;
  font-family: inherit;
  cursor: pointer;
  min-height:44px;
  flex: 0 0 auto;
}

#secret-reset {
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
}

.lock-badge::after {
  /* Says the badge does something, without spelling out "tap to unlock" in a
     space this small. */
  content: " ✕";
  font-size: 9px;
  opacity: 0.7;
}

.lock-badge:active {
  background: #333;
  color: #aaa;
}

/* =====================
   Undo Toast
===================== */
.undo-toast {
  position: fixed;
  bottom: var(--tab-bar-space, 96px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  padding: 12px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  z-index: 9000;
  min-width: 260px;
  max-width: 400px;
  width: min(calc(100% - 32px), 400px);
  flex-wrap: wrap;
  animation: toast-in var(--motion-standard) var(--ease-native);
}

.undo-toast.hidden {
  display: none;
}

.undo-toast span {
  color: var(--text-secondary);
  font-size: 14px;
  flex: 1;
}

#undo-btn {
  background: none;
  border: none;
  color: var(--text-primary);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  padding: 0 8px;
  min-height:44px;
}

/* width: 100% forces the timer onto its own line below the text and button,
   via the flex-wrap on the parent. */
.undo-timer {
  width: 100%;
  height: 3px;
  background: var(--border-subtle);
  border-radius: 3px;
  overflow: hidden;
}

/* The draining countdown. Starts full and is animated to 0% from JS, which
   also supplies the DURATION - the transition is declared here with a timing
   function but no time, so showUndo() can set it to match the undo window.
   Linear on purpose: this is a clock, and any easing would misrepresent how
   much time is actually left. */
.undo-bar {
  height: 100%;
  background: var(--surface-action);
  border-radius: 3px;
  width: 100%;
  transition: width linear;
}

/* Both keyframes have to repeat translateX(-50%) in every step. transform is
   a single property, so animating translateY alone would overwrite the
   centring above and the toast would slide off to the right mid-animation. */
@keyframes toast-in {
  from { opacity: 0; transform: translateX(-50%) translateY(20px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

@keyframes toast-out {
  from { opacity: 1; transform: translateX(-50%) translateY(0); }
  to { opacity: 0; transform: translateX(-50%) translateY(20px); }
}

/* `forwards` holds the final frame, so the toast stays invisible during the
   120ms before hideUndo() actually adds .hidden. Without it the toast would
   snap back to full opacity the instant the animation ended. */
.undo-toast.fading {
  animation: toast-out var(--motion-fast) var(--ease-in-native) forwards;
}

/* =====================
   Wallet Sections
===================== */
.wallet-section + .wallet-section {
  margin-top: 24px;
}

.wallet-card {
  cursor: pointer;
  margin-bottom: 12px;
}

.wallet-card input {
  width: 100%;
  margin-top: 10px;
  padding: 12px;
  border-radius: 12px;
}

.wallet-form {
  margin-top: 12px;
}

.actions [data-role="add-btn"] {
  background: var(--surface-action);
  color: var(--text-on-action);
}

.actions [data-role="take-btn"] {
  background: transparent;
  border: 1px solid #333;
  color: #b0b0b0;
}

.transfer-btn {
  width: 100%;
  margin-top: 8px;
  padding: 10px;
  border-radius: 12px;
  border: 1px dashed #3a3a3a;
  background: none;
  color: #888;
  font-size: 13px;
  cursor: pointer;
  min-height:44px;
}

.transfer-btn:active {
  background: #1a1a1a;
}

.transfer-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}

.transfer-dest-btn {
  width: 100%;
  text-align: left;
  background: #1a1a1a;
  border: 1px solid #2a2a2a;
  border-radius: 10px;
  padding: 12px 14px;
  color: #e8e8e8;
  font-size: 14px;
  cursor: pointer;
}

.transfer-dest-btn:active {
  background: #232323;
}

.transfer-dest-sub {
  display: block;
  font-size: 11px;
  color: #666;
  margin-top: 2px;
}

/* =====================
   Wallets Settings
===================== */
.wallets-header {
  margin-bottom: 2px;
}

.wallets-count {
  color: #666;
  font-size: 12px;
}

.wallets-settings-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
  margin-bottom: 10px;
}

.wallets-empty {
  color: #555;
  font-size: 12px;
  text-align: center;
  padding: 6px 0;
  margin: 0;
}

.wallet-setting-row,
.wallet-settings-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.wallet-settings-row { justify-content:space-between; padding:8px 0; border-bottom:1px solid var(--border-subtle); }
.wallet-settings-row > span { min-width:0; overflow-wrap:anywhere; }

/* Category rows reuse .wallet-setting-row for layout. The chip carries the
   exact colour the chart gives that category, so this list doubles as the
   chart's key - the one place colour is allowed outside the chart itself. */
.category-chip {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
}

.category-name {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  color: #d8d8d8;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Shown instead of a delete button on the fallback category, which cannot be
   removed because uncategorised entries are filed under it. */
.category-locked {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #6a6a6a;
  flex: 0 0 auto;
}

.setting-input-wide {
  flex: 1;
  min-width: 0;
  background: #111;
  color: #fff;
  border: 1px solid #2a2a2a;
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13px;
}

.setting-input-wide::placeholder {
  color: #555;
}

.wallet-delete-btn,
.wallet-settings-row > button {
  flex-shrink: 0;
  background: none;
  border: 1px solid #333;
  color: #8a8a8a;
  border-radius: 8px;
  width: 44px;
  height: 44px;
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
}

.wallet-delete-btn:active {
  background: #1a1a1a;
}

.modal-input {
  width: 100%;
  margin: 4px 0 0;
  padding: 10px 12px;
  font-size: 14px;
}

.modal-error {
  color: #fff;
  font-weight: 600;
  font-size: 12px;
  margin-top: 8px;
  margin-bottom: 0;
}

/* =====================
   Priority Buttons
===================== */

/* Add Priority */
#add-priority {
  background: var(--surface-action);
  color: var(--text-on-action);
}

.copy-last-btn {
  width: 100%;
  margin-top: 8px;
  padding: 10px;
  border-radius: 12px;
  border: 1px dashed #3a3a3a;
  background: none;
  color: #888;
  font-size: 13px;
  cursor: pointer;
  min-height:44px;
}

.copy-last-btn:active {
  background: #1a1a1a;
}

/* Save Priority */
#save-priority {
  background: transparent;
  border: 1px solid #333;
  color: #b0b0b0;
}

/* =====================
   Monthly Chart
===================== */
#chart-section {
  margin-top: 16px;
  margin-bottom: 28px;
}

.chart-container {
  background: linear-gradient(145deg, #141414, var(--surface-card));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: var(--space-card);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  /* Stretch, not centre: everything in here is now full-width rather than a
     centred square. */
  align-items: stretch;
  gap: 14px;
}

/* THE BAR
   A thin horizontal stack: one segment per row in the list below it.

   Fixed height and full width, so unlike the donut it replaced this never has
   to be resized to make the tab fit - it takes the same 16px however many
   categories the month has, and the rows grow instead. */
.chart-bar {
  width: 100%;
  height: 16px;
  display: flex;
  /* The 2px gaps ARE the separation between segments - two adjacent fills with
     no gap read as one shape, and no palette can fix that. */
  gap: 2px;
  border-radius: 8px;
  /* Rounds the outer ends of the stack without rounding every segment: the
     children are square and this clips them. */
  overflow: hidden;
}

.chart-bar-seg {
  /* flex-basis 0 with a grow set from the data - see renderChart. min-width is
     what keeps a 1% category visible instead of vanishing sub-pixel, which is
     the failure the donut could not avoid. */
  flex-basis: 0;
  min-width: 4px;
}

/* Empty state: the track at rest, no segments. */
.chart-bar-empty {
  flex: 1;
  background: #1a1a1a;
}

.chart-empty {
  margin: 0;
  color: #555;
  font-size: 13px;
}

/* The headline figure, above the bar. It used to be printed inside the donut's
   hole, which meant it shrank whenever the ring did. */
.chart-head {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.chart-head-label {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8a8a8a;
}

.chart-head-value {
  font-size: 26px;
  font-weight: 700;
  color: #fff;
}

/* Full width, so the bar above always has a definite box to divide up even if
   a future change makes the section itself narrow. */
.chart-container {
  align-self: stretch;
}

#chart-legend {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.legend-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  color: #ccc;
  gap:12px;
}

.legend-left {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width:0;
  overflow-wrap:anywhere;
}

.legend-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

.legend-amount {
  font-weight: 600;
  color: #fff;
}

/* Marks where spending ends and the money that has NOT been spent begins - the
   greyscale rows. A hairline rather than a heading, since it separates two
   kinds of row rather than starting a new section. */
.legend-divide {
  border-top: 1px solid #232323;
  padding-top: 8px;
  margin-top: 2px;
}

/* =====================
   History View
===================== */
.history-header-left {
  display: flex;
  align-items: center;
  gap: 10px;
}

.history-header-left h1 {
  font-size: 28px;
  font-weight: 700;
  margin: 0;
  letter-spacing: 0.3px;
}

/* Explicit height because fitCanvas() rewrites this canvas's height ATTRIBUTE
   to match the device pixel ratio; without a CSS height the element would take
   that backing-store value as its layout height and grow 2-3x tall. (The
   summary chart no longer has this problem - it is not a canvas any more.) */
#trend-chart {
  width: 100%;
  max-width: 440px;
  height: 190px;
}

.stat-grid {
  width: 100%;
  display: flex;
  gap: 10px;
}

.stat-cell {
  flex: 1;
  background: #0f0f0f;
  border: 1px solid #1e1e1e;
  border-radius: var(--radius-control);
  padding: 10px 6px;
  text-align: center;
  min-width:0;
  overflow-wrap:anywhere;
}

.stat-label {
  display: block;
  font-size: 10px;
  color: #666;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 4px;
}

.stat-value {
  font-size: 14px;
  font-weight: 600;
  color: #e8e8e8;
}

.stat-sub {
  display: block;
  font-size: 10px;
  color: #666;
  margin-top: 2px;
}

.trend-empty {
  width: 100%;
  text-align: center;
  font-size: 12px;
  color: #555;
  margin: 0;
}

.history-row {
  background: linear-gradient(145deg, #141414, var(--surface-card));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: 14px var(--space-card);
  margin-bottom: 12px;
  box-shadow: var(--shadow-card);
}

.history-row-main {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  cursor: pointer;
}

.history-month {
  font-size: 15px;
  font-weight: 600;
}

.history-sub {
  font-size: 11px;
  color: #666;
  margin-top: 3px;
}

.history-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.history-remaining {
  font-size: 14px;
  font-weight: 600;
}

.history-delete {
  background: none;
  border: 1px solid #333;
  color: #8a8a8a;
  border-radius: 8px;
  width: 44px;
  height: 44px;
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
}

.history-delete:active {
  background: #1a1a1a;
}

.history-detail {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-top: 1px solid #1f1f1f;
  margin-top: 10px;
  padding-top: 10px;
}

.history-meta {
  font-size: 11px;
  color: #555;
  margin-top: 2px;
}

.endless-entry-list .legend-left { flex-direction:column; align-items:flex-start; gap:2px; }
.endless-entry-list .legend-amount { white-space:nowrap; }

.delete-all-btn {
  width: 100%;
  margin-top: 4px;
  padding: 12px;
  border-radius: 12px;
  border: none;
  background: var(--surface-action);
  color: var(--text-on-action);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.delete-all-btn:active {
  transform: scale(0.98);
}

.storage-line {
  text-align: center;
  font-size: 11px;
  color: #555;
  margin-top: 14px;
}

/* =====================
   Motion
===================== */
/* Tabs switch often, so the old per-section stagger made every tap wait up to
   half a second. Animate only the incoming panel, as one compositor layer.
   The 6px direction preserves spatial context without turning a tab change
   into a page transition. */
.tab-panel.tab-enter-forward {
  animation: tab-in-forward 140ms var(--ease-native);
}

.tab-panel.tab-enter-back {
  animation: tab-in-back 140ms var(--ease-native);
}

@keyframes tab-in-forward {
  from { opacity: 0.72; transform: translateX(6px); }
}

@keyframes tab-in-back {
  from { opacity: 0.72; transform: translateX(-6px); }
}

/* History is a deeper destination rather than a peer tab, so it gets the
   slightly stronger push/pop language users expect from native navigation. */
.app.view-enter-forward {
  animation: view-in-forward var(--motion-standard) var(--ease-native);
}

.app.view-enter-back {
  animation: view-in-back var(--motion-standard) var(--ease-native);
}

@keyframes view-in-forward {
  from { opacity: 0.55; transform: translateX(12px); }
}

@keyframes view-in-back {
  from { opacity: 0.55; transform: translateX(-12px); }
}

/* Grows out of the button that opened it, hence the top-right origin - a
   default centre origin makes the panel appear to expand from nowhere. */
.settings-modal:not(.hidden) .settings-panel-card { animation: pop-in var(--motion-fast) var(--ease-native); }

.modal:not(.hidden):not(.is-closing) {
  animation: fade-in var(--motion-standard) ease;
}

.modal:not(.hidden):not(.is-closing) .modal-card {
  animation: pop-in var(--motion-standard) var(--ease-native);
}

.modal.is-closing {
  pointer-events: none;
  animation: fade-out var(--motion-fast) var(--ease-in-native) forwards;
}

.modal.is-closing .modal-card {
  animation: pop-out var(--motion-fast) var(--ease-in-native) forwards;
}

.settings-modal.is-closing .settings-panel-card { pointer-events:none; animation:pop-out var(--motion-fast) var(--ease-in-native) forwards; }

@keyframes pop-in {
  from { opacity: 0; transform: translateY(4px) scale(0.98); }
}

@keyframes pop-out {
  to { opacity: 0; transform: translateY(3px) scale(0.985); }
}

@keyframes fade-in {
  from { opacity: 0; }
}

@keyframes fade-out {
  to { opacity: 0; }
}

/* Added and removed by animateMoneyTo() whenever a figure changes, so the
   card flashes its border while the number counts. The class is what draws
   the eye to WHICH card moved when several update at once.

   It peaks at 35% and returns, rather than easing to a held colour, so the
   pulse reads as a blink and leaves no state behind to clean up. */
.card-pulse {
  animation: card-pulse var(--motion-emphasis) ease;
}

@keyframes card-pulse {
  0% { border-color: #232323; }
  35% { border-color: #9a9a9a; }
  100% { border-color: #232323; }
}

/* Every animation in the file is switched off here. Note this only disables
   ANIMATIONS - the count-up in animateMoneyTo() is driven by JS and checks
   the same media query itself, since CSS cannot reach it. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }

  .tab-panel.tab-enter-forward,
  .tab-panel.tab-enter-back,
  .app.view-enter-forward,
  .app.view-enter-back,
  .settings-panel:not(.hidden),
  .modal:not(.hidden),
  .modal:not(.hidden) .modal-card,
  .card-pulse {
    animation: none;
  }
}

/* The overspent legend row has no slice in the donut - an overspend is not a
   share of income - so it is brightened to stay legible as the one legend
   entry with nothing to point at. */
.legend-overspent .legend-amount {
  color: #e8e8e8;
}

/* Projected fill: where the bar lands once every unticked bill is paid.

   Absolutely positioned so it can sit UNDER the real fill and reach further
   right, with the real fill given a z-index to stay on top. A fixed dim grey
   rather than a dimmed version of the live colour - the real bar already
   shifts from dim grey to white by tier, so anything relative would vanish
   at the low end. */
.spend-bar-projected {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 0%;
  background: #4a4a4a;
  border-radius: 3px;
  z-index: 0;
  transition: width var(--motion-emphasis) var(--ease-native);
}

/* Balance now against balance after bills. Wraps rather than truncating on a
   narrow screen - both figures matter, neither is decoration. */
.projection-line {
  margin: 4px 0 0;
  font-size: 11px;
  line-height: 1.5;
  color: #777;
  display: flex;
  flex-wrap: wrap;
  gap: 0 6px;
}

/* The projected half is dimmer than the actual: it has not happened yet. */
.projection-part.is-projected {
  color: #5f5f5f;
}

.projection-sep {
  color: #3a3a3a;
}

/* =========================
   INSIGHTS + ACTIVITY FINDER
========================= */
.section-heading-row, .activity-search-head { display:flex; align-items:flex-end; justify-content:space-between; gap:16px; }
.section-heading-row h3 { margin:2px 0 0; }
.activity-search-head { align-items:center; margin-bottom:14px; }
.activity-search-head h3 { margin:0; }
.section-eyebrow { color:var(--text-tertiary); font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; }

.activity-modal-card { max-width:360px; text-align:left; }
.activity-search-controls { display:grid; grid-template-columns:1fr; gap:10px; }
.activity-search-controls input,
.activity-search-controls select {
  width:100%;
  min-height:45px;
  padding:12px;
  background:var(--surface-field);
  border:1px solid var(--border-strong);
  border-radius:var(--radius-control);
  color:#fff;
  font-family:inherit;
  font-size:15px;
  -webkit-appearance:none;
  appearance:none;
}
.activity-search-controls select {
  padding-right:36px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23777' stroke-width='1.5' fill='none'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 12px center;
}
.activity-search-controls select option { background:#1a1a1a; color:#fff; }
.activity-search-controls input::placeholder { color:#777; }
.activity-search-controls input:focus,
.activity-search-controls select:focus { outline:none; border-color:#3a3a3a; }
.activity-search-controls input[type="date"]::-webkit-calendar-picker-indicator { filter:invert(.6); cursor:pointer; }
.date-filter { display:flex; min-width:0; flex-direction:column; gap:6px; color:#777; font-size:10px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; }
.text-button { width:auto; min-height:44px; padding:6px 8px; border:0; background:transparent; color:var(--text-secondary); font-size:12px; }
.activity-summary { margin:14px 0 0; color:#777; font-size:12px; line-height:1.45; }
.activity-results { margin-top:8px; }
.activity-result { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:4px 14px; padding:12px 0; border-top:1px solid var(--border-subtle); }
.activity-result-main { min-width:0; }
.activity-result-name, .activity-result-amount { font-weight:700; }
.activity-result-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.activity-result-meta, .activity-result-date { color:#777; font-size:11px; }
.activity-result-amount, .activity-result-date { text-align:right; }
.empty-state-rich { padding:22px 16px; border:1px dashed #2d2d2d; border-radius:14px; color:#8d8d8d; text-align:center; }
.empty-state-rich strong { display:block; margin-bottom:5px; color:var(--text-primary); }
.empty-state-rich p { margin:0; font-size:12px; line-height:1.5; }

.app-loading { position:fixed; z-index:10000; inset:0; padding:max(28px,env(safe-area-inset-top)) 18px 24px; background:var(--surface-page); transition:opacity var(--motion-fast) ease; }
.app-loading.is-ready { opacity:0; pointer-events:none; }
.loading-shell { max-width:560px; margin:0 auto; }
.skeleton { display:block; border-radius:10px; background:linear-gradient(90deg,#111 25%,#1a1a1a 50%,#111 75%); background-size:220% 100%; animation:skeleton-shift 900ms linear infinite; }
.skeleton-title { width:42%; height:23px; }
.skeleton-subtitle { width:28%; height:12px; margin-top:10px; }
.loading-cards { display:grid; grid-template-columns:1fr; gap:12px; margin-top:32px; }
.skeleton-card { height:122px; border-radius:18px; }
.skeleton-block { height:150px; margin-top:18px; border-radius:18px; }
@keyframes skeleton-shift { to { background-position:-220% 0; } }
@media (max-width:380px) {
  :root { --space-page:16px; --space-card:14px; }
  .tab-bar { padding-inline:4px; }
  .tab-btn { font-size:9px; letter-spacing:.06em; }
}

@media (max-width:600px) {
  header h1 { font-size:clamp(20px,5.4vw,28px); }
  .header-row { align-items:center; }
  .settings-bar { flex-shrink:0; }
  .second-form input,
  .second-form select,
  .activity-search-controls input,
  .activity-search-controls select,
  .setting-input-wide,
  .setting-input,
  .setting-select,
  .modal-input {
    font-size:16px;
  }

  .update-banner { gap:6px; padding:10px; }
  .modal-card.settings-panel-card { padding:16px; }
  .settings-group { padding:12px; }
  .setting-row { gap:8px; }
  .backup-row { align-items:flex-start; }
  .backup-row > :last-child { max-width:55%; text-align:right; }
  .history-row-main { flex-wrap:wrap; }
  .history-right { margin-left:auto; }
}

/* Tablet: use the extra width for comparisons while keeping forms at a
   comfortable reading measure. Phone remains the primary one-column layout. */
@media (min-width:768px) {
  :root { --space-page:28px; --space-card:18px; }

  .app,
  .tab-bar { max-width:760px; }

  .loading-shell { max-width:704px; }
  .loading-cards { grid-template-columns:repeat(2,minmax(0,1fr)); }
  .tab-panel:not(#tab-home) > section,
  #tab-wallets .wallet-section,
  #tab-wallets .wallets-empty { max-width:640px; margin-inline:auto; }
  .settings-panel { width:320px; }
}

@media (min-width:900px) {
  .modal-card.settings-panel-card {
    display:flex;
    flex-direction:column;
    width:min(100%, 1080px);
    max-width:1080px;
    max-height:100%;
    overflow:hidden;
  }
  .settings-modal-head,
  .settings-modal-actions,
  .settings-panel-card > .setting-version { flex-shrink:0; }
  .settings-groups {
    display:grid;
    grid-template-columns:repeat(2,minmax(0,1fr));
    align-items:start;
    gap:12px;
    min-height:0;
    overflow:auto;
    overscroll-behavior:contain;
    padding:2px;
  }
  .settings-group + .settings-group { margin-top:0; }
}

/* Laptop layouts use horizontal space; phone and tablet retain their flow. */
@media (min-width:1100px) {
  .app { max-width:1280px; }
  .tab-bar { max-width:900px; }
  .app { padding-inline:36px; }
  .loading-shell { max-width:1208px; }
  .tab-panel:not(#tab-home) > section,
  #tab-wallets .wallet-section,
  #tab-wallets .wallets-empty { max-width:700px; }
  #tab-home:not(.hidden) { display:grid; grid-template-columns:minmax(0,1.25fr) minmax(0,1fr); gap:20px 24px; align-items:start; }
  #tab-home .setup-month { grid-column:1 / -1; margin:0; }
  #tab-home .summary { grid-column:1 / -1; grid-row:2; margin:0; }
  #tab-home .backup-reminder { grid-column:1 / -1; grid-row:3; margin:0; }
  #tab-home #chart-section { grid-column:1; grid-row:4; margin:0; min-width:0; }
  #tab-home .recent-section { grid-column:2; grid-row:4; margin:0; }
  .settings-groups { grid-template-columns:repeat(3,minmax(0,1fr)); }
  .settings-group[aria-labelledby="settings-recovery-title"] { grid-column:span 2; }
}

@media (hover:hover) and (pointer:fine) {
  .tab-btn:hover { color:var(--text-primary); }
  .tab-btn.is-active:hover { color:var(--text-primary); }
  .modal-actions button:not(.danger):hover,
  .transfer-dest-btn:hover { background:var(--border-subtle); }
}

/* =====================
   INTERACTION MOTION PASS
===================== */
.pull-indicator {
  transform: translateY(calc((1 - var(--pull-progress, 0)) * -8px));
  transition: opacity var(--motion-fast) ease, transform var(--motion-fast) var(--ease-native);
}

.pull-spinner { transform-origin:center; }
.pull-spinner.is-pulling {
  animation:none;
  transform:rotate(calc(var(--pull-progress, 0) * 180deg)) scale(calc(.8 + var(--pull-progress, 0) * .2));
}

.tab-btn { position:relative; z-index:1; }
.tab-motion-indicator {
  position:absolute;
  z-index:2;
  bottom:calc(4px + env(safe-area-inset-bottom, 0px));
  left:0;
  height:2px;
  border-radius:999px;
  background:var(--text-primary);
  pointer-events:none;
  transition:transform var(--motion-emphasis) var(--ease-spring), width var(--motion-fast) var(--ease-native);
}

.card {
  transition:transform var(--motion-standard) var(--ease-native), border-color var(--motion-standard) ease, box-shadow var(--motion-standard) ease;
}

.toggle-slider { transition:background var(--motion-standard) ease, box-shadow var(--motion-standard) ease; }
.toggle-slider::before { transition:transform var(--motion-emphasis) var(--ease-spring), background var(--motion-standard) ease; }
.toggle input:checked + .toggle-slider { box-shadow:0 0 0 3px rgba(232,232,232,.08); }

.empty-state-rich { animation:empty-reveal var(--motion-standard) var(--ease-native); }
.undo-toast.toast-pop { animation:toast-pop var(--motion-emphasis) var(--ease-spring); }
.wallet-card.card-pulse { animation:wallet-charge var(--motion-emphasis) var(--ease-native), card-pulse var(--motion-emphasis) ease; }

.chart-segment-enter {
  transform:scaleX(0);
  transform-origin:left center;
  animation:chart-grow var(--motion-emphasis) var(--ease-native) var(--chart-delay, 0ms) forwards;
}
.chart-legend-enter {
  opacity:0;
  transform:translateY(4px);
  animation:chart-legend-in var(--motion-standard) var(--ease-native) var(--chart-delay, 0ms) forwards;
}

.settings-modal:not(.hidden) .setting-row,
.settings-modal:not(.hidden) .setting-btn-action,
.settings-modal:not(.hidden) .setting-note {
  animation:setting-row-in var(--motion-standard) var(--ease-native) both;
}
.settings-modal:not(.hidden) .setting-row:nth-of-type(2),
.settings-modal:not(.hidden) .setting-btn-action { animation-delay:24ms; }
.settings-modal:not(.hidden) .setting-row:nth-of-type(3) { animation-delay:48ms; }
.settings-modal:not(.hidden) .setting-row:nth-of-type(4) { animation-delay:72ms; }

.surface-enter { animation:surface-enter var(--motion-standard) var(--ease-native) var(--surface-delay, 0ms) both; }
.form-success { animation:form-success 380ms var(--ease-native); }
.action-success { animation:action-success 300ms var(--ease-spring); }

@keyframes empty-reveal { from { opacity:0; transform:translateY(5px); } }
@keyframes toast-pop {
  0% { transform:translateX(-50%) translateY(10px) scale(.96); }
  65% { transform:translateX(-50%) translateY(-1px) scale(1.015); }
  100% { transform:translateX(-50%) translateY(0) scale(1); }
}
@keyframes wallet-charge {
  0% { transform:translateY(0) scale(1); }
  45% { transform:translateY(-2px) scale(1.008); }
  100% { transform:translateY(0) scale(1); }
}
@keyframes chart-grow { to { transform:scaleX(1); } }
@keyframes chart-legend-in { to { opacity:1; transform:translateY(0); } }
@keyframes setting-row-in { from { opacity:0; transform:translateY(-3px); } }
@keyframes surface-enter { from { opacity:0; transform:translateY(8px) scale(.988); } }
@keyframes form-success {
  0%,100% { transform:translateY(0); }
  45% { transform:translateY(-2px); box-shadow:0 0 0 1px rgba(255,255,255,.16), 0 10px 26px rgba(0,0,0,.2); }
}
@keyframes action-success {
  0% { transform:scale(1); }
  55% { transform:scale(.94); filter:brightness(1.12); }
  100% { transform:scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .surface-enter,
  .form-success,
  .action-success,
  .toast-pop,
  .chart-segment-enter,
  .chart-legend-enter,
  .empty-state-rich,
  .settings-panel:not(.hidden) .setting-row,
  .settings-panel:not(.hidden) .setting-btn-action,
  .settings-panel:not(.hidden) .setting-note,
  .wallet-card.card-pulse { animation:none; }
  .tab-motion-indicator { transition:none; }
  .chart-segment-enter { transform:none; }
  .chart-legend-enter { opacity:1; transform:none; }
  .settings-modal:not(.hidden) .setting-row,
  .settings-modal:not(.hidden) .setting-btn-action,
  .settings-modal:not(.hidden) .setting-note { animation:none; }
  .tutorial-card { animation:none; }
  .tutorial-spotlight { transition:none; }
}

/* Opening row of a history - money brought in from last month. Italic and
   muted so it reads as a statement about the list rather than an entry in
   it, which is exactly what it is: nothing here can be deleted. */
.carried-row td {
  color: #6f6f6f;
  font-style: italic;
}

/* Note under a setting explaining when a change takes effect. Sits outside
   .setting-row so it spans the full panel width rather than being squeezed
   into the label column. */
.setting-note {
  margin: 6px 0 0;
  font-size: 11px;
  line-height: 1.4;
  color: #777;
}

/* The Cancel that appears while editing. Styled like every other secondary
   action - transparent with a hairline border - rather than inheriting the
   browser's default button, which renders as a grey slab and reads as the
   most prominent thing in the form. */
/* Scoped through .actions deliberately. A bare [data-edit="cancel"] loses to
   `.actions button` on specificity (0,1,0 against 0,1,1), so `border: none`
   would win and this would render as the browser's default grey slab - the
   most prominent thing in the form, for the least important action in it. */
.actions button[data-edit="cancel"] {
  background: transparent;
  border: 1px solid #333;
  color: #ccc;
}

/* A row that can be opened for editing. Only a cursor hint on desktop - no
   badge or icon, because every row in these tables is editable and marking
   them all would be noise. Transfer halves and carried-forward lines never
   get this class, so they stay visibly inert. */
.row-editable {
  cursor: pointer;
}

/* The form an entry is being edited in. A left edge is enough to say "this is
   no longer a blank form" without moving anything on the page. */
.second-form.is-editing,
.wallet-form.is-editing {
  border-left: 2px solid #6f6f6f;
  border-radius: 0 14px 14px 0;
}

.date-stamp, .returned-mark, .setting-note, .setting-version,
.storage-line, .modal-hint, .category-locked, .history-meta,
.stat-label, .stat-sub, .transfer-dest-sub, .activity-result-meta,
.activity-result-date, .activity-summary, .date-filter,
.carried-row td, .wallets-empty, .wallets-empty-note,
.chart-empty, .trend-empty, .empty-state, .projection-part.is-projected {
  color:var(--text-tertiary);
}
.wallets-empty-note { color:var(--text-tertiary) !important; }
.wallets-empty p, .category-name, .budget-hint, .budget-warning,
.legend-overspent .legend-amount { color:var(--text-primary); }
.setting-version, .category-group-head { border-color:var(--border-subtle); }
.setting-input::placeholder, .setting-input-wide::placeholder,
.second-form input::placeholder, .activity-search-controls input::placeholder { color:var(--text-tertiary); }
.wallet-delete-btn, .wallet-settings-row > button, .copy-last-btn,
#wallets-empty-add, .actions button[data-edit="cancel"] {
  color:var(--text-secondary);
  border-color:var(--border-strong);
}
.wallet-delete-btn:active, .wallet-settings-row > button:active,
.copy-last-btn:active { background:var(--surface-field); }
.setting-btn-action:disabled, .history-more:disabled { opacity:.55; cursor:default; transform:none; }
.history-page-control { border:0; text-align:center; }
.history-page-control > td { padding:14px 0 0; text-align:center; }
.history-page-count { margin:10px 0 4px; color:var(--text-tertiary); font-size:12px; }
.history-more { width:100%; margin-top:4px; border:1px solid var(--border-strong); border-radius:var(--radius-control); background:var(--surface-field); color:var(--text-primary); font-weight:600; }
.history-more:active { background:var(--border-subtle); }
.sr-only { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }

/* =====================
   Hero meta line
===================== */
/* "31% spent" on the left, the balance after bills on the right. Spans, so
   the generic `.card span` block styling is undone here. */
.spend-bar-meta { display:flex; flex-wrap:wrap; justify-content:space-between; gap:2px 12px; margin:6px 0 0; }
.spend-bar-meta span { display:inline; margin:0; font-size:11px; }
.spend-bar-meta .projection-line { display:inline; margin:0; color:var(--text-tertiary); }
.spend-bar-meta .spend-bar-label { margin:0; }

/* =====================
   Phone dialogs as bottom sheets
===================== */
/* Below 600px every dialog rises from the bottom edge, full width, with a
   grabber, and can be swiped down to close (attachSwipeDismiss in app.js). The walkthrough keeps its
   own floating card. */
@media (max-width:600px) {
  .modal:not(.tutorial-overlay) {
    align-items:flex-end;
    padding:max(24px, env(safe-area-inset-top, 0px)) 0 0;
  }
  .modal:not(.tutorial-overlay) > .modal-card {
    width:100%;
    max-width:none;
    border-bottom:0;
    border-radius:var(--radius-sheet) var(--radius-sheet) 0 0;
    padding-bottom:calc(20px + env(safe-area-inset-bottom, 0px));
  }
  .modal:not(.tutorial-overlay) > .modal-card::before {
    content:"";
    display:block;
    width:36px;
    height:5px;
    margin:-8px auto 14px;
    border-radius:3px;
    background:var(--border-strong);
  }
  .modal.settings-modal > .modal-card.settings-panel-card { max-height:100%; }
  .modal:not(.hidden):not(.is-closing):not(.tutorial-overlay) > .modal-card,
  .settings-modal:not(.hidden):not(.is-closing) > .settings-panel-card {
    animation:sheet-up 260ms var(--ease-native);
  }
  .modal.is-closing:not(.tutorial-overlay) > .modal-card,
  .settings-modal.is-closing > .settings-panel-card {
    animation:sheet-down var(--motion-fast) var(--ease-in-native) forwards;
  }
}
@keyframes sheet-up { from { transform:translateY(100%); } }
@keyframes sheet-down { to { transform:translateY(100%); } }


/* Four figures do not fit one row on a phone without truncating, so they
   sit two by two there. */
@media (max-width:520px) {
  .cycle-summary { grid-template-columns:repeat(2,minmax(0,1fr)); row-gap:10px; }
  .cycle-summary .summary-cell strong { font-size:15px; }
}

/* Spend bar brightness steps (setSpendLevel in app.js). Dark brightens toward
   white; light darkens toward black, so the last step never disappears into
   the pale track. */
.spend-bar-fill[data-level="low"] { background:#6f6f6f; }
.spend-bar-fill[data-level="mid"] { background:#b0b0b0; }
.spend-bar-fill[data-level="high"] { background:#ffffff; }
.spend-bar-label.is-high { color:#e8e8e8; }
:root[data-theme="light"] .spend-bar-fill[data-level="low"] { background:#a3a39e; }
:root[data-theme="light"] .spend-bar-fill[data-level="mid"] { background:#62625e; }
:root[data-theme="light"] .spend-bar-fill[data-level="high"] { background:#1d1d1c; }
:root[data-theme="light"] .spend-bar-label.is-high { color:#1d1d1c; }

/* =====================
   Bill rows
===================== */
.bills-progress { margin:-6px 0 12px; color:var(--text-secondary); font-size:12px; }
.bills-heading-row + .section-note.hidden + .bills-progress { margin-top:-4px; }

#priority-list li:not(.empty-state-rich) { min-height:58px; padding:8px 0; gap:12px; }
.bill-row { display:flex; align-items:center; gap:12px; min-width:0; flex:1; cursor:pointer; }
/* A 44px tap area around the 26px tick, pulled back in with negative margins
   so the row does not move. */
.bill-tick { display:grid; place-items:center; flex:0 0 auto; width:44px; height:44px; margin:-9px; cursor:pointer; }
.bill-text { display:flex; flex-direction:column; gap:2px; min-width:0; }
.bill-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:15px; font-weight:600; color:var(--text-primary); }
.bill-meta { color:var(--text-tertiary); font-size:12px; }
.bill-amount { flex:0 0 auto; font-size:15px; font-weight:650; color:var(--text-primary); }

/* A round tick in the app's own monochrome, replacing the browser's square
   checkbox. The tick is two borders of a rotated box, so it needs no icon. */
.bill-check {
  -webkit-appearance:none;
  appearance:none;
  display:grid;
  place-content:center;
  flex:0 0 auto;
  width:26px;
  height:26px;
  margin:0;
  border:1.5px solid var(--border-strong);
  border-radius:50%;
  background:transparent;
  cursor:pointer;
  transition:background-color var(--motion-fast) ease, border-color var(--motion-fast) ease, transform var(--motion-fast) var(--ease-native);
}
.bill-check::after {
  content:"";
  width:11px;
  height:6px;
  border-left:2px solid var(--text-on-action);
  border-bottom:2px solid var(--text-on-action);
  transform:translateY(-1px) rotate(-45deg) scale(0);
  transition:transform var(--motion-emphasis) var(--ease-spring);
}
.bill-check:checked { background:var(--surface-action); border-color:var(--surface-action); }
.bill-check:checked::after { transform:translateY(-1px) rotate(-45deg) scale(1); }
.bill-check:active { transform:scale(.9); }

/* Paid: the row steps back - dimmed and struck through - so what is still
   owed is what stands out. */
.bill-name,
.bill-amount { transition:color var(--motion-standard) ease; }
#priority-list li.is-paid .bill-name,
#priority-list li.is-paid .bill-amount {
  color:var(--text-tertiary);
  text-decoration:line-through;
  text-decoration-color:var(--border-strong);
}
#priority-list li.is-paid .bill-amount { font-weight:600; }

@media (prefers-reduced-motion: reduce) {
  .bill-check,
  .bill-check::after { transition:none; }
}
/* The light theme gives every input a field fill; the tick is not a field. */
:root[data-theme="light"] .bill-check { background:transparent; border-color:#c9c9c3; }
:root[data-theme="light"] .bill-check:checked { background:var(--surface-action); border-color:var(--surface-action); }

/* =====================
   Day-grouped histories
===================== */
/* Spending and each wallet's history read like Recent Activity: one card,
   rows grouped under a heading per day, the name with its category beneath
   and the amount on the right. Still a table underneath - every row, swipe
   and edit handler is unchanged - only laid out as a list. */
table.day-list {
  display: block;
  margin-top: 8px;
  padding: 0 var(--space-card) 4px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  background: var(--surface-card);
  box-shadow: var(--shadow-card);
}
table.day-list thead { display: none; }
table.day-list tbody { display: block; }
table.day-list tr { border-bottom: 0; }

table.day-list .day-row th {
  display: block;
  padding: 16px 0 4px;
  color: var(--text-tertiary);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .12em;
  text-align: left;
  text-transform: uppercase;
}

table.day-list tbody tr:not(.day-row):not(.history-page-control):not(.empty-row) {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name amount" "meta amount";
  align-items: center;
  column-gap: 14px;
  padding: 11px 0;
  /* Opaque, so the row still covers what is behind it while swiping. */
  background: var(--surface-card);
}
table.day-list tbody tr:not(.day-row) + tr:not(.day-row):not(.history-page-control) {
  border-top: 1px solid var(--border-subtle);
}
table.day-list td { padding: 0; }
table.day-list .cell-name { grid-area: name; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 600; }
table.day-list .cell-meta { grid-area: meta; margin-top: 3px; color: var(--text-tertiary); font-size: 11px; }
table.day-list .cell-meta:empty { display: none; }
table.day-list .cell-amount { grid-area: amount; font-size: 14px; font-weight: 650; text-align: right; white-space: nowrap; }
table.day-list .date-stamp { display: none; }
table.day-list .carried-row .cell-name { font-style: italic; font-weight: 500; }

/* Empty state and "Show more" keep their own block layout inside the card. */
table.day-list tbody tr.empty-row { display: block; }
table.day-list td[colspan] { display: block; padding: 14px 0; }
table.day-list .empty-state-rich { border: 0; }
table.day-list tr.history-page-control,
table.day-list tr.history-page-control > td { display: block; }

/* Backup reminder - the same card language as "Set up this month". */
.backup-reminder {
  padding: 20px;
  margin-bottom: 12px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  background: linear-gradient(145deg, #171717, #0d0d0d);
  box-shadow: var(--shadow-card);
}
.backup-reminder h2 { margin: 5px 0 7px; font-size: 20px; letter-spacing: -.02em; }
.backup-reminder p { margin: 0 0 16px; color: var(--text-secondary); font-size: 13px; line-height: 1.5; }
.backup-reminder-actions { display: flex; gap: 10px; }
.backup-reminder-actions button {
  flex: 1;
  min-height: 44px;
  padding: 11px 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface-field);
  color: var(--text-primary);
  font-weight: 650;
}
.backup-reminder-actions .primary { border-color: transparent; background: var(--surface-action); color: var(--text-on-action); }
:root[data-theme="light"] .backup-reminder { background: #fff; }

/* Under the income box while editing: what the total adds on top. */
.income-edit-note { margin:8px 0 0; color:var(--text-secondary); font-size:12px; line-height:1.45; }

/* Category buttons - replace the category dropdown in the Bills and Spending
   forms. Three to a row; the hidden select keeps the value. Each button's dot
   and selected tint use the colour the chart gives that category. */
.second-form select.category-select { display:none; }
.category-picks {
  display:grid;
  grid-template-columns:repeat(3, minmax(0, 1fr));
  gap:8px;
  margin-bottom:10px;
}
.category-pick {
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:4px;
  min-width:0;
  min-height:52px;
  padding:6px 4px;
  background:var(--surface-field);
  border:1px solid var(--border-strong);
  border-radius:var(--radius-control);
  color:#b0b0b0;
  font-size:12px;
  font-weight:600;
  cursor:pointer;
  transition:background var(--motion-fast, .15s) ease, border-color var(--motion-fast, .15s) ease, color var(--motion-fast, .15s) ease;
}
.category-pick-dot {
  flex:0 0 auto;
  width:7px;
  height:7px;
  border-radius:50%;
  background:var(--pick-color);
}
.category-pick-name {
  min-width:0;
  line-height:1.2;
  text-align:center;
  overflow-wrap:break-word;
}
.category-pick.is-selected {
  border-color:var(--pick-color);
  background:color-mix(in srgb, var(--pick-color) 22%, var(--surface-field));
  color:#fff;
}
.category-pick:focus-visible { outline:2px solid var(--focus-ring); outline-offset:2px; }
.category-picks.input-error .category-pick { border-color:#e8e8e8; }
.category-picks.input-error { animation:shake var(--motion-standard) ease; }
:root[data-theme="light"] .category-pick { background:#fff; border-color:#d2d2cd; color:#4a4a46; }
:root[data-theme="light"] .category-pick.is-selected {
  border-color:var(--pick-color);
  background:color-mix(in srgb, var(--pick-color) 16%, #fff);
  color:#1a1a1a;
}
:root[data-theme="light"] .category-picks.input-error .category-pick { border-color:#1a1a1a; }

/* Set by ui-helpers.js while a dialog or sheet is open: the page behind it is
   pinned so it cannot scroll (iOS ignores overflow:hidden for touch). */
body.scroll-locked {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}
