/*
 * Application layout.
 *
 * `tokens.css` owns colour, type, radius, spacing, and the `.money` and
 * `.sr-only` utilities; this file owns arrangement. There are no literal hex
 * values below — if you need a colour that is not a token, the fix is a token,
 * not a literal, or the light/dark pairing drifts.
 *
 * The non-negotiables from the design brief that this file has to honour:
 * money is right-aligned and never truncated (`.money`, from tokens), pending
 * is visually *and* textually distinct, every auto-assigned category shows a
 * correction affordance, tap targets are 44px, and nothing here uses
 * `--c-danger` — spending is not an error state.
 */

.app {
  max-width: 42rem;
  margin: 0 auto;
  /* `env(safe-area-inset-*)` keeps content clear of the notch and the home
   * indicator on iOS, where the app runs full-bleed. Zero everywhere else. */
  padding: calc(var(--space-4) + env(safe-area-inset-top)) var(--space-4)
    calc(var(--space-6) + env(safe-area-inset-bottom));
}

/* ---- Pull to refresh ------------------------------------------------------
 *
 * The webview's own overscroll is suppressed in `tokens.css`, so this is what
 * the user sees instead while their finger is down. It is `fixed` and starts
 * above the top of the screen: the whole element is offscreen at rest and rides
 * down on the transform the gesture writes, which means no layout ever moves
 * and there is nothing to reflow at sixty frames a second.
 *
 * It exists only for the duration of the drag. Once the finger lifts, the sync
 * itself is reported by the status chip a few millimetres below — see
 * `PullIndicator`. */

.pull {
  position: fixed;
  top: calc(env(safe-area-inset-top) - 2.5rem);
  left: 0;
  right: 0;
  z-index: 5;
  display: flex;
  justify-content: center;
  /* The finger owns this element's position, so it must not also be animating
   * towards somewhere else. */
  transition: none;
  pointer-events: none;
}

.pull__label {
  padding: var(--space-2) var(--space-4);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-sm);
  font-size: var(--text-sm);
  color: var(--c-text-muted);
}

/* Armed. The wording changes too — the colour is the quick read and the
 * sentence is the one that survives not being able to see the difference. */
.pull--armed .pull__label {
  background: var(--c-accent-soft);
  border-color: var(--c-accent);
  color: var(--c-text);
}

/* ---- Header and status --------------------------------------------------- */

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.chip {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* The chip gives way, never the menu button. A long status string ("Catching
   * up on your transactions") must wrap or clip inside its own pill rather than
   * push the only control in the header off the edge of a 320px screen. */
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-sm);
}

/* ---- The profile menu -----------------------------------------------------
 *
 * Everything about the install rather than about the money: signing out,
 * deleting the account, and the local-only equivalents. It is a menu because
 * these used to be flat buttons in the header, which put the one irreversible
 * action in the app permanently one tap from the thumb and crowded a row that
 * has to work at 320px.
 *
 * Not a `<details>`. That would be less code and would not close on Escape or on
 * a tap outside, which are the two things people expect of a menu. See
 * `ProfileMenu`, which wires both. */

.menu {
  position: relative;
  /* Never squeezed by the chip beside it: this is the only control in the
   * header and it has to stay a 44px target however long the status is. */
  flex: none;
}

.menu__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-full);
  background: var(--c-surface);
  color: var(--c-text-muted);
  box-shadow: var(--shadow-sm);
  font-family: inherit;
  transition: transform var(--dur-fast) var(--ease-spring);
}

.menu__trigger:active {
  transform: scale(0.94);
}

.menu__trigger[aria-expanded="true"] {
  border-color: var(--c-border-strong);
  color: var(--c-text);
}

.menu__glyph {
  font-size: var(--text-lg);
  /* The midline dots sit low in most faces; this puts them on the button's own
   * centre rather than a third of the way down it. */
  line-height: 0.6;
}

/* Catches the tap that closes the menu. A button rather than a div so it is a
 * real target with real semantics, and `aria-hidden` because a screen reader
 * user closes the menu with Escape and does not want a nameless control in the
 * tab order. */
