/* Self-hosted variable fonts (SIL OFL, see frontend/fonts/*/OFL.txt).
   Each family ships one variable-weight woff2 per subset; the font-weight
   range lets the browser instance the exact weight requested instead of
   needing a separate file per weight. */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('fonts/fraunces/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('fonts/fraunces/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 500 800;
  font-display: swap;
  src: url('fonts/manrope/manrope-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 500 800;
  font-display: swap;
  src: url('fonts/manrope/manrope-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Icon font for the app's quick-action tiles (ISC, see
   frontend/fonts/icons/LICENSE.txt) — self-hosted the same way as the two
   text faces above. See that directory's README for the glyph table. */
@font-face {
  font-family: 'SummerhouseIcons';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('fonts/icons/summerhouse-icons.woff2') format('woff2');
}

:root {
  color-scheme: light dark;

  /* Design language shared with event.summerhouse.app — copied verbatim so
     the two apps read as one product. */
  --crust:#faf5ee; --crust-2:#f1e7d7; --card:#ffffff; --card-2:#f7ede0;
  --cream:#2a2016; --cream-dim:#7c6c5a; --tomato:#d6491f; --tomato-dark:#b53d1f;
  --basil:#4f7e42; --basil-dark:#3d6333; --semolina:#b8811a; --slate:#3f6b7a;
  --melanzana:#6b4c93; --line: rgba(42,32,22,0.14); --radius: 14px;
  --safe-top: env(safe-area-inset-top, 0px); --safe-bottom: env(safe-area-inset-bottom, 0px);

  /* Spacing and type scale — community-specific, kept consistent everywhere
     rather than ad-hoc pixel values. */
  --space-1: .25rem; --space-2: .5rem; --space-3: .75rem;
  --space-4: 1rem; --space-5: 1.5rem; --space-6: 2.5rem;
  --font-sm: .8125rem; --font-base: 1rem; --font-lg: 1.125rem;
  --shadow-card: 0 1px 3px rgba(42,32,22,.10);
}

:root[data-theme="dark"] {
  --crust:#1b1410; --crust-2:#241b15; --card:#2b2019; --card-2:#332619;
  --cream:#f5efe6; --cream-dim:#c9bfae; --tomato:#e1512b; --basil:#6fa15e;
  --semolina:#e8b84b; --slate:#5c8a9b; --melanzana:#b79ce0;
  --line: rgba(245,239,230,0.12);
  --shadow-card: 0 1px 3px rgba(0,0,0,.35);
}

* { box-sizing: border-box; }
html, body { height: 100%; }

/* The app shell (VISION.md "The app shell — fixed, never scrolling").
   body is a column of topbar + #app, sized to the DYNAMIC viewport so #app
   never has to grow past it — dvh, not vh, because a phone browser's
   collapsing address bar changes vh mid-scroll and the bottom nav would
   move with it.

   Screens that are NOT the four-band shell (login, tiles, people, the
   profile/password screens) still scroll the page normally; #app keeps
   overflow-y: auto for them. When a shell IS mounted, #app carries
   .app-shelled and stops scrolling entirely — see below. */
body {
  margin: 0;
  padding: 0;
  background: var(--crust);
  color: var(--cream);
  font-family: 'Manrope', system-ui, sans-serif;
  font-size: var(--font-base);
  line-height: 1.5;
  transition: background-color .2s ease, color .2s ease;
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
}

h1, h2, h3 { font-family: 'Fraunces', serif; font-weight: 700; line-height: 1.25; margin: 0 0 var(--space-3); }
h2 { font-size: 1.375rem; }
h3 { font-size: 1.125rem; }

a { color: var(--tomato); }

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

:focus-visible {
  outline: 2px solid var(--tomato);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------
   Topbar (VISION.md "The topbar", issue #20): home, the current service's
   headline centred — where the signed-in person's name and role used to be
   — theme toggle, then a profile button (name/role, change password, log
   out live inside it now). Hidden entirely on the login screen. A fixed
   band, not scrolling with the content beneath it. */
.topbar {
  flex: 0 0 auto;
  background: var(--crust-2);
  border-bottom: 1px solid var(--line);
}
.topbar.hidden { display: none; }

.topbar-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: calc(var(--space-2) + var(--safe-top)) var(--space-3) var(--space-2);
  max-width: 60rem;
  margin-inline: auto;
}

.topbar-btn {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--cream);
  font-weight: 700;
  font-size: var(--font-sm);
  cursor: pointer;
}
.topbar-btn:active { background: var(--card-2); }

.topbar-icon { padding: 0; font-size: 1.15rem; }

.topbar-logout { color: var(--tomato); }

.topbar-headline {
  flex: 1;
  min-width: 0;
  text-align: center;
  font-family: 'Fraunces', serif;
  font-weight: 700;
  font-size: var(--font-lg);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

@media (min-width: 640px) {
  .topbar-btn { font-size: var(--font-base); padding: var(--space-2) var(--space-4); }
}

/* The profile VIEW (#88 — the overlay panel and its .profile-panel/
   .topbar-name css died with the modal): identity, then change password,
   then log out — the destructive act deliberately last. */
.profile-view { display: flex; flex-direction: column; gap: var(--space-3); max-width: 26rem; }

/* ---------------------------------------------------------------------
   Main content column. flex: 1 fills whatever height the topbar leaves.
   overflow-y: auto is for the NON-shell screens (login, the tile picker,
   people, profile/password). A mounted shell adds .app-shelled, which turns
   it off — inside the shell only .shell-content scrolls. */
main#app {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 60rem;
  margin-inline: auto;
  padding: var(--space-4) var(--space-3) calc(var(--space-4) + var(--safe-bottom));
}

/* Inside a shell the safe-area padding belongs to the bottom nav, not to
   #app — otherwise the nav floats above a strip of dead space. */
main#app.app-shelled {
  padding-bottom: 0;
}
/* shell v3 (#112 v2): the BAND owns the horizontal padding — #app adds
   none, so a .full-bleed table really reaches the viewport edge */
main#app.app-shell-v3 { padding-inline: 0; padding-top: var(--space-2); }

@media (min-width: 960px) {
  main#app { padding: var(--space-5) var(--space-4) calc(var(--space-5) + var(--safe-bottom)); }
}

/* ---------------------------------------------------------------------
   The fixed shell (app-shell.js): quick actions, content, bottom nav.

   STRUCTURAL, not measured. An explicit grid with three rows — auto / 1fr /
   auto — so the bottom nav is incapable of being pushed anywhere, rather
   than merely usually staying put. The previous version relied on pager.js
   measuring available height and fitting rows to it, with the page as an
   escape hatch: anything the measurement did not anticipate (an expanded
   transaction row, a wrapping header line) pushed the whole page and took
   the nav with it. Row-fitting still decides how many rows a page holds; it
   is no longer what holds the layout together.

   min-height: 0 on the content row is load-bearing. A grid row's default
   min-height is auto, meaning it refuses to shrink below its content — so
   without this the content row grows to fit and pushes the nav off-screen,
   which is exactly the bug this replaces.

   Modern browsers only: grid and dvh are used directly, no fallbacks. */
.app-shelled {
  overflow: hidden;   /* the page itself never scrolls while a shell is up */
}
.shell {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 0;
  height: 100%;
}
/* A screen with no quick actions (#27, VISION.md "It is the layout for
   everything") gets this instead of an empty measured `auto` row — the
   grid itself only has two explicit rows, so the band is structurally
   absent, not present-but-empty. */
.shell-no-actions {
  grid-template-rows: 1fr auto;
}
.shell-content {
  min-height: 0;
  /* The ONLY scrollable region in the shell. An expanded transaction row
     taller than the band (FINANCE-UX.md §5.5) scrolls here instead of
     pushing the page — the nav stays exactly where the thumb left it. */
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---------------------------------------------------------------------
   Bottom nav (VISION.md "The app shell"): the scroll-preference toggle to
   the LEFT of the pager arrows, so both navigation controls sit together —
   shared by every paged list (bottom-nav.js). */
.bottom-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.bottom-nav .pager { margin-top: 0; justify-content: flex-end; }

.muted { color: var(--cream-dim); font-size: var(--font-sm); }
.hidden { display: none; }

.error {
  color: var(--tomato);
  font-weight: 700;
  margin: var(--space-3) 0;
}
.error:empty { display: none; }

/* ---------------------------------------------------------------------
   Buttons, inputs, selects — one shared radius/border treatment. */
button {
  cursor: pointer;
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--cream);
  font-weight: 700;
  font-size: var(--font-base);
}
button:active { background: var(--card-2); }

button[type="submit"] {
  background: var(--tomato);
  color: #fff;
  border-color: transparent;
}
button[type="submit"]:active { background: var(--tomato-dark); }

input, select, textarea {
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--cream);
  font-size: 16px; /* keep iOS from zooming in on focus */
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--tomato);
  outline-offset: -1px;
}

