/* App shell: nav rail / bottom bar, module header, boot + login screens. */

.app {
  display: none;
  min-height: 100vh;
  min-height: 100dvh;
}
/* The rail's width is reserved by the .app padding rather than a margin on
   .app-main, so the main column can centre itself in what's left instead of
   being pinned to the rail. */
@media (min-width: 900px) {
  .app { padding-left: var(--rail-w); }
}
.app.is-ready { display: block; }

/* ── Navigation ──────────────────────────────────────────────────────────
   One <nav>, two layouts, zero duplicate DOM. Bottom bar on phones because
   the highest-frequency action is logging a spend, and that belongs at thumb
   height. Left rail on desktop because each module already wants its own
   horizontal toolbar and top tabs would stack two bars. */
.nav {
  position: fixed;
  z-index: 40;
  background: var(--surface);
  display: flex;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  background: none;
  border: 0;
  color: var(--ink-3);
  text-decoration: none;
  transition: color 120ms var(--ease);
}
.nav-item .nav-icon { display: block; width: 22px; height: 22px; }
.nav-item.is-active { color: var(--accent); }
.nav-item:hover { color: var(--ink); }
.nav-item.is-active:hover { color: var(--accent); }

.nav-label { font-size: var(--t-micro); font-weight: 500; }

/* phone: bottom bar */
@media (max-width: 899px) {
  .nav {
    left: 0; right: 0; bottom: 0;
    height: calc(var(--bar-h) + var(--safe-bottom));
    padding-bottom: var(--safe-bottom);
    border-top: 1px solid var(--rule);
    justify-content: space-around;
    align-items: stretch;
  }
  .nav-item {
    flex: 1;
    flex-direction: column;
    justify-content: center;
    gap: 3px;
    padding: var(--s-1) 0;
  }
  /* There is no rail footer on a phone, so secondary items flow into the bar
     rather than disappearing — display:none here left admin unreachable on
     mobile entirely. `contents` keeps them as direct flex children. */
  .nav-rail-foot { display: contents; }
}

/* desktop: left rail */
@media (min-width: 900px) {
  .nav {
    top: 0; bottom: 0; left: 0;
    width: var(--rail-w);
    padding: var(--s-5) 0 var(--s-4);
    padding-left: var(--safe-left);
    border-right: 1px solid var(--rule);
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-1);
  }
  .nav-item {
    flex-direction: column;
    gap: 4px;
    padding: var(--s-3) var(--s-1);
  }
  /* Admin is not a peer tab — it sits in the rail footer so the three money
     modules stay the primary axis. */
  .nav-rail-foot { margin-top: auto; display: flex; flex-direction: column; }
}

/* ── Module header ───────────────────────────────────────────────────────
   One large amount, one short label, one rule whose width encodes progress.
   Same position in every module; only the meaning changes. This is the one
   place boldness is spent. */
.app-main {
  /* Longhands only, deliberately. The bottom padding that clears the fixed tab
     bar used to live in the phone media query ABOVE this rule, and the `padding`
     shorthand here reset it to 0 — so the bar sat on top of the last section of
     every page.

     padding-top takes --safe-top because the viewport is `viewport-fit=cover`:
     without it the topbar slides under the status bar / notch on a phone. */
  padding-top: max(var(--s-4), var(--safe-top));
  padding-left: max(var(--s-4), var(--safe-left));
  padding-right: max(var(--s-4), var(--safe-right));
  padding-bottom: calc(var(--bar-h) + var(--safe-bottom) + var(--s-5));
  max-width: 1040px;
  margin: 0 auto;
}
@media (min-width: 900px) {
  .app-main {
    padding-top: max(var(--s-5), var(--safe-top));
    padding-left: max(var(--s-6), var(--safe-left));
    padding-right: max(var(--s-6), var(--safe-right));
    padding-bottom: var(--s-6);
  }
}

/* ── Topbar ──────────────────────────────────────────────────────────────
   The only chrome above the module header: what this is, who you are, and the
   way out. Sign-out lives here rather than in the nav because it is not a
   destination, and burying it behind a menu on a four-item bottom bar would
   make it unreachable on a phone. */