.menu__backdrop {
  position: fixed;
  inset: 0;
  z-index: 20;
  border: none;
  padding: 0;
  background: transparent;
  cursor: default;
}

.menu__panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  /* Anchored to the right edge, which is where the trigger is. Anchoring left
   * would hang the panel off the side of the screen. */
  right: 0;
  z-index: 21;
  display: flex;
  flex-direction: column;
  min-width: 15rem;
  /* Never wider than the viewport, whatever the copy does. `--space-4` twice is
   * the app's own gutter. */
  max-width: calc(100vw - var(--space-6));
  padding: var(--space-2);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

/* What kind of install this is, in one line. Not decoration: "signed in" and
 * "on this device only" are genuinely different products, and this is the one
 * place the app says which one somebody is using. */
.menu__note {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  color: var(--c-text-muted);
  font-size: var(--text-xs);
}

.menu__item {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--c-text);
  font-family: inherit;
  font-size: var(--text-base);
  text-align: left;
  cursor: pointer;
}

.menu__item:hover,
.menu__item:focus-visible {
  background: var(--c-surface-sunken);
}

.menu__item:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: -2px;
}

/* The one place in the app `--c-danger` appears, and the reason it exists.
 * Spending is never an error state; deleting an account is genuinely
 * destructive, and the colour is reserved for exactly that. */
.menu__item--destructive {
  color: var(--c-danger);
}

@media (prefers-reduced-motion: no-preference) {
  .menu__panel {
    animation: menu-in var(--dur-fast) var(--ease-out);
  }

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

.chip__label {
  font-size: var(--text-sm);
  color: var(--c-text-muted);
  /* Clipped rather than wrapped. The chip is a fixed-height pill beside a
   * fixed-size button, and a status string that wrapped to two lines would grow
   * the header on exactly the screens with least room for it. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The bunny lives here and nowhere near a number. Fixed size so a pose change
 * cannot reflow the row it sits in. */
.bunny-host {
  width: 2rem;
  height: 2rem;
  flex: none;
  line-height: 0;
}

.bunny-host svg {
  width: 100%;
  height: 100%;
}

.empty .bunny-host,
.signin .bunny-host {
  width: 6rem;
  height: 6rem;
  margin-inline: auto;
}

/* ---- Buttons ------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--space-5);
  min-height: var(--touch-target);
  border: none;
  background: var(--c-accent);
  color: var(--c-accent-text);
  font-size: var(--text-base);
  font-weight: 600;
  transition: transform var(--dur-fast) var(--ease-spring);
}

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

.button[disabled] {
  opacity: 0.6;
  cursor: default;
}

.button--quiet {
  background: transparent;
  color: var(--c-text-muted);
  font-weight: 500;
  padding-inline: var(--space-3);
}

/* ---- Sign in ------------------------------------------------------------- */

.signin {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  text-align: center;
  padding-top: var(--space-6);
}

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

.signin__subtitle {
  margin: 0 0 var(--space-4);
  color: var(--c-text-muted);
}

.signin__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  text-align: start;
}

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

.field__label {
  font-size: var(--text-sm);
  color: var(--c-text-muted);
}

.field__input {
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  background: var(--c-surface);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--radius-md);
  color: var(--c-text);
  /* 16px minimum, or iOS Safari zooms the viewport on focus and never zooms
   * back out. This is the whole reason the base size is not smaller. */
  font-size: var(--text-base);
  font-family: inherit;
}

/* The one-time code is a number the user is copying digit by digit, so it gets
 * the tabular face and room to breathe. */
.field__input--code {
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.4em;
  text-align: center;
}

/* ---- Notices ------------------------------------------------------------- */

.notice {
  margin: var(--space-4) 0 0;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--c-surface-sunken);
  color: var(--c-text-muted);
  font-size: var(--text-sm);
}

/* Rose, not `--c-danger`. A failed sign-in is a typo, not a catastrophe, and
 * the alarm colour is reserved for destructive confirmations. */