.field { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-3); }
.field label { font-size: var(--font-sm); color: var(--cream-dim); }

label.inline { display: flex; align-items: center; gap: var(--space-2); min-height: 44px; }
label.inline input { min-height: auto; width: 20px; height: 20px; }

/* ---------------------------------------------------------------------
   Forms: the create-account row stacks on a phone, sits inline once
   there's room to breathe. (The journal's old account/date filter form used
   this too, before #25 folded it into Search — FINANCE-UX.md §4.) */
.create-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: stretch;
  margin: var(--space-3) 0 var(--space-4);
}

@media (min-width: 640px) {
  .create-form { flex-direction: row; flex-wrap: wrap; align-items: flex-end; }
}

.inline { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }

.login-form {
  max-width: 22rem;
  margin: var(--space-5) auto 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.entry-form {
  max-width: 34rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow-card);
  padding: var(--space-4);
  margin: var(--space-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.posting-rows { display: flex; flex-direction: column; gap: var(--space-2); }
.posting-row { display: flex; flex-direction: column; gap: var(--space-2); }
.posting-row select { flex: 1; }

@media (min-width: 480px) {
  .posting-row { flex-direction: row; align-items: center; }
  .posting-row input { width: 12rem; }
}

.posting-sum { font-weight: 700; }
.posting-sum.balanced { color: var(--basil); }

/* ---------------------------------------------------------------------
   Login — the front door. Calm, centered, full height; the app-wide
   topbar is hidden while this is on screen. */
.login-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: calc(var(--space-6) + var(--safe-top)) var(--space-4) calc(var(--space-6) + var(--safe-bottom));
  text-align: center;
}
.login-mark {
  font-size: clamp(1.75rem, 6vw, 2.25rem);
  margin-bottom: var(--space-2);
}
.login-tag {
  color: var(--cream-dim);
  max-width: 30ch;
  line-height: 1.6;
  margin: 0 auto;
}
.login-wrap .login-form { text-align: left; }
.login-wrap .error { min-height: 1.2em; }

/* ---------------------------------------------------------------------
   Tiles — the real home screen (#53). The container is a PLAIN single
   column with NO gap at every width: each .tile-row is one pager item and
   carries ALL spacing inside its own box (padding-bottom), so the pager's
   height sum is exact. A container gap or responsive multi-column here is
   spacing the pager cannot see — that WAS #40's static-scroll bug. The two
   columns live inside .tile-row, never on this container. */
.tiles {
  display: block;
  margin-top: var(--space-4);
}

.tile {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow-card);
  padding: var(--space-4);
  min-height: 96px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-1);
  transition: transform .1s ease;
}

.tile h3 { margin: 0; }
.tile p { margin: 0; color: var(--cream-dim); font-size: var(--font-sm); }

.tile-built { cursor: pointer; }
.tile-built:active { background: var(--card-2); transform: scale(.98); }

.tile-unbuilt { opacity: .55; }

.tile-badge {
  display: inline-block;
  align-self: flex-start;
  margin-top: var(--space-2);
  font-size: .7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--cream-dim);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: var(--space-1) var(--space-2);
}

/* ---------------------------------------------------------------------
   Money. Balances are the point of this app: tabular numerals, right
   aligned, negative values get color *and* keep their sign (never color
   alone — formatCents() always emits the "-"). */
.amount {
  font-variant-numeric: tabular-nums;
  font-family: 'Fraunces', serif;
  font-weight: 600;
  text-align: right;
}
.amount.negative { color: var(--tomato); font-weight: 700; }

/* The reconciliation detail inside the circulation row's modal
   (circulation-row.js, FINANCE-UX.md §2.4, §5.3) — one per location. The
   separate always-visible strip #13 shipped is gone; this only ever shows
   on tap now. */
.reconciliation-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  font-family: 'Fraunces', serif;
  font-size: 1.125rem;
  font-weight: 700;
  margin: 0;
}
.reconciliation-row + .reconciliation-row { margin-top: var(--space-1); }
.reconciliation-label { color: var(--cream-dim); font-weight: 600; }
.reconciliation-status { margin: var(--space-1) 0 0; font-size: var(--font-sm); }
.reconciliation-status.reconciled { color: var(--basil); font-weight: 700; }
.reconciliation-status.mismatch { color: var(--tomato); font-weight: 700; }

/* Overdraft warning (FINANCE-UX.md §2.6-§2.7): loud and persistent, not a
   number quietly sitting in a table row. */

/* ---------------------------------------------------------------------
   Tables. Mobile-first: every table.responsive-cards row renders as a
   labelled card by default (data-label on each <td> supplies the
   caption via ::before — no separate mobile render path in the JS).
   From 640px up they become real tables again, which is where scanning
   a column of numbers genuinely beats a stack of cards. */
table { border-collapse: collapse; width: 100%; margin-top: var(--space-3); }
th, td { text-align: left; padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--line); }
th { color: var(--cream-dim); font-weight: 700; font-size: var(--font-sm); text-transform: uppercase; letter-spacing: .03em; }

table.responsive-cards thead { display: none; }
table.responsive-cards, table.responsive-cards tbody { display: block; width: 100%; }
table.responsive-cards tr {
  display: block;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
}
table.responsive-cards tr.hidden { display: none; }
table.responsive-cards td {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line);
  width: 100%;
}
table.responsive-cards td:last-child { border-bottom: none; }
table.responsive-cards td::before {
  content: attr(data-label);
  font-size: .7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--cream-dim);
}
table.responsive-cards td:not([data-label])::before { content: none; }
table.responsive-cards td.amount { align-items: flex-end; }