.topbar {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

/* ── Logo ────────────────────────────────────────────────────────────────
   The mark is one <symbol> in index.html, used twice. It is inline SVG rather
   than an <img> for one reason: currentColor. An external image can't inherit
   the theme, and the brand teal is too dark to sit on the dark canvas, so the
   logo takes the app accent and tracks the theme for free.

   The wordmark is live text in Inter rather than traced outlines — it stays
   crisp, selectable and in the app's own voice. Caps with tracking here is
   the brand lockup, not the ALL-CAPS eyebrow the design rules ban. */
.logo-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--accent-app);
}
.logo-mark {
  height: 1.55em;
  width: auto;
  fill: currentColor;
  flex-shrink: 0;
}
.logo-word {
  font-size: var(--t-lead);
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  /* The tracking is on the right of the last letter too; pull it back so the
     lockup is optically centred rather than sitting a hair to the left. */
  margin-right: -0.13em;
  line-height: 1;
}

/* Stacked, for the login screen. */
.logo--stacked {
  flex-direction: column;
  gap: var(--s-3);
}
.logo--stacked .logo-mark { height: 76px; }
.logo--stacked .logo-word { font-size: var(--t-h1); }
.logo-tagline {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.17em;
  margin-right: -0.17em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.topbar-who {
  margin-left: auto;
  font-size: var(--t-label);
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.headline { margin-bottom: var(--s-5); }

.headline-module {
  font-size: var(--t-label);
  font-weight: 500;
  color: var(--ink-3);
  margin-bottom: var(--s-2);
}

.hero-amount {
  font-size: var(--t-hero);
  font-weight: 600;
  /* Inter has no width axis, so the hero is set apart by size, weight and
     tracking alone — plus its optical size, which the browser picks up from
     the variable font on its own at this size. */
  letter-spacing: -0.022em;
  line-height: 1.05;
  color: var(--ink);
}

.headline-label {
  margin-top: var(--s-2);
  font-size: var(--t-small);
  color: var(--ink-2);
}

/* Not decoration: the width is the progress. */
.headline-meter {
  margin-top: var(--s-3);
  height: 2px;
  width: 160px;
  max-width: 100%;
  background: var(--rule);
  border-radius: 2px;
  overflow: hidden;
}
.headline-meter > i {
  display: block;
  height: 100%;
  width: var(--pct, 0%);
  background: var(--accent);
  transition: width 320ms var(--ease);
}

.module-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-5);
}

/* ── Module mount ────────────────────────────────────────────────────────
   The only orchestrated motion: the content crossfades, the nav and header
   never move. */
#module-root { animation: module-in 140ms var(--ease); }
@keyframes module-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* Shown only once a module switch has been slow enough to notice — see
   shell.js. A fixed height, not 40vh: vh on a phone tracks the browser chrome
   collapsing, so the spinner drifted while the page loaded. */
.module-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 180px;
  padding: var(--s-6) 0;
}

/* ── Boot and login ──────────────────────────────────────────────────────── */
.boot, .login {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--canvas);
  padding: var(--s-5);
  z-index: 100;
}

/* The track is --accent-dim, not --rule: --rule is a 10%-alpha hairline, which
   at 2px left no visible ring at all — just a lone arc flicking round, which
   read as a rendering fault rather than a spinner. */
.spinner {
  width: 26px; height: 26px;
  border: 2.5px solid var(--accent-dim);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 800ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* base.css flattens every animation to 0.01ms under reduced motion, which for
   an infinite spin means a strobing blur rather than no motion. A slow, small
   rotation is the accessible form of this control, so it opts back in. */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 1.4s !important; }
}

/* Same corner as in the app, so the control is where you already found it. */
.login-theme {
  position: absolute;
  top: max(var(--s-4), var(--safe-top));
  right: max(var(--s-4), var(--safe-right));
}

.login-card {
  width: 100%;
  max-width: 380px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-5);
  padding: var(--s-6) var(--s-5);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
}
/* --canvas and --surface are the same white in light mode, so the border is
   what makes this read as a container at all. */

.login-blurb { color: var(--ink-2); font-size: var(--t-small); margin: 0; }
.login-btn { width: 100%; }

.login-error {
  background: var(--out-dim);
  color: var(--out);
  border-radius: var(--r-1);
  padding: var(--s-3);
  font-size: var(--t-small);
  width: 100%;
}

/* ── No access ───────────────────────────────────────────────────────────── */
.no-access {
  max-width: 420px;
  margin: var(--s-7) auto;
  text-align: left;
}

/* ── Read-only degradation ───────────────────────────────────────────────
   Modules mark their create/edit/delete affordances [data-write] and never
   branch on permissions themselves. This is cosmetic only — the server is
   authoritative. */
[data-access="read"] [data-write] { display: none !important; }