.notice--error {
  background: var(--c-rose-soft);
  color: var(--c-text);
}

.banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--c-butter-soft);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
}

/* ---- Empty state --------------------------------------------------------- */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
  text-align: center;
}

.empty__title {
  margin: 0;
  font-size: var(--text-lg);
}

.empty__body {
  margin: 0 0 var(--space-3);
  max-width: 28rem;
  color: var(--c-text-muted);
  font-size: var(--text-sm);
}

/* ---- Ledger -------------------------------------------------------------- */

.ledger__day {
  position: sticky;
  top: 0;
  margin: var(--space-5) 0 var(--space-2);
  padding: var(--space-2) 0;
  background: var(--c-bg);
  color: var(--c-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
}

.ledger__list {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.tx {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: var(--space-3) var(--space-4);
}

.tx + .tx {
  border-top: 1px solid var(--c-border);
}

.tx__main {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  /* `min-width: 0` lets the merchant name shrink so the amount never has to.
   * Truncating a name is a cosmetic loss; truncating an amount is a lie. */
  min-width: 0;
  flex: 1;
}

.tx__merchant {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tx__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* ---- Tags ----------------------------------------------------------------
 * Pending and "needs a category" are stated in words, not merely in colour: a
 * colour-only distinction is invisible to a good share of users, and a pending
 * charge read as settled is the most common way a spend tracker lies. */

.tag {
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--c-surface-sunken);
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
}

.tag--pending {
  background: var(--c-accent-soft);
  color: var(--c-text);
}

/* The review queue's affordance. Mint rather than a warning colour — an
 * unlabelled transaction is an invitation, not a problem the user caused. */
.tag--review {
  background: var(--c-mint-soft);
  color: var(--c-text);
}

/* An automatically assigned category. The dotted underline is the "tap to
 * correct" affordance every non-user-set category is required to carry. */
.tag--auto {
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}

/* ---- Navigation ----------------------------------------------------------
 * Three tabs, because there are three things a person does here. Sticky to the
 * top so the review count stays visible while scrolling a long ledger — that
 * number is what makes somebody go and clear it. */

.nav {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  gap: var(--space-1);
  margin-bottom: var(--space-4);
  padding: var(--space-1);
  background: var(--c-surface-sunken);
  border-radius: var(--radius-full);
}

.nav__tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex: 1;
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--c-text-muted);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background var(--dur-fast) var(--ease-out);
}

/* The selected tab is a filled pill *and* carries `aria-current`, so the
 * distinction survives both a colour-blind reader and a screen reader. */
.nav__tab--on {
  background: var(--c-surface);
  color: var(--c-text);
  box-shadow: var(--shadow-sm);
}

/* Mint, not red. A queue of things to label is an invitation, not a problem the
 * user caused — the same call as `.tag--review`. */
.nav__badge {
  min-width: 1.25rem;
  padding: 0 var(--space-1);
  border-radius: var(--radius-full);
  background: var(--c-mint-soft);
  color: var(--c-text);
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
}

/* ---- Category picker -----------------------------------------------------
 * A native `<select>`, styled to read as a tag rather than as a form control.
 * Native because it opens the platform's own picker on iOS and Android, which
 * is scrollable, thumb-sized, and already familiar — and because it is the only
 * version that is keyboard- and screen-reader-navigable for free. */

.picker {
  max-width: 100%;
  /* Below the 44px floor on purpose: this sits inline in a dense list and the
   * whole row is comfortably past the minimum. The picker's own hit area is
   * padded out to meet it in `.tx--review`, where it is the primary action. */
  padding: 2px var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: var(--c-surface-sunken);
  color: var(--c-text-muted);
  font-family: inherit;
  font-size: var(--text-xs);
  /* The default arrow doubles the control's width for no information; the
   * dotted underline below is the affordance that matters. */
  appearance: none;
}

.picker:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* Automatic, and therefore correctable. Same dotted underline as `.tag--auto`,
 * for the same reason: every non-user-set category must invite a correction. */