@media (min-width: 640px) {
  /* Explicit display values rather than `revert`. revert is supported widely
     enough (Safari 15.4+), but if it ever is not, the declaration is dropped
     and the rows stay stacked as cards on a desktop — a silent, wrong-looking
     layout on the widest screens. Naming the values costs five lines and
     cannot fail. */
  table.responsive-cards { display: table; width: 100%; }
  table.responsive-cards thead { display: table-header-group; }
  table.responsive-cards tbody { display: table-row-group; }
  table.responsive-cards tr { display: table-row; width: auto; }
  table.responsive-cards th,
  table.responsive-cards td { display: table-cell; width: auto; }
  table.responsive-cards tr {
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    margin-bottom: 0;
  }
  table.responsive-cards td {
    border-bottom: 1px solid var(--line);
    padding: var(--space-2) var(--space-3);
  }
  table.responsive-cards td::before { content: none; }
  table.responsive-cards td.amount { text-align: right; }
}

table.statement { margin: var(--space-2) 0 var(--space-3); }
.postings { margin: 0; padding-left: 1.1rem; }
.postings li { margin-bottom: var(--space-1); }

/* ---------------------------------------------------------------------
   Roles, status and other small labels. Colour is always paired with
   text, never the only signal. */
.roles { display: flex; gap: var(--space-1); flex-wrap: wrap; }
.role-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3);
  font-size: var(--font-sm);
}
.role-tag button {
  min-width: 32px;
  min-height: 32px;
  padding: 0;
  border: none;
  background: none;
  color: var(--cream-dim);
  font-size: 1rem;
  border-radius: 50%;
}
.role-tag button:active { background: var(--card); color: var(--tomato); }

.status-temporary { color: var(--cream-dim); }
.status-known { color: var(--semolina); }
.status-member { color: var(--basil); }
.status-left { color: var(--cream-dim); text-decoration: line-through; }

/* ---------------------------------------------------------------------
   A project's checklist (issue #30, VISION.md §3b) and My tasks' own
   rendering of the same rows. The tick is the primary action and this app
   is thumb-first throughout (VISION.md "The app shell": actions are
   "always in the same place under the thumb"), so it gets the full-size
   touch target — `label.inline`'s existing 44px row plus its 20px
   checkbox already covers that; reorder/delete are secondary, so they
   follow the smaller round-button precedent `.role-tag button` already
   established for a rarer per-row action (removing a label). */
.task-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.task-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line);
}
.task-row:last-child { border-bottom: none; }
.task-check { flex: 1; min-width: 0; }
.task-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.task-text.task-done, .task-row.task-done .task-text { text-decoration: line-through; color: var(--cream-dim); }
.task-due { flex-shrink: 0; }
.task-assignee { flex-shrink: 0; }
.task-row select { min-height: 44px; max-width: 9rem; }
.task-row-actions { display: flex; gap: var(--space-1); flex-shrink: 0; }
.task-row-actions button {
  min-width: 32px;
  min-height: 32px;
  padding: 0;
  border: none;
  background: none;
  color: var(--cream-dim);
  font-size: 1rem;
  border-radius: 50%;
}
.task-row-actions button:active { background: var(--card-2); color: var(--tomato); }
.task-row-actions button:disabled { opacity: 0.35; cursor: default; }

/* ---------------------------------------------------------------------
   Finance: the six-tile quick-action row (FINANCE-UX.md §5.1). Fixed at
   six columns at EVERY width — the row must not reflow by role or by
   viewport, that stability is the whole point, so this does not reuse
   .tiles' responsive column count. Icon above, small label below. */
.quick-actions {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-1);
  margin: var(--space-3) 0 var(--space-4);
}
@media (min-width: 480px) {
  .quick-actions { gap: var(--space-2); }
}
@media (min-width: 960px) {
  .quick-actions { gap: var(--space-3); }
}

.tile-compact {
  position: relative;
  min-height: 64px;
  min-width: 44px;
  padding: var(--space-2) var(--space-1);
  align-items: center;
  text-align: center;
  gap: var(--space-1);
}
.tile-compact h3 {
  font-size: .7rem;
  font-weight: 600;
  margin: 0;
  line-height: 1.15;
  color: var(--cream-dim);
}
.tile-compact p { display: none; }
.tile-compact .icon { font-size: 1.4rem; }
@media (min-width: 480px) {
  .tile-compact .icon { font-size: 1.6rem; }
  .tile-compact h3 { font-size: var(--font-sm); }
}

.tile-compact[aria-disabled='true'] { opacity: .45; cursor: pointer; }

/* The journal's own header line, inside the content band (FINANCE-UX.md
   §5.3b, issue #25): its "Journal" label plus the `New entry` control that
   opens the manual multi-leg entry modal — not a tile, so it stays a plain
   button, not `.tile-compact`'s icon-first sizing. `Build ledger` (§7,
   still not built) shares this line, same person same purpose per §5.3b. */
.journal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.journal-header h3 { margin: 0; }
.journal-header-actions .tile-compact {
  flex: 0 1 auto;
  min-height: 44px;
  width: auto;
  text-align: left;
  padding: var(--space-2) var(--space-3);
}
.journal-header-actions .tile-compact h3 { font-size: var(--font-sm); }

.tile-badge-count {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 .3rem;
  border-radius: 999px;
  background: var(--tomato);
  color: #fff;
  font-size: .65rem;
  font-weight: 700;
  line-height: 1.1rem;
  text-align: center;
}

/* The icon glyphs themselves (frontend/fonts/icons/README.md) — inherit
   colour and size from CSS like any character, decorative only (the label
   underneath is the accessible name, so these carry aria-hidden). */
.icon {
  font-family: 'SummerhouseIcons';
  font-style: normal;
  font-weight: normal;
  display: block;
  line-height: 1;
}
.icon-requests::before { content: '\e904'; }
.icon-journal::before { content: '\e905'; }
.icon-search::before { content: '\e906'; }
.icon-labels::before { content: '\e90a'; }
.icon-add-task::before { content: '\e90d'; }

/* ---------------------------------------------------------------------
   Accounts band (FINANCE-UX.md §5.2-§5.3, issue #25): ONE line per
   account — name left, balance right, no type column, no stacked-card
   treatment (`responsive-cards` is deliberately not used here, unlike
   people.js's tables).
   Three visually separated PANELS, not section headings inside a table:
   `.account-panel-label`/`.account-row`/`.circulation-row` are flat
   siblings (pager.js has no notion of a wrapping element per page), but
   pager.js marks the first/last rendered item of each contiguous group
   with `pager-group-first`/`pager-group-last` every time it lays out a
   page, so the CSS below can round exactly the top/bottom edge of
   whatever run of same-group rows actually lands on a given page — a
   label always opens its panel (it is always its group's first item) and
   so always gets the rounded top; the closing edge is whichever row is
   visually last on THIS page, even mid-group when a panel spans a page
   break. Whole row opens the account (FINANCE-UX.md §5.4). */
.accounts-list { margin-top: 0; }

.account-panel-label {
  padding: var(--space-3) var(--space-3) var(--space-1);
  background: var(--card);
  color: var(--cream-dim);
  font-weight: 700;
  font-size: var(--font-sm);
  text-transform: uppercase;
  letter-spacing: .04em;
}

.account-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--card);
  border-top: 1px solid var(--line);
  cursor: pointer;
}
.account-row:active { background: var(--card-2); }
.account-row-name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The top edge of whatever's visually first in a panel's run on this page
   — normally the label (always true, a label is always its group's first
   item) but also a bare account row when a panel's continuation opens a
   fresh page after a page break mid-group. Scoped to `.accounts-list`: the
   two classes are pager.js-generic (its own header comment), not
   accounts-specific, so this only claims them within the accounts band. */
.accounts-list .pager-group-first { margin-top: var(--space-4); border-top-left-radius: var(--radius); border-top-right-radius: var(--radius); }
.accounts-list .account-row.pager-group-first { border-top: none; }
/* The bottom edge of whatever's visually last in a panel's run on this
   page — the true end of the group in the common case, or a mid-group row
   right before a page break. */
.accounts-list .pager-group-last { border-bottom-left-radius: var(--radius); border-bottom-right-radius: var(--radius); margin-bottom: var(--space-2); }

/* The circulation row (circulation-row.js): the accounts band's first row,
   the reconciliation folded in (FINANCE-UX.md §5.3) — two lines, unlike
   every other account row, because it carries the last count's state too. */
.circulation-row { flex-direction: column; justify-content: flex-start; align-items: stretch; white-space: normal; }
.circulation-row-main {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  font-family: 'Fraunces', serif;
  font-size: 1.125rem;
  font-weight: 700;
}
.circulation-mark { font-size: var(--font-base); }
.circulation-row-sub { margin-top: var(--space-1); }
.circulation-detail-location { margin-bottom: var(--space-3); }
.circulation-detail-location:last-of-type { margin-bottom: 0; }

/* ---------------------------------------------------------------------
   Account detail (FINANCE-UX.md §5.4): the header is the same one-line
   name/balance shape as the accounts-band row that was tapped, so the row
   visibly becomes the header — then the six-tile bar scoped to this
   account (Search/Back replace Request/Journal), then its history. */
.account-detail-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-1);
  margin-bottom: var(--space-2);
  background: none;
  border-top: none;
  font-family: 'Fraunces', serif;
  font-size: 1.125rem;
  font-weight: 700;
  cursor: default;
}
.account-detail-header:active { background: none; }

/* An archived account's row shades back wherever it lists (2026-08-10 —
   the landing keeps my archived wallets as the way to Reactivate). */
.account-row-inactive { opacity: 0.55; }

/* Ownership badges on event rows (#116): community / mine / assigned —
   a quiet pill; the row's meaning, not a control. */
/* slice 4: the relation GLYPH and the countdown column on event rows */
.event-glyph { flex: none; align-self: center; font-size: 1.05rem; }
.event-countdown { flex: none; min-width: 3.8rem; }

.event-badge {
  flex: none;
  align-self: center;
  font-size: var(--font-sm);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0 var(--space-2);
  color: var(--cream-dim);
}

/* An overdrawn account announces itself ON its row (round 3, 2026-08-10 —
   the FINANCE-UX §2.6 warning banner is gone): a light orange wash, the
   negative pill still carries the number. */
.account-row-overdrawn { background: rgba(224, 122, 61, 0.16); }
.account-row-overdrawn:active { background: rgba(224, 122, 61, 0.28); }

/* The two-card member landing (round 4; split grid 2026-08-10): the
   GRID hands each card half the band — community always starts at the
   50% line; a card sizes to its content within its half and only
   overflow makes the middle section scroll. Header and the TOTALS row
   are fixed. The height mirrors .app-shell-v3's own calc minus the
   band's paddings, so the lower card never pads into the rail; on a
   short viewport the min-height lets the band scroll instead. */
.accounts-list-split {
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: var(--space-2);
  /* the 3rem = #app padding-top (space-2) + padding-bottom (space-4)
     + the band's own padding-block (2 × space-2) */
  height: calc(100dvh - var(--topbar-h, 3.25rem) - var(--rail-h, 3.5rem) - 3rem);
  min-height: 22rem;
}
.account-card {
  display: flex;
  flex-direction: column;
  background: var(--card);
  border-radius: var(--radius);
  overflow: hidden;
  align-self: start;
  max-height: 100%;
  min-height: 0;
}
.account-card-header, .account-card-total { flex: none; }
.account-card-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--line);
  background: var(--card-2);
  font-weight: 700;
}
.account-card-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.4rem;
  padding: var(--space-1) var(--space-3);
}
.account-card-title {
  flex: 1;
  min-width: 0;
  color: var(--cream); /* bold and white, like the totals row (final round) */
  font-weight: 700;
  font-size: var(--font-sm);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.account-card-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* round 5: the landing's list rows read ~15% larger than the shared row */
.account-card-body .account-row {
  padding: calc(var(--space-2) * 1.5) var(--space-3);
  font-size: 1.05em;
}
.account-card-empty { padding: var(--space-2) var(--space-3); }

/* the header controls share the TITLE's row height (round 4) — no taller */
.account-add-toggle, .account-filter-cycle {
  background: none;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  color: var(--cream);
  font-size: var(--font-sm);
  font-weight: 600;
  padding: 0 var(--space-2);
  min-height: 1.7rem;
  flex: none;
}
.account-add-toggle[aria-expanded="true"] { border-style: solid; border-color: var(--tomato); color: var(--tomato); }
/* a header without a title uses a spacer to keep controls at both ends */
.account-card-header .header-spacer { flex: 1; }
.account-add-form {
  text-transform: none;
  letter-spacing: normal;
  font-weight: normal;
  padding: var(--space-1) var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--line);
}

/* The in-card separator (round 4): DIMMED background, bold like the
   header title — direct accounts above, project accounts below. Doubles
   as the LANE label in cards mode. */
.account-panel-separator {
  padding: var(--space-1) var(--space-3);
  background: var(--card-2);
  color: var(--cream-dim);
  font-size: var(--font-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Cards mode (the topbar fold's List/Cards toggle): two-column mini
   cards per type lane, the event task-card look on accounts. */
.account-card-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
}
.account-mini-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  text-align: left;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--cream);
  padding: var(--space-2);
  min-height: 4rem;
  min-width: 0;
}
.account-mini-name {
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* with a state glyph on a FOLD toggle, open/closed needs its own signal
   (reviewer note 2026-08-10) — the open fold tints its button */
.topbar-fold-toggle[aria-expanded="true"] { border-color: var(--tomato); color: var(--tomato); }

/* The WORD Balance is the journal's tile (2026-08-10 round 2): compact,
   the row itself stays a plain header line with the amount pill beside.
   Pressed = the journal is the open surface. */
.balance-tile {
  padding: var(--space-1) var(--space-2);
  background: none;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  color: var(--cream);
  font-family: 'Fraunces', serif;
  font-size: 1.125rem;
  font-weight: 700;
  text-align: left;
}
.balance-tile[aria-pressed="true"] {
  border-style: solid;
  border-color: var(--tomato);
}
.account-surface { margin-top: var(--space-2); }

/* The available-balance line (FINANCE-UX.md §3) — only present when it
   differs from the balance shown just above, so it must not reserve space
   when absent (account-detail.js sets/clears its textContent, never
   toggles the element itself). */
.account-detail-available { margin: calc(-1 * var(--space-1)) var(--space-1) var(--space-2); }
.account-detail-available:empty { margin: 0; }

/* ---------------------------------------------------------------------
   Transaction rows (FINANCE-UX.md §5.5): one line — date, description,
   amount — that expands in place. A <ul>/<li> list rather than a table:
   the expanded detail lives in the same element, no colspan gymnastics. */
.tx-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; }

/* ---------------------------------------------------------------------
   Modal. One at a time (modal.js#openModal enforces that), centered,
   scrollable body on a short viewport, backdrop click / × / Esc all close. */