.picker--auto {
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}

/* Nothing chosen yet. */
.picker--review {
  background: var(--c-mint-soft);
  color: var(--c-text);
  font-weight: 600;
}

/* Where a category came from, in words. The visible half of the confidence
 * affordance — the dotted underline alone says nothing to a screen reader. */
.tx__why {
  color: var(--c-text-faint);
  font-size: var(--text-xs);
}

/* ---- Review queue -------------------------------------------------------- */

.review__header {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.review__intro {
  margin: 0;
  color: var(--c-text-muted);
  font-size: var(--text-sm);
}

/* The queue's rows stack rather than sitting on one line: the picker is the
 * primary action here, not an afterthought beside the amount. */
.tx--review .tx__main {
  gap: var(--space-2);
}

.tx--review .picker {
  align-self: flex-start;
  min-height: var(--touch-target);
  padding-inline: var(--space-3);
  border-color: var(--c-border-strong);
  background: var(--c-surface);
  color: var(--c-text);
  font-size: var(--text-base);
}

.toggle {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  color: var(--c-text-muted);
  font-size: var(--text-sm);
}

.toggle input {
  width: 1.25rem;
  height: 1.25rem;
  accent-color: var(--c-accent);
}

.toggle--compact {
  min-height: auto;
  flex: none;
}

/* ---- Rules --------------------------------------------------------------- */

.rule-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  padding: var(--space-4);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
}

/* The live match count. Shown before saving, including when it is zero — that
 * is the most useful case, because it says the rule does nothing while there is
 * still time to fix it. */
.rule-form__preview {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--c-accent-soft);
  border-radius: var(--radius-md);
  color: var(--c-text);
  font-size: var(--text-sm);
}

/* ---- Receipts -------------------------------------------------------------
 *
 * The tone rule again, applied to a screen that is easy to get wrong. A receipt
 * the matcher could not place is not a failure and must not be dressed as one:
 * it is the app declining to guess between two charges it genuinely cannot tell
 * apart, which is the behaviour the user would want if they were asked. So the
 * asking state is mint — the same colour the review queue uses for an invitation
 * — and nothing on this screen is red. */

.receipts {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4) 0;
}

.receipts__capture {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Full width and thumb-sized: this is the primary action of the screen and, on
 * a phone, the button somebody presses while holding a piece of paper in their
 * other hand. */
.receipts__capture .button--primary {
  width: 100%;
  border-radius: var(--radius-lg);
}

.button--primary {
  background: var(--c-accent);
  color: var(--c-accent-text);
  border-radius: var(--radius-md);
}

.receipts__heading {
  font-size: var(--text-lg);
  font-weight: 700;
  margin: var(--space-2) 0 0;
}

.receipts__note {
  font-size: var(--text-sm);
  color: var(--c-text-muted);
  margin: 0;
  /* Sentences wrap; a note that reads as a warning label is a note nobody
   * reads. */
  max-width: 46ch;
}

.receipts__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.receipt {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-md);
}

.receipt--asking {
  border-color: var(--c-mint);
  background: var(--c-mint-soft);
}

.receipt__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.receipt__merchant {
  font-weight: 600;
}

.receipt__meta {
  font-size: var(--text-sm);
  color: var(--c-text-muted);
}

.receipt__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: flex-end;
}

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

/* The whole row is the target, not a small button inside it. This is a choice
 * between things, made with a thumb, and a 44px pill beside a description is a
 * smaller target than the description the user is already reading. */
.candidate__pick {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  background: var(--c-surface);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--radius-md);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-spring);
}

.candidate__pick:active {
  transform: scale(0.99);
}

.candidate__pick:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