/* the modal machinery died with #106 — only the statement styles the
   pay form still uses survive, renamed territory for a later polish */
.field-group { display: flex; flex-direction: column; gap: var(--space-1); }
.field-group.hidden { display: none; }
.modal-statement { font-weight: 700; padding: var(--space-2) 0; }
.modal-statement-pending { color: var(--cream-dim); font-weight: 400; }
.modal-statement-direct { color: var(--basil); }
.modal-statement-request { color: var(--semolina); }
.modal-statement-block { color: var(--tomato); }

/* ---------------------------------------------------------------------
   Approve inbox request cards. */
.request-card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}
.request-card-title { font-weight: 700; margin: 0 0 var(--space-1); }
.request-card .inline input { flex: 1; min-width: 8rem; }

/* ---------------------------------------------------------------------
   My requests. */
.request-state { font-weight: 700; text-transform: uppercase; font-size: 0.7rem; letter-spacing: 0.03em; }
.request-state-open { color: var(--semolina); }
.request-state-approved { color: var(--basil); }
.request-state-rejected { color: var(--tomato); }

/* ---------------------------------------------------------------------
   Account table row actions and the pager beneath it. */
.row-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.row-actions button { min-width: 0; }

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-3);
}

/* ---------------------------------------------------------------------
   The dev loop (VISION.md §5 "The dev loop", #45–#47): the Development
   screen, the element picker, and the state-label chips. Everything uses
   the theme tokens above, so both themes come for free. */
.dev-column { display: flex; flex-direction: column; gap: var(--space-3); padding-bottom: var(--space-4); }
.dev-column h2 { font-size: var(--font-lg); margin: var(--space-3) 0 0; }
.dev-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: var(--space-3);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.dev-decision { border-left: 4px solid var(--semolina); }
.dev-ref { font-weight: 700; font-variant-numeric: tabular-nums; }
.dev-question {
  margin: 0; padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--line); color: var(--cream);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.dev-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.dev-counts { margin: 0; }
.dev-issues { display: flex; flex-direction: column; gap: var(--space-1); }
.dev-issue-row {
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%; text-align: left; min-height: 44px;
  background: var(--card); color: var(--cream);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
}
.dev-issue-row .dev-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dev-thread { display: flex; flex-direction: column; gap: var(--space-2); }
.dev-body, .dev-comment > div { white-space: pre-wrap; overflow-wrap: anywhere; }
.dev-comment { border-top: 1px solid var(--line); padding-top: var(--space-2); }
.dev-comment .muted { margin: 0 0 var(--space-1); }

.label-chip {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  padding: 2px 8px; border-radius: 999px; border: 1px solid var(--line);
  color: var(--cream); white-space: nowrap;
}
.label-decision { border-color: var(--semolina); color: var(--semolina); }
.label-blocked { border-color: var(--tomato); color: var(--tomato); }
.label-in-flight { border-color: var(--slate); color: var(--slate); }
.label-groomed { border-color: var(--basil); color: var(--basil); }

/* Attention markers (#49): who must move this next. One per issue, always. */
.attn-operator { border-color: var(--tomato); color: var(--tomato); }
.attn-agent { border-color: var(--basil); color: var(--basil); }
.attn-inflight { border-color: var(--slate); color: var(--slate); }
.attn-triage { border-color: var(--semolina); color: var(--semolina); }

/* The per-service landing (#49) */
.dev-service { text-align: left; width: 100%; cursor: pointer; }
.dev-service h2 { margin: 0; font-size: var(--font-lg); }
.dev-service-counts { margin: 0; display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
.dev-needs-you { color: var(--tomato); font-weight: 700; }
.dev-back { align-self: flex-start; }

.dev-burndown { width: 100%; height: auto; }
.dev-burndown-axis { stroke: var(--line); stroke-width: 1; }
.dev-burndown-opened { fill: var(--semolina); }
.dev-burndown-closed { fill: var(--basil); }

/* The picker (#46): the ◎ control next to the scroll toggle, and the
   hover/tap highlight while armed. The outline sits OUTSIDE the box so
   arming never shifts layout. */
.capture-toggle { min-width: 44px; }
/* A toggle must LOOK toggled (operator feedback 2026-08-05, #48): both
   bottom-nav toggles show their ON state — armed picker, continuous
   scrolling — as border + tinted background. The extra border weight is an
   inset shadow, so switching never shifts layout. Both buttons already
   carry aria-pressed; this is the visual truth of that attribute. */
.capture-toggle[aria-pressed="true"],
.capture-mode { cursor: crosshair; }
.capture-hover { outline: 2px solid var(--tomato); outline-offset: 2px; }
.attn-blocked { border-color: var(--cream-dim); color: var(--cream-dim); }
/* Compact cards (#50): quick info on the screen, everything else in a modal. */
.dev-compact { text-align: left; width: 100%; cursor: pointer; }
.dev-compact .dev-preview { margin: 0; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.dev-progress { height: 6px; border-radius: 999px; background: var(--crust-2); overflow: hidden; }
.dev-progress-fill { height: 100%; background: var(--basil); }

/* The quickbar (#51): expand tile, catalog panel, customize numbering. */
.quickbar { display: flex; flex-direction: column; gap: var(--space-1); margin: var(--space-3) 0 var(--space-4); }
/* Inside the quickbar the rows sit tight: the grid's own outer margins would
   otherwise stack a huge gap between the bar and its extension. */
.quickbar .quick-actions { margin: 0; }
/* Expanded = one visible group: border + lighter surface around bar AND
   extension together (operator feedback, 2026-08-05). */
.quickbar.quickbar-expanded {
  border: 1px solid var(--line);
  background: var(--card-2);
  border-radius: var(--radius);
  padding: var(--space-1);
}
.quickbar-glyph { font-family: inherit; font-weight: 700; }
.quickbar-expand.quickbar-open { border-color: var(--tomato); color: var(--tomato); }
/* The expanded catalog is a seamless second row of the same grid — no box,
   no border, just more of the bar (#51 design feedback). The panel chrome
   below is reserved for customize mode, which needs its hint and actions. */
.quickbar-pick { position: relative; }
.quickbar-picked { border-color: var(--basil); box-shadow: inset 0 0 0 1px var(--basil); }

/* The view baseline (VISION "The view model", #87): on a screen whose
   quickbar names views, the CURRENT view's tile is marked — the bar always
   answers "where am I". Same accent treatment as an open expand tile, plus
   the inset ring so it reads as pressed-in rather than merely tinted. */

/* Inline two-step confirm (confirm-button.js): the armed state must look
   unmistakably "about to do the thing". */
button.confirm-armed { background: var(--tomato); color: #fff; border-color: transparent; }

/* The table primitive (data-table.js, #92): headers are CONTROLS — they
   sort/filter on tap — so they read tappable; the open one is marked like
   an open quickbar tile. Rows that descend get the row-hover treatment. */
.data-table table { margin-top: var(--space-2); }
.data-table th[role="button"] { cursor: pointer; user-select: none; }
.data-table .data-table-th-open { color: var(--tomato); }
.data-table .data-table-search-th { text-align: right; width: 2.5rem; }
.data-table tbody tr[role="button"] { cursor: pointer; }
.data-table tbody tr[role="button"]:active { background: var(--card-2); }
.data-table-controls { margin: var(--space-2) 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
.data-table-search input { width: 100%; }
.quickbar-pick .tile-badge-count { background: var(--basil); }

/* The front door grid (#53): two uniform columns, fixed height, no prose.
   The row carries its own spacing INSIDE its box so the pager's height sum
   is exact — grid gaps outside item boxes were the static-scroll bug. */
.tile-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
  padding-bottom: var(--space-2);
}
.tile-service {
  height: 104px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  text-align: center;
  position: relative;
}
.tile-service h3 { margin: 0; font-size: var(--font-base); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.tile-icon { font-size: 1.6rem; line-height: 1; }
.tile-soon {
  font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--cream-dim); border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px;
}
.tile-service .tile-badge-count { position: absolute; top: var(--space-2); right: var(--space-2); }

/* help mode ON: everything with a help modal glows; tapping opens it */
.help-mode .tile-service { outline: 2px solid var(--semolina); outline-offset: -2px; cursor: help; }

.tiles-modal-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); }
.more-panel { display: flex; flex-direction: column; gap: var(--space-3); }

/* Reload progress (#54): the glyph spins while the refresh runs. */
@keyframes reload-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.reload-button.reloading {
  color: var(--tomato); border-color: var(--tomato);
  animation: reload-spin 0.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .reload-button.reloading { animation: none; background: var(--card-2); box-shadow: inset 0 0 0 1px var(--tomato); }
}

/* Picker armed (#54): like help mode, FRAME what is touchable while the
   picker is live — the common tappable sections get a dashed outline so
   the capture surface is visible before you hover it. */
.capture-mode :is(.tile, .dev-card, .dev-issue-row, .accounts-list > *, .quick-actions .tile) {
  outline: 1px dashed var(--semolina);
  outline-offset: -1px;
}

/* Unmissable failure (#71): the password modal's error is not a whisper. */
.error-loud:not(:empty) {
  font-weight: 700;
  border: 1px solid var(--tomato);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
  background: var(--card-2);
}

/* Options-as-tiles (#81): the per-account option row and its extension.
   Pick rows are full-width tappable buttons — no <select> on phones. */
.option-tiles { margin: var(--space-2) 0; }
.option-pick-row {
  text-align: left;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
}
.option-pick-row[aria-pressed="true"] { border-color: var(--tomato); }

/* ---------------------------------------------------------------------
   Shell v3 (#107, VISION "Shell v3 — the rail takes the helm"): the
   content column SCROLLS between the slim topbar and the fixed rail; the
   rail is the app's one navigation, byte-identical everywhere. */
.topbar-slim .topbar-chrome-hidden { display: none; }
.topbar-slim .topbar-headline { text-align: center; }
/* round 6: the slim bar sits tighter, and the toggle matches the
   TITLE's line — no chunky pill towering over it */
.topbar-slim .topbar-row {
  padding-top: calc(var(--space-1) + var(--safe-top));
  padding-bottom: var(--space-1);
}
.topbar-slim .topbar-fold-toggle {
  min-height: 2rem;
  min-width: 2.4rem;
  padding: 0 var(--space-2);
  font-size: 1rem;
}

/* the view-owned header (refinement 2026-08-08): a view may register a
   fold-DOWN section — the rail's mirror. The toggle exists only then. */
.topbar-fold-toggle { min-width: 1.8rem; }
.topbar-fold-toggle.hidden { display: none; }
.topbar-fold {
  border-top: 1px solid var(--line);
  padding: var(--space-2) var(--space-3);
  max-height: 40dvh;
  overflow-y: auto;
}
.topbar-fold.hidden { display: none; }

.app-shell-v3 {
  /* the page scrolls — the v2 fixed-band regime does not apply here */
  overflow-y: auto;
  height: calc(100dvh - var(--topbar-h, 3.25rem) - var(--rail-h, 3.5rem));
  padding-bottom: var(--space-4);
}
.shell-v3-content { padding: var(--space-2) var(--space-3); }

.rail {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  background: var(--card);
  border-top: 1px solid var(--line);
  z-index: 30;
  padding-bottom: env(safe-area-inset-bottom, 0);
}
/* geometry take 2 (2026-08-09): a STATIC row of seven distinct buttons —
   fixed columns, so a changing view name never moves the arrows; the
   centre reads as a toggle, the arrows as plain steppers, Back its own. */
.rail-row {
  display: grid;
  grid-template-columns: 2.75rem 2.75rem 2.5rem minmax(0, 1fr) 2.5rem 2.75rem 2.75rem;
  gap: var(--space-1);
  align-items: stretch;
  padding: var(--space-1) var(--space-2);
  height: var(--rail-h, 3.5rem);
  box-sizing: border-box;
}
.rail-btn {
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--cream);
  font-size: 1.05rem;
  padding: var(--space-1);
  min-width: 0;
}
.rail-btn:disabled { opacity: 0.35; }
/* a nav lock dims but keeps controls tappable — the tap speaks the reason */
.rail [aria-disabled="true"] { opacity: 0.35; }
/* Back is UPWARD (or clears a draft) — its own weight, never an arrow's twin */
.rail-back {
  background: var(--card);
  font-weight: 700;
  font-size: 1.15rem;
}
/* ◀▶: plain steppers */
.rail-arrow { border-style: dashed; font-size: 0.85rem; }
/* the centre: a TOGGLE — own fill, the caret says it opens */
.rail-views {
  display: flex;
  align-items: center;
  gap: 4px;
  background: var(--card-2);
  font-size: var(--font-sm);
  font-weight: 700;
  padding: var(--space-1) var(--space-2);
  overflow: hidden; /* below ~344px the caret clips instead of bleeding out */
}
.rail-views-label {
  flex: 1 1 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
  min-width: 0;
}
.rail-views-caret { flex: none; opacity: 0.7; font-size: 0.8em; }
.rail-notice { margin: 0; padding: 0 var(--space-3); }
.rail-notice:empty { display: none; }
/* clearing a dirty transaction confirms HERE, in the bar — never a modal */
.rail-confirm {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  padding: 0 var(--space-3);
  font-size: var(--font-sm);
}
.rail-confirm:empty { display: none; }
.rail-confirm-discard { color: var(--tomato); border-color: var(--tomato); }

/* the fold: bar chrome expanding upward — the view picker, the current
   view marked; a utility row beneath. Part of the rail, never a modal. */
.rail-fold {
  border-top: 1px solid var(--line);
  padding: var(--space-2) var(--space-3);
  max-height: 45dvh;
  overflow-y: auto;
}
.rail-fold.hidden { display: none; }
.rail-view-grid {
  display: grid;
  /* minmax(0,…): 1fr's implicit min is the tile's min-content, and a
     long account label (the sibling cycle, 2026-08-10) pushed the grid
     past the viewport — force the tracks to actually share the width
     and let the labels ellipsize */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2);
}
/* take 2: the FOLD slims, not the bar — glyph and label side by side;
   sized up 40-50% on the operator's 0.69.0 check (2026-08-09) */
.rail-view-tile {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--cream);
  font-size: 1rem;
  font-weight: 600;
  padding: var(--space-2);
  min-height: 2.75rem;
  position: relative;
  min-width: 0; /* let the tile shrink to its track so the label ellipsizes */
}
.rail-view-glyph { font-size: 1.4rem; flex: none; }
/* TEXT glyphs (emoji) opt out of the icon font; icon-class glyphs keep it
   (the font-family:inherit here on the shared class ate the finance icons
   on 0.70.0 — operator report 2026-08-09) */