.candidate__main,
.candidate__figures {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.candidate__figures {
  align-items: flex-end;
  /* Never let the amount shrink or wrap. Money is right-aligned and never
   * truncated, everywhere in this app. */
  flex: 0 0 auto;
}

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

.candidate__why,
.candidate__date {
  font-size: var(--text-xs);
  color: var(--c-text-muted);
}

/* The paperclip on a ledger row. Quiet on purpose — it is a fact about the row,
 * not a call to action, and the ledger is for reading. */
.tag--receipt {
  background: var(--c-surface-sunken);
  color: var(--c-text-muted);
}

/* ---- The confirmation sheet ----------------------------------------------
 *
 * Modal, and deliberately so. This is the step that everything the parser is
 * allowed to guess at is borrowed against: it repairs OCR digit confusions and
 * assumes month-first on an ambiguous date, and both are only acceptable
 * because a person looks at the result. Dismissing it by accident would save an
 * unreviewed reading, so there is no click-outside-to-close. */

.sheet {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgb(46 42 38 / 40%);
  padding: var(--space-4);
  z-index: 10;
}

.sheet__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: 100%;
  max-width: 32rem;
  /* Never taller than the viewport, and scrollable inside itself: a keyboard
   * appearing on a phone halves the available height, and a sheet whose
   * buttons have gone under it is a sheet the user cannot finish. */
  max-height: 90vh;
  overflow-y: auto;
  padding: var(--space-5);
  background: var(--c-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.sheet__title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 700;
}

.sheet__actions {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
  margin-top: var(--space-2);
}

.field__hint {
  font-size: var(--text-xs);
  color: var(--c-text-faint);
}

/* Slides up rather than fading in, so the direction matches the gesture that
 * would dismiss it. Honoured only where motion is welcome. */
@media (prefers-reduced-motion: no-preference) {
  .sheet__body {
    animation: sheet-in var(--dur-base) var(--ease-out);
  }

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

@media (min-width: 40rem) {
  .sheet {
    align-items: center;
  }
}

/* ---- Budgets -------------------------------------------------------------
 * The colour ramp is `--c-budget-ok` → `--c-budget-near` → `--c-budget-over`,
 * which is mint → butter → peach. Peach, not `--c-danger`: going over a budget
 * is a fact about a month, not an error the user committed, and the alarm
 * colour stays reserved for destructive confirmations. See the tone rule in
 * `bunny.css` — this file is where it either holds or does not.
 *
 * Nothing here is colour-only. Every band has a text equivalent in
 * `format::budget_line`, and the bar itself is `role="progressbar"` carrying
 * the same numbers. */

.budgets {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.budgets__allclear {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--c-mint-soft);
  border-radius: var(--radius-lg);
}

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

.budgets__list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.budget {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
}

.budget__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.budget__name {
  font-weight: 600;
  /* The name gives way, never the figures. Truncating a budget's label is a
   * cosmetic loss; truncating what is left in it is a lie. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.budget__bar {
  position: relative;
  height: 10px;
  border-radius: var(--radius-full);
  background: var(--c-surface-sunken);
  overflow: hidden;
}

.budget__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--c-budget-ok);
}

.budget--watch .budget__fill {
  background: var(--c-budget-near);
}

.budget--over .budget__fill {
  background: var(--c-budget-over);
}

/* Where the bar would be if spending were perfectly even. One tick, and it is
 * what turns "60% used" into "60% used and it is the 3rd" — the comparison the
 * fill alone cannot make, because a full-looking bar on the 29th and one on the
 * 3rd are opposite situations. */
.budget__pace-mark {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--c-border-strong);
}

/* A hatched cap on a bar that is already full, so "at the limit" and "well past
 * it" are not the same picture. The text says the figure either way. */
.budget__overflow {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 18%;
  background: repeating-linear-gradient(
    45deg,
    var(--c-budget-over) 0 4px,
    var(--c-peach-soft) 4px 8px
  );
}

.budget__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.budget__line {
  font-variant-numeric: tabular-nums;
}

.budget__pace {
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

.budget__actions {
  display: flex;
  justify-content: flex-end;
}

/* Butter rather than mint: a budget wanting attention is not the same kind of
 * thing as a queue of transactions to label, and the two badges sit next to each
 * other in the tab bar. Still not peach — a badge is a signpost, and the tab it
 * points at is where the situation gets explained. */
.nav__badge--budget {
  background: var(--c-butter-soft);
}

/* The good-news banner. The only place in the app where a banner is a reward,
 * and mint marks it as such against the butter of an ordinary alert. */
.banner--good {
  background: var(--c-mint-soft);
}

.banner__text {
  min-width: 0;
  flex: 1;
}

/* At this rate, where the period lands. Butter rather than peach: it is a
 * projection rather than a fact, and dressing a forecast in the same colour as
 * "you are over" would make the two indistinguishable at a glance when only one
 * of them has already happened. */
.budget__projection {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--c-butter-soft);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

/* Every budget added up. Sits above the bars because it answers the question
 * somebody opened the screen with, and the individual rows answer the follow-up.
 * Deliberately quiet: it is one line of orientation, not the content. */
.summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  background: var(--c-surface-sunken);
  border-radius: var(--radius-lg);
}

.summary__figures {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  flex-wrap: wrap;
}

.summary__spent {
  font-size: var(--text-xl);
  font-weight: 700;
}

.summary__of {
  color: var(--c-text-muted);
  font-variant-numeric: tabular-nums;
}

.summary__line {
  margin: 0;
  color: var(--c-text-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.budget-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--c-surface-sunken);
  border-radius: var(--radius-lg);
}

.budget-form__title {
  font-size: var(--text-base);
  font-weight: 600;
}

.budget-form__actions {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
}

/* The optional half of the form, behind its own disclosure. Left-aligned and
 * quiet so it reads as an offer rather than a step. */
.budget-form__more {
  align-self: flex-start;
  padding-inline: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The trigger that opens either form. Full width because on the empty screen it
 * is the only thing to do, and on a full one it is the end of the list. */
.budgets__add,
.funds__add {
  width: 100%;
  border: 1px dashed var(--c-border-strong);
  border-radius: var(--radius-lg);
}

/* Changing a budget or a fund that already exists. Inset and sunken so it reads
 * as part of the row it belongs to rather than as a new card floating under it —
 * an editor that looked like a sibling would leave the user unsure which budget
 * they were about to change. */
.budget-editor,
.fund-editor {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding: var(--space-3);
  background: var(--c-surface-sunken);
  border-radius: var(--radius-md);
}

/* ---- Sinking funds --------------------------------------------------------
 *
 * The other half of the budget screen, and the half a period boundary destroys.
 * A budget is a ceiling that resets; a fund is a balance that accumulates.
 *
 * The colour language is deliberately *not* the budget ramp. A budget's bar
 * fills with spending and going further is worse, so it runs mint → butter →
 * peach. A fund's bar fills with saving and going further is better, so it is
 * accent throughout and turns mint when it is done. Reusing the budget ramp here
 * would mean a nearly-funded envelope glowing peach, which says the opposite of
 * what happened.
 *
 * Peach still appears, for the two states that genuinely want a look: behind
 * schedule, and overdrawn. Never red — falling behind on a savings plan is a
 * fact about a schedule, and `--c-danger` stays reserved for destructive
 * confirmations. */

.funds {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--c-border);
}

.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.section-head__title {
  margin: 0;
  font-size: var(--text-lg);
}

.section-head__body {
  margin: 0;
  max-width: 46ch;
  color: var(--c-text-muted);
  font-size: var(--text-sm);
}

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

.fund {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
}

.fund__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.fund__name {
  font-weight: 600;
  /* The name gives way, never the figures. Same rule as `.budget__name`:
   * truncating a label is cosmetic, truncating an amount is a lie. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fund__bar {
  position: relative;
  height: 10px;
  border-radius: var(--radius-full);
  background: var(--c-surface-sunken);
  overflow: hidden;
}

.fund__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--c-accent);
}

/* Done. Mint, the app's colour for a thing that came out well. */
.fund--funded .fund__fill {
  background: var(--c-mint);
}

/* Short of the schedule, and overdrawn. Peach for both, and the sentence beside
 * the bar is what distinguishes them — a colour on its own has never been the
 * only signal anywhere in this app. */
.fund--behind .fund__fill,
.fund--overdrawn .fund__fill {
  background: var(--c-peach);
}

/* Nothing left in it at all. Without this the bar is simply empty, which looks
 * the same as a fund nobody has started paying into. */
.fund--overdrawn .fund__bar {
  background: var(--c-peach-soft);
}

.fund__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.fund__line {
  font-variant-numeric: tabular-nums;
}

.fund__pace {
  color: var(--c-text-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

/* The deadline and what has been taken out. Facts about the fund rather than
 * calls to action, so they sit below the sentence that matters and are sized
 * accordingly. */
.fund__meta--quiet {
  color: var(--c-text-faint);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

.fund__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* Same reasoning as `.budget__fill`: a bar that jumps has no direction, and
 * direction is the information. Dropped under reduced motion, where the figures
 * beside it were never carried by the animation anyway. */
@media (prefers-reduced-motion: no-preference) {
  .fund__fill {
    transition: width var(--dur-slow) var(--ease-out),
      background-color var(--dur-base) var(--ease-out);
  }
}

/* The fill animates because a bar that jumps has no direction, and direction is
 * the information — a budget going up and one being corrected downward look
 * identical at rest. Dropped entirely under reduced motion: the figures beside
 * it were never carried by the animation. */
@media (prefers-reduced-motion: no-preference) {
  .budget__fill {
    transition: width var(--dur-slow) var(--ease-out),
      background-color var(--dur-base) var(--ease-out);
  }
}

/* ---- Without an account ---------------------------------------------------
 *
 * Offered below the sign-in form, not beside it. An account is still the better
 * product — it fills itself in and it survives the phone — and presenting the
 * two as equals would be selling somebody the harder one by accident. But it is
 * a real offer rather than a teaser, so it gets the same surface and the same
 * touch targets as everything else, not a footnote's typography. */

.offline-offer {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--c-border);
  text-align: start;
}

.offline-offer__title {
  margin: 0;
  font-size: var(--text-lg);
}

.offline-offer__body {
  margin: 0;
  color: var(--c-text-muted);
  font-size: var(--text-sm);
}

/* The trade, stated before the choice rather than discovered after it. Muted
 * rather than red: these are the terms of a reasonable option, not warnings
 * about a dangerous one, and colouring them as alarm would make the honest list
 * read as a reason not to. */
.offline-offer__costs {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-5);
  background: var(--c-surface-sunken);
  border-radius: var(--radius-md);
  color: var(--c-text-muted);
  font-size: var(--text-sm);
}

/* ---- Adding an entry by hand ----------------------------------------------
 *
 * The one screen local mode adds, and the one real cost of having no bank. It
 * is styled to be reopened dozens of times rather than admired once: the form
 * stays open after a save, and the trigger is a full-width target because it is
 * the most-tapped control on the ledger for anyone using this mode. */

.add-entry__open {
  width: 100%;
  margin-bottom: var(--space-4);
}

.add-entry {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  padding: var(--space-4);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
}

.add-entry__direction {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  border: 0;
}

.add-entry__direction .field__label {
  flex-basis: 100%;
  padding: 0;
}

.choice {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  font-size: var(--text-base);
}

.choice input {
  width: 1.25rem;
  height: 1.25rem;
  accent-color: var(--c-accent);
}

/* Money the user is typing gets the tabular face for the same reason money the
 * app prints does: a column of figures that shifts as digits are added is one
 * nobody can check at a glance. */
.field__input--amount {
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
}

/* ---- What this device has learned -----------------------------------------
 *
 * Only in local mode, where merchant memory has nowhere else to live. It is
 * listed rather than hidden because a memory nobody can inspect is a memory
 * nobody can correct, and this one is answering questions on the user's behalf
 * every time they add an entry. */

.memory {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

.memory__intro {
  margin: 0;
  color: var(--c-text-muted);
  font-size: var(--text-sm);
}

.memory__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.memory__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: 0 var(--space-3);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-md);
}

.memory__merchant {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.memory__count {
  color: var(--c-text-muted);
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
}