.rail-view-glyph-text { font-family: inherit; }
.rail-view-label {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}
/* in the slim row the customize badge flows at the row's end, not absolute */
.rail-view-tile .tile-badge-count { position: static; margin-left: auto; flex: none; }
.rail-view-current { border-color: var(--tomato); box-shadow: inset 0 0 0 1px var(--tomato); color: var(--tomato); }
/* MORE than six views: the tile area becomes a horizontal slider —
   swipe, scroll-snap; never a taller fold, never truncation */
.rail-view-slider {
  grid-template-columns: unset;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, auto);
  grid-auto-columns: calc((100% - 2 * var(--space-2)) / 3);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
.rail-view-slider .rail-view-tile { scroll-snap-align: start; }
/* the utility row rides the tiles' three-column grid (take 2) */
.rail-util-row {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--line);
}
.rail-util { font-size: 1rem; min-height: 2.5rem; padding: var(--space-2); min-width: 0; }

/* the styling CONTRACT (VISION shell v3): a view's own actions must never
   read as view tiles — inside a v3 shell the option tiles flatten into
   outlined action buttons, clearly of the content, not the chrome. */
.app-shell-v3 .option-tiles .tile {
  flex-direction: row;
  min-height: 0;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-style: dashed;
}
.app-shell-v3 .option-tiles .tile .icon { font-size: 1rem; }
.app-shell-v3 .option-tiles .tile h3 { font-size: var(--font-sm); }

/* Tasks are cards (#108): modes over the same tasks; the card grid and
   the agenda calendar share the card */
.task-mode-bar { display: flex; gap: var(--space-1); margin: var(--space-2) 0; }
.task-mode-btn { font-size: var(--font-sm); min-height: 0; padding: var(--space-1) var(--space-2); }
.task-mode-btn[aria-pressed="true"] { border-color: var(--tomato); color: var(--tomato); }
.task-card-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
  margin: var(--space-2) 0;
}
.task-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  text-align: left;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--cream);
  padding: var(--space-2);
  min-height: 4.5rem;
}
.task-card-done { opacity: 0.6; }
.task-card-text { font-weight: 600; }
.task-card-when, .task-card-money { font-size: var(--font-sm); }
.task-calendar-day { margin: var(--space-3) 0 var(--space-1); }
.task-text-link { cursor: pointer; text-decoration: underline dotted; }
.task-view-done { display: inline-flex; align-items: center; gap: var(--space-1); }

/* The view baseline (#112): the action grid — a view's actions on the
   fold's three columns; member rows, then the admin tier; the extension
   opens beneath. Action tiles stay dashed in-band controls, never view
   tiles. */
.action-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  margin: var(--space-2) 0;
}
.action-grid-admin { border-top: 1px dashed var(--line); padding-top: var(--space-2); }
.action-tile {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-1);
  background: none;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  color: var(--cream);
  font-size: var(--font-sm);
  font-weight: 600;
  padding: var(--space-1) var(--space-2);
  min-height: 2.5rem;
  text-align: left;
}
.action-tile[aria-disabled="true"] { opacity: 0.5; }
.action-tile-open, .action-tile[aria-pressed="true"], .account-filter-tile[aria-pressed="true"] {
  border-style: solid;
  border-color: var(--tomato);
  color: var(--tomato);
}
.action-tile-glyph { font-size: 1.1rem; flex: none; }
.action-tile-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.action-grid-notice:empty { display: none; }

/* the breadcrumb topbar (#112): half the bar is the path, half the name */
.topbar-path {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: var(--font-sm);
  opacity: 0.75;
  overflow: hidden;
  white-space: nowrap;
  flex: 1 1 50%;
  min-width: 0;
}
.topbar-path:empty { display: none; }
.topbar-path-level { overflow: hidden; text-overflow: ellipsis; }
.topbar-path-chevron { opacity: 0.6; flex: none; }
/* right-align the name ONLY beside a non-empty path — every pathless
   screen keeps its centred headline (review baseline F1) */
.topbar-path:not(:empty) ~ .topbar-headline { flex: 1 1 50%; text-align: right; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* the journal spine (#112): rows expand in place */
.tx-journal-row { cursor: pointer; }
.tx-journal-detail {
  padding: var(--space-1) var(--space-3) var(--space-2);
  border-left: 2px solid var(--line);
  margin-left: var(--space-2);
  font-size: var(--font-sm);
}
.expense-withdrawn { opacity: 0.55; }

/* account v2 (operator 2026-08-09): full-bleed tables — the padding lives
   in the cells; rows are clean, the expansion owns detail and acts */
.shell-v3-content .full-bleed { margin-inline: calc(-1 * var(--space-3)); width: calc(100% + 2 * var(--space-3)); }
.full-bleed .task-row { padding-inline: var(--space-3); }
.expense-table-row { cursor: pointer; }
.expense-detail {
  list-style: none;
  padding: var(--space-1) var(--space-3) var(--space-2);
  border-left: 2px solid var(--line);
  margin-left: var(--space-2);
  font-size: var(--font-sm);
}

/* account tables v3 (operator 2026-08-09): REAL columns, viewport-wide,
   the padding in the cells; the expansion row carries the structure */
.account-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-sm);
}
.account-table th {
  text-align: left;
  font-weight: 600;
  opacity: 0.7;
  padding: var(--space-1) var(--space-2);
  border-bottom: 1px solid var(--line);
}
.account-table td { padding: var(--space-2); border-bottom: 1px solid var(--line); vertical-align: top; }
.account-table th:first-child, .account-table td:first-child { padding-left: var(--space-3); }
.account-table th:last-child, .account-table td:last-child { padding-right: var(--space-3); }
.account-table-amount { text-align: right; white-space: nowrap; }
.account-table-when { white-space: nowrap; }
.expense-detail-row td { background: var(--card-2); }
.expense-state-open td { font-weight: 600; }

/* journal round 4 (operator 2026-08-09): TYPE is a letter column, STATE is
   colour, amounts are PILLS that stand off the background */
.journal-header { display: flex; align-items: center; gap: var(--space-2); }
.journal-header h3 { margin-right: auto; }
.journal-tool { font-size: var(--font-sm); min-height: 0; padding: var(--space-1) var(--space-2); }
.journal-tool[aria-pressed="true"] { border-color: var(--tomato); color: var(--tomato); }
.journal-search-row { margin: var(--space-2) 0; }
.journal-search-row input { width: 100%; }
.journal-filter-panel { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; margin: var(--space-2) 0; }
.journal-filter-toggle { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--font-sm); }
.journal-member-list { flex-basis: 100%; display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-1); }
.type-letter { font-weight: 700; font-size: var(--font-sm); opacity: 0.85; }
.type-claim { color: var(--tomato); }
.type-external { color: var(--cream); opacity: 0.6; }
.type-in { color: #7bc47f; }
.type-out { color: var(--tomato); }
.amount-pill {
  display: inline-block;
  padding: 2px var(--space-2);
  border-radius: 999px;
  font-weight: 600;
  white-space: nowrap;
}
.amount-pos { background: rgba(123, 196, 127, 0.16); color: #8fd693; }
.amount-neg { background: rgba(226, 91, 66, 0.16); color: var(--tomato); }
.amount-open { background: rgba(255, 255, 255, 0.08); }
.amount-muted { background: rgba(255, 255, 255, 0.05); opacity: 0.7; }
.amount-rejected { background: rgba(255, 255, 255, 0.05); opacity: 0.55; text-decoration: line-through; }

/* r5: the two cycling balance pills; settled rows shade back */
.balance-pills { display: flex; gap: var(--space-2); margin: var(--space-2) 0; }
/* r6: each pill takes HALF the row */
.balance-pill { border: none; font-size: var(--font-sm); cursor: pointer; flex: 1 1 50%; min-width: 0; text-align: center; }
.expense-settled td { opacity: 0.65; }

/* r7: state is its own icon column; ACTIVE rows (open / part-booked)
   carry a lighter background so decidable work stands out */
.state-icon { font-size: var(--font-sm); }
.state-open, .state-part-booked { filter: none; }
.expense-active td { background: rgba(255, 235, 205, 0.06); }
.expense-active .account-table-what { font-weight: 600; }

/* r8: the expansion's design pass — the stamp under a THICK rule, the
   bookings table without a title, half-width acts, the green-ready Book */
.expense-detail-stamp {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding-bottom: var(--space-1);
  border-bottom: 3px solid var(--line);
  margin-bottom: var(--space-2);
}
.expense-detail-when { font-weight: 700; font-size: var(--font-base); }
.expense-detail-desc { margin: 0 0 var(--space-2); font-weight: 600; }
.booking-table { width: 100%; border-collapse: collapse; margin-bottom: var(--space-2); }
.booking-table td { padding: var(--space-1) var(--space-2); border-bottom: 1px solid var(--line); }
.booking-summary-row td { font-weight: 700; }
.booking-amount { color: var(--tomato); }
.booking-reason-row td { border-bottom: 1px solid var(--line); font-style: italic; }
.expense-act-buttons { display: flex; gap: var(--space-2); margin-top: var(--space-1); }
.expense-act-half { flex: 1 1 50%; min-width: 0; }
.expense-act-process { display: flex; flex-direction: column; gap: var(--space-1); margin-top: var(--space-1); }
.expense-act-row { display: flex; gap: var(--space-1); }
.expense-act-row input { flex: 1 1 auto; min-width: 0; }
.book-button.book-ready { background: rgba(123, 196, 127, 0.25); border-color: #7bc47f; color: #8fd693; }
/* r11: Clear rides the act row at full row height — no more offset */

/* r9: entity pills go places; the receipt seat waits on the right; the
   act rows settle — one row for approve, a 50/50 for reject */
.entity-pill {
  display: inline-block;
  background: rgba(255, 255, 255, 0.08);
  border: none;
  border-radius: 999px;
  color: var(--cream);
  font-size: var(--font-sm);
  padding: 2px var(--space-2);
  min-height: 0;
  cursor: pointer;
}
.expense-detail-head { display: flex; gap: var(--space-2); align-items: flex-start; }
.expense-detail-headmain { flex: 1 1 auto; min-width: 0; }
.expense-detail-receipt { flex: 0 0 3rem; }
.expense-act-buttons.hidden { display: none; }
.expense-act-row-half > * { flex: 1 1 50%; }

/* r10: the act row CONTAINS itself — the input shrinks, buttons compact;
   the receipt seat gets its frame; task pills grow light-blue; the
   selected row outshines its table */
.expense-act-row { flex-wrap: nowrap; align-items: stretch; }
.expense-act-row input { flex: 1 1 0; width: 0; min-width: 0; }
.expense-act-row button {
  flex: 0 0 auto;
  min-height: 0;
  font-size: var(--font-sm);
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}
.expense-act-row-half { flex-wrap: nowrap; }
.expense-act-row-half > button { flex: 1 1 50%; }
.expense-detail-receipt {
  flex: 0 0 3rem;
  height: 3rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}
.task-pill {
  font-size: calc(var(--font-sm) * 1.2);
  background: rgba(110, 160, 255, 0.18);
  color: #a8c7ff;
}
.tx-journal-row.row-selected td, .expense-table-row.row-selected td {
  background: rgba(255, 235, 205, 0.12);
}
.row-selected .account-table-what { font-weight: 700; }

/* r11: the label row on the three-column sizing — room for three labels */
.entity-pill-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2); margin: var(--space-2) 0; }
.entity-pill-row .task-pill { width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rejection-row td { opacity: 0.8; }

/* #126: the lookup's card chrome, status pills and inline act forms */
.account-lookup-fold { display: flex; gap: var(--space-2); padding: var(--space-2); align-items: center; flex-wrap: wrap; }
/* the cascade lesson (#127): this rule comes AFTER .hidden, so the fold
   needs its own override or it ships expanded — render-check cannot see
   computed display (no layout engine), only this source line can */
.account-lookup-fold.hidden { display: none; }
.account-lookup-fold input[type="search"] { flex: 1; min-width: 0; }
.account-lookup-pills { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.account-lookup-pills .amount-pill { margin-left: auto; } /* the balance right-aligns (#127) */
.tile-compact[aria-pressed="true"] { border-color: var(--tomato); color: var(--tomato); }
.field-row { display: flex; gap: var(--space-2); align-items: center; }
.field-row > * { flex: 1; min-width: 0; }
.field-row .form-balance { flex: 0 0 auto; }
.act-compact-form { margin-top: var(--space-2); }
.label-field { display: flex; flex-direction: column; }
.label-chip-row, .label-pill-row { display: flex; flex-wrap: wrap; gap: 4px; }
.label-chip-row { order: -2; } /* visually ON TOP, DOM after the input (tab order) */
.label-pill-row { order: -1; }
.label-chip-row { margin-bottom: 4px; }
.label-pill-row:not(:empty) { margin-bottom: 4px; }
.label-chip, .label-pill { font-size: 0.75rem; padding: 1px 8px; border-radius: 999px; background: none; color: inherit; cursor: pointer; }
.label-chip { border: 1px dashed; opacity: 0.8; }
.label-chip[aria-pressed="true"] { border-style: solid; opacity: 1; }
.label-pill { border: 1px solid; }

/* #23: People — the address book on the lookup baseline */
.people-filter-fold { flex-direction: column; align-items: stretch; }
.people-filter-row { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }

/* #128: active toggles read ORANGE; the folds read as raised, divided
   sections — shared by the lookup and People (the fold classes are one
   family) */
.account-card-header .account-filter-cycle[aria-expanded="true"] {
  border-color: var(--tomato);
  color: var(--tomato);
}
.label-chip[aria-pressed="true"] { border-color: var(--tomato); color: var(--tomato); }
.account-lookup-fold:not(.hidden) {
  background: rgba(255, 235, 205, 0.08);
  border-radius: var(--radius);
  border-bottom: 1px solid rgba(255, 235, 205, 0.18);
  margin: 0 var(--space-1) var(--space-1);
}

/* #129: the one-shown login secret — unmissable, never subtle */
.person-minted { border: 1px solid var(--tomato); border-radius: var(--radius); padding: var(--space-2); margin: var(--space-1) 0; }
.person-minted code { user-select: all; }

/* #123: the community home — the collections pattern + the fund headline */
.collection-header { cursor: pointer; }
.collection-header .collection-total { margin-left: auto; }
.collection-card { margin-top: var(--space-1); }
.community-fund-headline { font-weight: 700; }
.community-desk-filters { padding: var(--space-1) 0; }
