/* =============================================================================
 * styles.css
 *
 * Three parts, in this order:
 *   1. the tokens, from build.mjs DARK (5-12), LIGHT (13-20), vars() (21) and
 *      the four deep spare hues out of READY (29-43)
 *   2. BASE_CSS, from build.mjs 574-622, ported rule for rule
 *   3. the component CSS: the design's inline styles, lifted out of the
 *      component functions into the class names those functions keep
 *
 * THEMES. The design paints a wrapper `.theme-dark` or `.theme-light`, because
 * an artboard is told which one to draw. The app has no such prop, so:
 *   :root                                      dark, the base
 *   @media (prefers-color-scheme: light)       follow the system, unless a
 *     :root:not([data-theme="dark"])           choice was made
 *   [data-theme="light"] / [data-theme="dark"] the Appearance tile's choice,
 *                                              which wins in both directions
 * state.js writes the attribute and remembers it per browser (SPEC.md 0).
 * ========================================================================== */

/* ============================================================ 1. the tokens */

:root {
  color-scheme: dark;
  /* build.mjs:5-12, DARK */
  --bg: #0d1013; --surface: #15191c; --surface-2: #1e2226;
  --line: #2e3339; --line-soft: #232830;
  --text: #f3f2ee; --text-2: #a6abb1; --text-3: #757b81;
  --ok: #41dca5; --ok-bg: rgba(65,220,165,0.12);
  --due: #fdca3a; --due-bg: rgba(253,202,58,0.12); --due-ink: #fdca3a;
  --danger: #ff655a; --danger-ink: #0d1013;
  --btn-bg: var(--ok); --btn-text: var(--bg);
  --c-cell: #00aaab; --c-ins: #d17746; --c-util: #8c7dd5; --c-net: #cf6872;
  --c-ent: #348dcf; --c-fit: #909b2f; --c-soft: #c96fa4; --c-shop: #48a260;
  --c-gov: #b78512; --c-other: #757b81;
  /* build.mjs:39-42, the four deep spares. At the main lightness the hue wheel
     is full at nine, so these step deeper (L 0.50 instead of 0.65). */
  --c-rust: #a54a24; --c-sea: #007a59; --c-plum: #874e99; --c-olive: #636a00;
  /* build.mjs:604 carries this shadow inline with a light-theme override; it
     is a token here so the rule below can stay a single rule. */
  --shadow-seg: 0 1px 2px rgba(0,0,0,0.25);
  --shadow-pop: 0 8px 24px rgba(0,0,0,0.35);
  --shadow-menu: 0 16px 40px rgba(0,0,0,0.35);
  --shadow-sheet: 0 -16px 40px rgba(0,0,0,0.4);
  --shadow-dialog: 0 24px 60px rgba(0,0,0,0.5);
  --scrim: rgba(0,0,0,0.55);
}

/* build.mjs:13-20, LIGHT. One list, written once, referenced twice below. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;
    --bg: #f8f7f4; --surface: #ffffff; --surface-2: #efeeea;
    --line: #d9d7d3; --line-soft: #e6e4df;
    --text: #0e1216; --text-2: #50565c; --text-3: #757b81;
    --ok: #00885e; --ok-bg: rgba(0,136,94,0.10);
    --due: #b07100; --due-bg: rgba(176,113,0,0.10); --due-ink: #8a5800;
    --danger: #a50210; --danger-ink: #f8f7f4;
    --btn-bg: var(--ok); --btn-text: var(--bg);
    --c-cell: #009294; --c-ins: #c86830; --c-util: #6a57b3; --c-net: #be525f;
    --c-ent: #006eb3; --c-fit: #7e8900; --c-soft: #b2548d; --c-shop: #1c8742;
    --c-gov: #a16c00; --c-other: #757b81;
    --c-rust: #953400; --c-sea: #006c4b; --c-plum: #77398a; --c-olive: #565c00;
    --shadow-seg: 0 1px 2px rgba(0,0,0,0.10);
  }
}

[data-theme="light"] {
  color-scheme: light;
  --bg: #f8f7f4; --surface: #ffffff; --surface-2: #efeeea;
  --line: #d9d7d3; --line-soft: #e6e4df;
  --text: #0e1216; --text-2: #50565c; --text-3: #757b81;
  --ok: #00885e; --ok-bg: rgba(0,136,94,0.10);
  --due: #b07100; --due-bg: rgba(176,113,0,0.10); --due-ink: #8a5800;
  --danger: #a50210; --danger-ink: #f8f7f4;
  --btn-bg: var(--ok); --btn-text: var(--bg);
  --c-cell: #009294; --c-ins: #c86830; --c-util: #6a57b3; --c-net: #be525f;
  --c-ent: #006eb3; --c-fit: #7e8900; --c-soft: #b2548d; --c-shop: #1c8742;
  --c-gov: #a16c00; --c-other: #757b81;
  --c-rust: #953400; --c-sea: #006c4b; --c-plum: #77398a; --c-olive: #565c00;
  --shadow-seg: 0 1px 2px rgba(0,0,0,0.10);
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0d1013; --surface: #15191c; --surface-2: #1e2226;
  --line: #2e3339; --line-soft: #232830;
  --text: #f3f2ee; --text-2: #a6abb1; --text-3: #757b81;
  --ok: #41dca5; --ok-bg: rgba(65,220,165,0.12);
  --due: #fdca3a; --due-bg: rgba(253,202,58,0.12); --due-ink: #fdca3a;
  --danger: #ff655a; --danger-ink: #0d1013;
  --btn-bg: var(--ok); --btn-text: var(--bg);
  --c-cell: #00aaab; --c-ins: #d17746; --c-util: #8c7dd5; --c-net: #cf6872;
  --c-ent: #348dcf; --c-fit: #909b2f; --c-soft: #c96fa4; --c-shop: #48a260;
  --c-gov: #b78512; --c-other: #757b81;
  --c-rust: #a54a24; --c-sea: #007a59; --c-plum: #874e99; --c-olive: #636a00;
  --shadow-seg: 0 1px 2px rgba(0,0,0,0.25);
}

/* ============================================================ 2. BASE_CSS
 * build.mjs:574-622. The design applies these under `.app`; the app is the
 * whole page, so they hang off body.
 * ========================================================================= */

html { background: var(--bg); }
body {
  margin: 0;
  font-family: 'Instrument Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  font-size: 15px;
  line-height: 1.4;
  box-sizing: border-box;
  min-height: 100dvh;
}
*, *::before, *::after { box-sizing: border-box; }

a { color: inherit; text-decoration: none; }
/* UNIT 5 TOOK `color: inherit` OFF THE HOVER RULE, and it is a fix rather than
   a taste. build.mjs:576 carries both, which costs a static artboard nothing.
   In a live page `a:hover` is specificity 0-0-1-1 and beats `.btn`'s 0-0-1-0,
   so hovering ANY anchor drawn as a button re-inherited the body's text colour
   and painted near-white text on a near-white button: the label vanished under
   the cursor. The rule above already stops the browser's blue, so the hover
   rule never needed to say it twice. Found at 375 on the Categories footer,
   where the pointer happens to rest on the button after a tap. */
a:hover { text-decoration: underline; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }

.num { font-family: 'Azeret Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-variant-numeric: tabular-nums lining-nums; letter-spacing: -0.02em; }
.fig { font-variant-numeric: lining-nums; }
.eyebrow { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-3); font-weight: 600; }
.h1 { font-size: 28px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; }
.muted { color: var(--text-2); }
.faint { color: var(--text-3); }
.strong { font-weight: 600; }
.ok-ink { color: var(--ok); display: inline-flex; }
.due-ink { color: var(--due-ink); font-weight: 500; }
.card { background: var(--surface); border: 1px solid var(--line-soft); border-radius: 10px; }
/* THE PRIMARY BUTTON IS MINT, app wide, his call on 2026-09-15 after seeing it
   beside the rest. The design drew it as the inverse of the page, and on dark
   that near-white block was the loudest thing on screen, pulling the eye off
   the figures the app exists to show.

   It is written as var(--ok) rather than a hex so it follows the theme by
   itself: bright mint on dark with near-black text, deep green on light with
   near-white text, the same pair the tick marks and the avatars use. */
.btn { display: inline-flex; align-items: center; gap: 8px; height: 40px; padding: 0 16px; border-radius: 8px; font-weight: 600; font-size: 14px; background: var(--btn-bg); color: var(--btn-text); white-space: nowrap; }
.btn:hover { text-decoration: none; }
.btn-ghost { display: inline-flex; align-items: center; gap: 8px; height: 40px; padding: 0 14px; border-radius: 8px; font-weight: 600; font-size: 14px; background: transparent; color: var(--text); border: 1px solid var(--line); white-space: nowrap; }
.btn-ghost:hover { text-decoration: none; }
.iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 8px; color: var(--text-2); border: 1px solid var(--line-soft); background: var(--surface); }
/* The top bar's Settings gear, in the brand's mint (his call, 2026-09-19). The
   icon strokes in currentColor, so the colour goes on the button. Written
   after .iconbtn on purpose: same weight, so the later rule wins. */
.settings-btn { color: var(--ok); }
.tab { height: 36px; display: inline-flex; align-items: center; padding: 0 12px; border-radius: 8px; font-weight: 500; color: var(--text-2); }
.tab.on { color: var(--text); background: var(--surface-2); font-weight: 600; }
.chip { display: inline-flex; align-items: center; gap: 8px; height: 34px; padding: 0 12px; border-radius: 6px; border: 1px solid var(--line); color: var(--text-2); font-size: 13px; font-weight: 500; white-space: nowrap; }
.chip.on { background: var(--text); color: var(--bg); border-color: var(--text); }
.chip .n { opacity: 0.7; font-weight: 600; }
.dot { width: 8px; height: 8px; border-radius: 2px; flex: none; display: inline-block; }
.pill { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 8px; border-radius: 6px; font-size: 12px; font-weight: 600; white-space: nowrap; }
.pill-ok { background: var(--ok-bg); color: var(--ok); }
.pill-due { background: var(--due-bg); color: var(--due-ink); }
.pill-plain { background: var(--surface-2); color: var(--text-2); }
.pill-year { border: 1px solid var(--line); color: var(--text-2); }
.ring { width: 8px; height: 8px; border-radius: 2px; border: 2px solid var(--due); flex: none; display: inline-block; }
.cents { font-size: 0.5em; font-weight: 500; color: var(--text-2); letter-spacing: 0; }
.field { display: flex; flex-direction: column; gap: 6px; }
.lbl { font-size: 13px; font-weight: 600; color: var(--text-2); gap: 4px; }
.input { height: 48px; display: flex; align-items: center; gap: 10px; padding: 0 14px; border-radius: 8px; border: 1px solid var(--line); background: var(--surface); font-size: 16px; color: var(--text); width: 100%; text-align: left; }
.input:focus, .input.focus, .input:focus-within { border-color: var(--text); box-shadow: 0 0 0 3px var(--surface-2); outline: none; }
.hint { font-size: 12.5px; color: var(--text-3); line-height: 1.4; }
/* build.mjs:608. A caveat that runs to a second line stays readable as a
   block rather than reflowing around whatever sits beside it. */
.tipline { display: block; line-height: 1.6; }
.seg { display: flex; padding: 3px; border-radius: 8px; background: var(--surface-2); gap: 2px; }
.seg > div { flex: 1; height: 42px; display: flex; align-items: center; justify-content: center; gap: 8px; border-radius: 6px; font-weight: 600; font-size: 14px; color: var(--text-2); }
.seg > div.on { background: var(--surface); color: var(--text); box-shadow: var(--shadow-seg); }
.row { display: flex; align-items: center; }
.tick { width: 8px; height: 8px; border-radius: 2px; flex: none; display: inline-block; }
.hatch { background-image: repeating-linear-gradient(135deg, currentColor 0 2px, transparent 2px 5px); opacity: 0.75; }

/* build.mjs:610 and 613. The four tab bar. The design draws it inside a
   390x880 frame with position: absolute; the app runs at the size of the
   screen, so it is fixed to the bottom edge and pushed clear of the home
   indicator. THE 44px MINIMUM IS THE RULE, not the decoration (SPEC.md 5). */
.tab-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 4;
  height: 86px; padding: 8px 8px max(30px, env(safe-area-inset-bottom));
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px;
  background: var(--surface); border-top: 1px solid var(--line-soft);
}
.tab-bar > * { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; font-size: 11px; font-weight: 600; color: var(--text-3); border-radius: 8px; min-height: 44px; }
.tab-bar > *.on { color: var(--text); }
.tab-bar > *:hover { text-decoration: none; }

@keyframes rise { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } }
.rise { animation: rise 420ms cubic-bezier(.2,.7,.2,1) both; }
@media (prefers-reduced-motion: reduce) { .rise { animation: none; } }

/* =================================================== 3. the app's own shell
 * One markup tree, responsive by CSS (SPEC.md section 5). The desktop bar
 * appears at 900px and the phone bars go away; nothing re-renders.
 * ========================================================================= */

#app { display: flex; flex-direction: column; min-height: 100dvh; }
/* router.js shell(): one fresh wrapper per render, so a screen's listeners die
   with the screen. It must not be a box of its own. */
.app-tree { display: contents; }

/* THE LOADING SIGN (router.js, waitStart). A line along the top edge while a
   screen is being fetched, shown only once that has taken longer than a
   moment. MINT: it is the app taking care of something, and it asks nothing of
   the person, which is the test amber fails (WHAT AMBER MEANS, above). It sits
   over everything and takes no taps. With motion reduced it is a still line. */
.loadbar { position: fixed; top: env(safe-area-inset-top, 0px); left: 0; right: 0; height: 3px; z-index: 60; pointer-events: none; overflow: hidden; opacity: 0; transition: opacity 120ms; }
.loadbar::before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 40%; border-radius: 3px; background: var(--ok); transform: translateX(-100%); }
.is-loading .loadbar { opacity: 1; }
.is-loading .loadbar::before { animation: loadbar 1100ms ease-in-out infinite; }
@keyframes loadbar { from { transform: translateX(-100%); } to { transform: translateX(250%); } }
@media (prefers-reduced-motion: reduce) { .is-loading .loadbar::before { animation: none; width: 100%; transform: none; opacity: 0.8; } }
.broke-actions { gap: 10px; flex-wrap: wrap; margin-top: 4px; }

/* The invisible number field that raises the phone's keypad ahead of the PIN
   pane (ui.js wireKeypadAhead). It has to be a real, focusable field, so it is
   not display: none; 16px keeps an iPhone from zooming in on it. */
.keypad-ahead { position: fixed; top: 0; left: 0; width: 1px; height: 1px; padding: 0; border: 0; opacity: 0; font-size: 16px; pointer-events: none; }

/* A TAP SHOWS AT ONCE. On a phone there is no pointer to hover with, so until
   the next screen arrives nothing said the tap was felt, and the phone's own
   grey flash over the whole row is the wrong colour on both themes. What is
   pressed dims while the finger is on it. Touch screens only: a mouse already
   has hover. main.js adds the empty touchstart listener an iPhone needs before
   it will apply :active at all. */
html { -webkit-tap-highlight-color: transparent; }
@media (hover: none) {
  a:active, button:active, [role="button"]:active, [data-tick]:active, summary:active { opacity: 0.55; transition: opacity 0ms; }
}
.screen { display: flex; flex-direction: column; flex: 1; }
/* Room for the fixed tab bar, on the screens that have one. */
.screen.has-tabs { padding-bottom: 86px; }
.pad { padding: 16px 16px 24px; display: flex; flex-direction: column; gap: 18px; }

/* The bar: the mark, then the name. inline-flex rather than the plain span it
   was, so the two sit on one baseline and the gap is a gap rather than a
   space character. The mark carries no glow here: at 26px the drawing is
   mostly the white card, which has all the contrast it needs against either
   surface, and a glow that small reads as a smudge. */
.wordmark { display: inline-flex; align-items: center; gap: 7px; font-weight: 700; font-size: 17px; letter-spacing: -0.01em; }
/* The name is ONE flex child, not two. A gap on the row applies between every
   child, so with the two tone spans loose in here it opened a space inside the
   word and the bar read "Leech list". */
.brand-word { white-space: nowrap; }
.bar-logo { width: 32px; height: 32px; flex: none; object-fit: contain; display: block; }

/* The bar mark gets the same treatment as the big one on the front door, at a
   radius that suits 32px: green on dark, nothing on light. Without it the
   leech is near-black on a near-black bar and the drawing goes muddy, which is
   the same problem the front door had and the same answer. Stronger than this
   reads as a notification badge rather than a logo, at this size. */
:root { --bar-glow: drop-shadow(0 0 5px rgba(65, 220, 165, 0.40)) drop-shadow(0 0 2px rgba(65, 220, 165, 0.28)); }
.bar-logo { filter: var(--bar-glow); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .bar-logo { filter: none; }
}
[data-theme="light"] .bar-logo { filter: none; }
[data-theme="dark"] .bar-logo { filter: var(--bar-glow); }
@media (prefers-reduced-transparency: reduce) {
  .bar-logo { filter: none; }
}

/* build.mjs:677-695, the desktop bar. Hidden below 900px. */
.topbar { display: none; height: 64px; padding: 0 40px; justify-content: space-between; border-bottom: 1px solid var(--line-soft); }
.topbar-left { gap: 28px; }
.topbar-tabs { gap: 4px; }
.topbar-right { gap: 10px; }

/* build.mjs:1188-1191 and 1192-1196, the phone bars. */
.mobile-top { height: 56px; padding: 0 16px; justify-content: space-between; }
.mobile-top-right { gap: 6px; margin-right: -6px; }
.mobile-add { height: 40px; padding: 0 12px 0 10px; gap: 6px; }
.profile-btn-m { width: 44px; height: 44px; justify-content: center; }
.mobile-nav { height: 56px; padding: 0 8px 0 4px; justify-content: space-between; }
.mobile-back { gap: 2px; height: 44px; padding: 0 8px; font-weight: 500; color: var(--text-2); }
.mobile-nav-title { font-weight: 600; }
.mobile-nav-right { width: 72px; justify-content: flex-end; }
.more-btn-m { width: 44px; height: 44px; justify-content: center; color: var(--text-2); }

@media (min-width: 900px) {
  .topbar { display: flex; }
  .mobile-top, .mobile-nav, .tab-bar { display: none; }
  .screen.has-tabs { padding-bottom: 0; }
  .pad { padding: 36px 40px 48px; gap: 36px; }
}

/* ---------------------------------------------------------- the front door
 * build.mjs:2760-2764 (desktop authFrame and authCard) and 2830-2831 (the
 * phone's). One tree: the phone gets the bare column, the desktop gets the
 * same column centred inside a card.
 * ------------------------------------------------------------------------ */
.auth-top { height: 56px; padding: 0 16px; flex: none; }
.auth-wrap { flex: 1; display: flex; flex-direction: column; padding: 24px 20px 0; }
.auth-card { display: flex; flex-direction: column; gap: 22px; width: 100%; }
.auth-head { display: flex; flex-direction: column; gap: 6px; }
/* The boards draw the heading and the sentence as spans 6px apart; an h1 and
   a p carry browser margins that would double that. */
.auth-card h1, .auth-card p, .page-head h1, .page-head p { margin: 0; }
.auth-card .h1 { font-size: 26px; }
.auth-intro { font-size: 14.5px; }
.auth-card .input { height: 50px; }
.auth-card .btn { height: 50px; justify-content: center; font-size: 16px; }
.auth-card .btn svg { width: 18px; height: 18px; }
.auth-links { justify-content: space-between; }
.auth-links a { font-size: 14px; font-weight: 500; min-height: 44px; display: inline-flex; align-items: center; }
.auth-rule { height: 1px; background: var(--line-soft); }
.auth-out { font-size: 13.5px; font-weight: 500; text-align: center; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.back-link { gap: 4px; font-size: 14px; font-weight: 500; align-self: flex-start; min-height: 44px; margin: -8px 0 -6px -6px; padding: 0 6px; }
.signed-in { justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.signed-in-email { color: var(--text-2); font-weight: 500; }
.signed-in-out { font-size: 13px; font-weight: 500; min-height: 44px; display: inline-flex; align-items: center; }

@media (min-width: 900px) {
  .auth-top { height: 64px; padding: 0 40px; border-bottom: 1px solid var(--line-soft); }
  .auth-wrap { align-items: center; justify-content: center; padding: 48px 40px 80px; }
  .auth-card { width: 440px; padding: 32px 32px 28px; background: var(--surface); border: 1px solid var(--line-soft); border-radius: 10px; }
  .auth-card.wide { width: 640px; }
  .auth-intro { font-size: 15px; }
  .auth-card .input { height: 48px; }
  .auth-card .btn { height: 48px; font-size: 15px; }
  .auth-card .btn svg { width: 16px; height: 16px; }
  .back-link { min-height: 32px; }
}

/* ============================================== 4. the component CSS
 * Each block below is the inline style the matching function in build.mjs
 * carried, moved out so the markup is readable and the rule is in one place.
 * ========================================================================= */

/* tickBox, build.mjs:664 */
.tickbox { border-radius: 6px; flex: none; display: inline-block; }
.tickbox.paid { background: var(--ok); color: var(--bg); display: inline-flex; align-items: center; justify-content: center; }
.tickbox.open { border: 2px solid var(--due); }
/* Still to come, and it pays itself: a box to tick if the person pays early,
   and nothing they are being asked to do. Grey, by the amber rule above. */
.tickbox.idle { border: 2px solid var(--text-3); opacity: 0.75; }

/* estPill 776, viaPill 3565, lockBadge 2724, unread 2957 */
.est-pill, .via-pill { padding: 0 6px; letter-spacing: 0.04em; text-transform: uppercase; }
.via-pill { letter-spacing: 0.02em; text-transform: none; flex: none; }
.lock-badge { height: 22px; padding: 0 7px 0 6px; font-size: 11.5px; gap: 4px; }
.badge { padding: 0 6px; border-radius: 999px; background: var(--text); color: var(--bg); justify-content: center; font-weight: 700; flex: none; letter-spacing: 0; }

/* toggle 772, radio 670 */
.toggle { width: 46px; height: 28px; border-radius: 999px; background: var(--line); position: relative; flex: none; }
.toggle > span { position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 999px; background: var(--surface); }
.toggle.on { background: var(--ok); }
.toggle.on > span { left: auto; right: 3px; background: var(--bg); }
.radio { width: 20px; height: 20px; border-radius: 999px; border: 2px solid var(--line); flex: none; display: inline-block; }
.radio.on { border-color: var(--text); display: inline-flex; align-items: center; justify-content: center; }
.radio.on > span { width: 10px; height: 10px; border-radius: 999px; background: var(--text); }

/* priceStep / priceSeq 673-674 */
.pricestep { display: flex; flex-direction: column; gap: 2px; padding: 8px 12px; border-radius: 8px; background: var(--surface-2); }
.pricestep-amt { font-weight: 600; font-size: 15px; }
.priceseq { gap: 8px; flex-wrap: wrap; }
.seq-chev { display: inline-flex; }

/* the static popover 668, and the live one (ui.js showInfo) */
.popover { position: absolute; z-index: 3; width: 250px; padding: 10px 12px; border-radius: 8px; background: var(--text); color: var(--bg); font-size: 12.5px; line-height: 1.4; font-weight: 500; box-shadow: var(--shadow-pop); }
.popover-caret { position: absolute; top: -5px; width: 10px; height: 10px; background: var(--text); transform: rotate(45deg); }
.infotip { position: fixed; z-index: 40; max-width: 280px; padding: 10px 12px; border-radius: 8px; background: var(--text); color: var(--bg); font-size: 12.5px; line-height: 1.4; font-weight: 500; box-shadow: var(--shadow-pop); display: none; }
.infotip.show { display: block; }
/* build.mjs:3573. A real tap target: 44px on the phone, 28px with a mouse. */
.info { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: -12px -8px; border-radius: 999px; color: var(--text-3); flex: none; vertical-align: middle; }
.info.on { color: var(--text); }
@media (hover: hover) and (min-width: 900px) { .info { width: 28px; height: 28px; margin: -6px -4px; } }

/* dayStrip 697-722 */
.daystrip { display: grid; gap: 3px; }
.daystrip-m { display: flex; flex-direction: column; gap: 6px; }
.daystrip-row { display: grid; gap: 2px; }
.daycell { display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 8px 0; border-radius: 6px; }
.daycell.sm { gap: 3px; padding: 6px 0; }
.daycell.weekend { background: var(--surface-2); }
.daycell-wd { font-size: 10px; font-weight: 600; letter-spacing: 0.06em; color: var(--text-3); }
.daycell-num { font-size: 13px; color: var(--text); height: 24px; display: inline-flex; align-items: center; font-weight: 500; }
.daycell.sm .daycell-num { font-size: 12px; }
.daycell-num.past { color: var(--text-3); }
.daycell-num.today { background: var(--text); color: var(--bg); border-radius: 999px; width: 24px; justify-content: center; font-weight: 600; }
.daycell-marks { display: flex; gap: 2px; height: 8px; align-items: center; }
.tick.sm, .ring.sm { width: 7px; height: 7px; }
.tick.past { opacity: 0.45; }
.legend-strip { gap: 18px; font-size: 12.5px; color: var(--text-3); flex-wrap: wrap; }
.legend-strip > span { gap: 7px; }
.weekend-key { background: var(--surface-2); border: 1px solid var(--line); }

/* yearStrip 787-800 */
.yearstrip { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 6px; }
.yearstrip.sm { gap: 3px; }
.yearcell { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 8px 0; border-radius: 6px; background: transparent; }
.yearcell.sm { padding: 6px 0; }
.yearcell.on { background: var(--surface-2); }
.yearcell-mon { font-size: 11px; font-weight: 600; letter-spacing: 0.04em; color: var(--text-3); }
.yearcell.on .yearcell-mon { color: var(--text); }
.yearstrip.sm .yearcell-mon { font-size: 10px; }
.yearcell-marks { display: flex; gap: 2px; height: 8px; align-items: center; }

/* periodCard 802-816 */
.periodcard { display: flex; flex-direction: column; gap: 6px; }
.periodrow { gap: 14px; }
.periodrow.sm { gap: 8px; }
.periodrow-y { width: 40px; font-size: 12px; }
.periodrow.sm .periodrow-y { width: 34px; }
.periodrow-cells { flex: 1; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 4px; }
.periodrow.sm .periodrow-cells { gap: 3px; }
.periodrow-n { width: 24px; font-size: 12px; text-align: right; }
.periodcell { border-radius: 3px; }
.periodcell.off { background: var(--surface-2); opacity: 0.6; }
.periodcell.est { opacity: 0.45; }

/* ticket 730-758 */
.ticket { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
.ticket.compact { padding: 8px 12px; flex-direction: row; justify-content: space-between; align-items: center; gap: 12px; }
/* The name takes all the spare room, so every name starts at the same place.
   With space-between alone the three parts were spread out and a short
   name floated towards the middle (his screenshot, 2026-10-01). */
.ticket.compact .ticket-id { flex: 1 1 auto; }
.ticket.compact .ticket-figs { flex: none; }
.ticket.manual { border-color: var(--due); }
.ticket-top { justify-content: space-between; gap: 8px; }
.ticket-top .ticket-when { margin-right: auto; }
.ticket-when { font-size: 12px; font-weight: 500; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.04em; }
.ticket-when.due { color: var(--due-ink); }
.ticket-body { justify-content: space-between; gap: 12px; align-items: flex-end; }
.ticket-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ticket-name { font-weight: 600; font-size: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ticket.compact .ticket-name { font-size: 15px; }
.ticket-line { gap: 6px; font-size: 12.5px; color: var(--text-3); }
.ticket-amt { font-size: 18px; font-weight: 600; white-space: nowrap; }
.ticket.compact .ticket-amt { font-size: 16px; }
.ticket-figs { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.ticket-date { gap: 5px; font-size: 11px; font-weight: 600; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.04em; }
.ticket-date.due { color: var(--due-ink); }

/* spectrum 761, shareLegend 765-770 */
.spectrum { display: flex; gap: 2px; }
.spectrum > a { border-radius: 3px; display: block; }
.share-legend { display: grid; gap: 12px 24px; }
.share-row { justify-content: space-between; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--line-soft); color: inherit; min-height: 44px; }
.share-row.on { background: var(--surface-2); border-radius: 8px; border-bottom-color: transparent; padding: 10px; }
.share-name { gap: 10px; min-width: 0; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.share-row.on .share-name { font-weight: 700; }
.share-name .faint { font-weight: 400; }
.share-figs { gap: 12px; }
.share-amt { font-weight: 600; }
.share-pct { font-size: 12px; width: 44px; text-align: right; }

/* hueSwatch / hueGrid 778-785 */
.hue-grid { display: grid; gap: 14px 8px; }
.hue { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; }
.hue-chip { border-radius: 10px; display: inline-flex; align-items: center; justify-content: center; color: var(--bg); }
.hue-chip.used { opacity: 0.32; }
.hue-chip.on { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--text); }
.hue-label { font-size: 11.5px; font-weight: 500; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.hue-label.used { color: var(--text-3); }

/* expenseRow 901-930. Five columns that line up down the whole list. */
.expense-row { position: relative; display: grid; grid-template-columns: 2.2fr 1.1fr 2fr 1.3fr 1fr; gap: 16px; align-items: center; min-height: 60px; padding: 0 20px; border-bottom: 1px solid var(--line-soft); color: inherit; }
.expense-row:hover { text-decoration: none; background: var(--surface-2); }
.expense-who { gap: 12px; min-width: 0; }
.expense-initial { width: 34px; height: 34px; border-radius: 8px; background: var(--surface-2); display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 13px; color: var(--text-2); flex: none; }
.expense-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.expense-namerow { gap: 8px; min-width: 0; }
.expense-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.expense-cat { gap: 6px; font-size: 12.5px; color: var(--text-3); }
.expense-shape { gap: 8px; color: var(--text-2); display: flex; flex-direction: column; align-items: flex-start; }
.expense-shape.row { flex-direction: row; align-items: center; }
.pay { gap: 8px; }
.expense-next { display: flex; flex-direction: column; gap: 2px; font-size: 13.5px; color: var(--text-2); }
.expense-next.due { color: var(--due-ink); font-weight: 600; }
.expense-sub { font-weight: 500; font-size: 12px; }
.expense-price { justify-content: flex-end; gap: 6px; }
.expense-price .num { font-weight: 600; font-size: 15px; }
.expense-per { font-size: 12px; width: 40px; color: var(--text-3); }

/* expenseChips 932-949 */
.chips { gap: 8px; flex-wrap: wrap; }
.chips.sm { flex-wrap: nowrap; overflow-x: auto; margin-right: -16px; scrollbar-width: none; }
.chips.sm::-webkit-scrollbar { display: none; }
.chip.sm { height: 36px; }
.chips-divider { width: 1px; height: 22px; background: var(--line); margin: 0 4px; flex: none; }

/* menuItem 2313, moreMenu 2314 */
.menu-item { gap: 12px; min-height: 44px; padding: 6px 10px; border-radius: 6px; color: var(--text); align-items: center; }
.menu-item.with-hint { align-items: flex-start; }
.menu-item.quiet { color: var(--text-2); }
.menu-item:hover { text-decoration: none; background: var(--surface-2); }
.menu-icon { display: inline-flex; flex: none; }
.menu-item.with-hint .menu-icon { margin-top: 3px; }
.menu-text { display: flex; flex-direction: column; gap: 1px; }
.menu-label { font-weight: 500; font-size: 14px; }
.menu-divider { height: 1px; background: var(--line-soft); margin: 4px 6px; }
.more-menu { width: 272px; padding: 6px; box-shadow: var(--shadow-menu); display: flex; flex-direction: column; gap: 2px; }

/* deleteDialog 2321-2340. The numbers are on the screen, never behind an "i". */
.delete-dialog { width: min(560px, 100%); padding: 24px 26px 26px; box-shadow: var(--shadow-dialog); display: flex; flex-direction: column; gap: 20px; border-color: var(--danger); }
.delete-head { justify-content: space-between; align-items: flex-start; }
.delete-title { font-weight: 600; font-size: 20px; letter-spacing: -0.01em; }
.delete-loss { display: flex; flex-direction: column; gap: 8px; }
.delete-sentence { font-size: 16px; line-height: 1.45; }
.delete-sentence .strong { font-weight: 700; }
.delete-instead { padding: 16px; border-radius: 8px; border: 1px solid var(--text); background: var(--surface-2); display: flex; flex-direction: column; gap: 10px; }
.delete-instead-title { font-weight: 600; }
.delete-instead .btn { height: 44px; align-self: flex-start; padding: 0 20px; }
.delete-actions { justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.delete-actions .btn, .delete-actions .btn-ghost { height: 44px; padding: 0 20px; }
/* The way out that erases: red, wherever it appears. It stays a quiet text
   link rather than a button, because the design puts "mark as ended" first on
   purpose and this is the rarer, heavier answer; the colour is what says
   which one it is. */
.delete-go, .dialog-danger { color: var(--danger); }
.delete-go { gap: 8px; font-size: 14px; font-weight: 500; min-height: 44px; padding: 0 8px; }

/* freshConfirm 2342-2346 */
.fresh-confirm { width: 300px; padding: 16px 18px; box-shadow: var(--shadow-menu); display: flex; flex-direction: column; gap: 12px; }
.fresh-title { font-weight: 600; font-size: 16px; }
.fresh-hint { font-size: 13.5px; color: var(--text-2); }
.fresh-actions { gap: 8px; }

/* WHY DANGER IS ITS OWN COLOUR, his call on 2026-09-15 from four reds shown on
   the real dialog. The red is the mint turned to hue 27 in OKLCH and taken a
   touch deeper, because a red AT the mint's lightness cannot hold enough
   chroma and lands on salmon. So it is the same family, not the same number.

   Amber is untouched and keeps its own meaning: it says "due, you pay this one
   yourself" on tick boxes, tickets, dates and the calendar legend. Danger and
   due are different ideas, and a delete borrowing the due colour blurred them.
   The live users of --danger are the delete dialogs' borders and their erase
   links, the profile delete card on desktop (the phone shows it as a sheet),
   and the admin danger dialog. The profile card was the one delete surface
   still wearing amber until 2026-09-16. */

/* WHAT AMBER MEANS, settled with him on 2026-09-19 after going through every
   place it appeared: AMBER IS "THIS IS WAITING ON YOU". A bill you pay
   yourself is the main case and the original one, and the rest are the same
   idea: a form that needs fixing before it can save (.form-msg), instalments
   that do not add up to the year's total (.inst-tie.off), a new year's total
   nobody has answered yet (.new-year-card, .new-year-nudge), and a revoke that
   only half landed on the admin Users screen. Mint is "done, or it takes care
   of itself". Red is "this erases something" and nothing else.

   What amber is NOT, each of these removed on purpose between 2026-09-16 and
   2026-09-19: a delete (the profile delete card, now red), a setting (the
   Auto-payment card, now plain), an option that is merely the heavier of two
   (Sign out everywhere, now plain), and a finished state (the Revoked tag,
   now the neutral pill). Before adding amber anywhere, ask what the person is
   being asked to do. If the answer is nothing, it is not amber.

   And, found on the phone on 2026-09-20 and changed at Laurent's word: THE BOX
   OF A PAYMENT THAT PAYS ITSELF AND HAS NOT HAPPENED YET. It was gold on every
   screen that draws one (a plan's schedule, a bill's payments, the Overview's
   tickets, the desktop list), and it asks nothing. It is grey now
   (.tickbox.idle), still tappable so a plan can be paid off early, and the
   amount beside it is plain. Gold stays for a payment made by hand, one whose
   date has passed, and one that was unticked because it bounced. The rule
   lives in one place, money.js stillToCome(). */

/* scrim 2060 and sheet 2061 */
.sheet-layer { position: fixed; inset: 0; z-index: 30; }
.sheet-scrim { position: absolute; inset: 0; background: var(--scrim); z-index: 5; }
.sheet { position: absolute; left: 0; right: 0; bottom: 0; z-index: 6; padding: 12px 16px max(34px, env(safe-area-inset-bottom)); background: var(--surface); border-radius: 18px 18px 0 0; border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: 14px; box-shadow: var(--shadow-sheet); }
.sheet-grab { width: 36px; height: 4px; border-radius: 2px; background: var(--line); align-self: center; }
.sheet-head { font-weight: 600; font-size: 14px; color: var(--text-2); text-align: center; }
.sheet-rows { display: flex; flex-direction: column; gap: 4px; }
.sheet-items { display: flex; flex-direction: column; }
.sheet-item { gap: 12px; min-height: 52px; padding: 8px 10px; border-radius: 8px; color: var(--text); }
.sheet-item.quiet { color: var(--text-2); }
.sheet-item-text { display: flex; flex-direction: column; gap: 1px; }
.sheet-item-label { font-weight: 500; font-size: 15px; }
.sheet-close { height: 46px; justify-content: center; }

/* THE DAY OF THE MONTH, tapped from a grid (components.js dayField, dayGrid;
   Laurent, 2026-09-30). The field is a button that looks like the input it
   replaced; the sheet holds the days seven across, each at least 44px tall,
   the chosen one filled like a chosen frequency chip. */
.day-pick { justify-content: space-between; cursor: pointer; }
.day-pick svg { color: var(--text-3); flex: none; }
.day-sheet { display: flex; flex-direction: column; gap: 12px; }
.day-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.day-grid button { height: 46px; border-radius: 8px; border: 1px solid var(--line); background: transparent; color: var(--text); font-size: 16px; font-weight: 600; cursor: pointer; }
.day-grid button.on { background: var(--text); color: var(--bg); border-color: var(--text); }
.day-grid button:active { opacity: 0.6; }
/* On a wide screen the sheet sits in the middle as a small panel rather than
   a strip across the whole bottom edge. */
@media (min-width: 900px) {
  .sheet:has(.day-sheet) { left: 50%; right: auto; bottom: 50%; transform: translate(-50%, 50%); width: 420px; border-radius: 14px; border: 1px solid var(--line); padding-bottom: 20px; }
  .sheet:has(.day-sheet) .sheet-grab { display: none; }
}

/* avatar 2723, profileChip 2726, profileMenu 2730, picker 2783 */
/* A PROFILE ICON IS A FILLED MINT DISC, his choice on 2026-09-15 from six
   treatments shown at every size they appear. Mint is not one colour: the
   token is bright (#41dca5) on dark and deep (#00885e) on light, because green
   on white has to go darker to stay legible, so the letter takes --bg and
   comes out near-black on dark and near-white on light. A fill rather than a
   ring because the header chip draws this at 28px, where a 2px border eats
   into the letter and the thin-ring versions go faint. */
.avatar { border-radius: 999px; justify-content: center; flex: none; font-weight: 700; background: var(--ok); color: var(--bg); border: 1px solid var(--ok); }
.avatar.ring { border: 2px solid var(--ok); }
.profile-chip { gap: 8px; height: 40px; padding: 0 10px 0 5px; border-radius: 8px; border: 1px solid var(--line-soft); background: var(--surface); color: var(--text); font-weight: 600; font-size: 14px; }
.profile-chip.open { border-color: var(--text); }
.profile-chip .faint { display: inline-flex; }
.profile-menu { width: 304px; padding: 6px; box-shadow: var(--shadow-menu); display: flex; flex-direction: column; gap: 2px; }
.profile-menu-head { padding: 8px 10px 4px; }
.profile-row { gap: 12px; min-height: 48px; padding: 6px 10px; border-radius: 6px; color: inherit; }
.profile-row.big { min-height: 60px; padding: 8px 10px; border-radius: 8px; }
.profile-row.current { background: var(--surface-2); }
.profile-row:hover { text-decoration: none; background: var(--surface-2); }
.profile-row-text { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.profile-row-name { gap: 8px; font-weight: 600; font-size: 14.5px; }
.profile-row .faint { display: inline-flex; }
.picker-tile { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 24px 16px 20px; border-radius: 10px; border: 1px solid var(--line); background: var(--surface-2); color: inherit; }
.picker-tile.add { justify-content: center; border-style: dashed; background: transparent; color: var(--text-2); }
.picker-tile:hover { text-decoration: none; }
.picker-id { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.picker-name { font-weight: 600; font-size: 17px; }
.picker-tile.add .picker-name { font-size: 15px; }
.picker-nopin { height: 22px; }
.picker-add-circle { width: 64px; height: 64px; border-radius: 999px; justify-content: center; border: 1px dashed var(--line); }
.picker-row { gap: 14px; min-height: 76px; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--line); background: var(--surface-2); color: inherit; }
.picker-row-text { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
.picker-row-name { gap: 8px; font-weight: 600; font-size: 17px; }

/* codeBoxes 2747. The real input sits behind the boxes, invisible but focusable. */
.codeboxes { position: relative; }
.codeboxes-input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; border: 0; background: transparent; font-size: 16px; letter-spacing: 2em; color: transparent; caret-color: transparent; }
.codebox { justify-content: center; border-radius: 8px; border: 1px solid var(--line); background: var(--surface); font-weight: 600; flex: none; position: relative; }
.codebox.focus { border-color: var(--text); box-shadow: 0 0 0 3px var(--surface-2); }
.codebox-caret { display: none; width: 1.5px; height: 50%; background: var(--text); }
.codebox.focus .codebox-caret { display: inline-block; }
.codebox.focus .codebox-digit:not(:empty) + .codebox-caret { display: none; }

/* settingsTile 2958-2966. Settings is a hub of tiles, never a long page. */
.settings-tile { padding: 14px; display: flex; flex-direction: column; justify-content: space-between; gap: 10px; color: inherit; }
.settings-tile:hover { text-decoration: none; border-color: var(--line); }
.settings-tile-top { justify-content: space-between; color: var(--text-2); }
.settings-tile-top > .row { gap: 8px; }
.settings-tile-text { display: flex; flex-direction: column; gap: 3px; }
.settings-tile-label { font-weight: 600; font-size: 15px; }
.settings-tile-value { font-size: 13px; color: var(--text-2); overflow-wrap: anywhere; }
.settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
@media (min-width: 900px) { .settings-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* progressBar 3531, scheduleRows 3536, scheduleGrid 3547, staircase 3577 */
.progress { display: flex; flex: none; }
.progress-seg { flex: 1; border-radius: 2px; background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--line); }
.progress-seg.done { background: var(--ok); box-shadow: inset 0 0 0 1px transparent; }
.progress-seg.next { box-shadow: inset 0 0 0 1px var(--due); }
.schedule-rows { display: flex; flex-direction: column; }
.schedule-row { gap: 14px; min-height: 48px; border-bottom: 1px solid var(--line-soft); }
.schedule-row.sm { gap: 10px; min-height: 44px; }
.schedule-k { width: 28px; font-size: 12px; font-weight: 600; }
.schedule-when { font-weight: 500; font-size: 15px; color: var(--text); flex: 1; white-space: nowrap; }
.schedule-row.sm .schedule-when { font-size: 14px; }
.schedule-when.next { font-weight: 700; }
.schedule-when.done { color: var(--text-2); }
.schedule-when.due { color: var(--due-ink); }
.schedule-note { white-space: nowrap; }
.schedule-amt { font-weight: 600; width: 80px; text-align: right; color: var(--text); }
.schedule-row.sm .schedule-amt { width: 64px; }
.schedule-amt.done { color: var(--text-2); }
/* THE BOX SITS IN THE SAME PLACE ON EVERY ROW. A payment the ledger already
   holds gets a tappable box with a 44px hit area; one beyond the ledger gets
   the plain 22px slot. The hit area used to take part in the layout (44 less
   one margin), so the two kinds were different widths and the amounts beside
   them did not line up. It now overhangs by the same amount on every side, so
   in the layout it is the 22px box it looks like. */
.schedule-row .ticket-tick { width: 44px; height: 44px; min-width: 0; min-height: 0; margin: -11px; }
.schedule-grid { display: grid; }
.schedule-cell { display: flex; flex-direction: column; gap: 4px; padding: 10px 8px; border-radius: 8px; background: var(--surface-2); }
.schedule-cell.sm { padding: 8px; }
.schedule-cell.done { background: var(--ok-bg); }
.schedule-cell.next { box-shadow: inset 0 0 0 1px var(--due); }
.schedule-cell-top { justify-content: space-between; }
.schedule-cell-top .num { font-size: 11px; font-weight: 600; }
.schedule-cell-date { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; }
.schedule-cell.sm .schedule-cell-date { font-size: 12px; }
.schedule-cell-date.done { color: var(--text-2); }
.schedule-cell-date.due { color: var(--due-ink); }
.staircase { display: flex; flex-direction: column; gap: 5px; font-size: 13.5px; color: var(--text-2); }
.staircase-step { gap: 9px; }
.staircase-when { font-weight: 600; color: var(--text); min-width: 112px; }
.staircase-after { font-weight: 600; color: var(--text); }

/* stepper 3575, methodField 3899, viaField 3907 */
.stepper { justify-content: space-between; padding-right: 6px; }
.stepper-n { font-size: 18px; font-weight: 600; }
.stepper-btns { gap: 4px; }
.stepper .iconbtn { width: 36px; height: 36px; border-radius: 6px; }
.method-field, .via-field { position: relative; }
.method-field .input { justify-content: space-between; }
.method-field .input .num { font-weight: 600; }

/* ================================================= 5. unit 2: the front door,
 * the picker, the PIN, the switcher, account and profiles settings. Each
 * block names the generator in build.mjs whose inline styles it carries.
 * ========================================================================= */

/* The hidden attribute wins over any display rule below: a pane or a message
   that is hidden is hidden, whatever flex or grid it carries when shown. */
[hidden] { display: none !important; }

/* One tree, two shapes: what only one width shows. Each rule only ever hides,
   so an element keeps its own display (grid, flex) at the width it is for. */
@media (max-width: 899.98px) { .only-desktop { display: none !important; } }
@media (min-width: 900px) { .only-phone { display: none !important; } }

/* The form message: what a screen says in place when something did not go
   through. Never a toast (SPEC.md section 5). */
.form-msg { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--due-ink); }
.form-msg.centered, .lock-note.centered { text-align: center; }
.strong-ink { color: var(--text); font-weight: 500; }
.btn:disabled, .btn-ghost:disabled { opacity: 0.55; cursor: default; }
.auth-btn-ghost { height: 50px; justify-content: center; font-size: 16px; }
@media (min-width: 900px) {
  .auth-card.w480 { width: 480px; }
  .auth-btn-ghost { height: 48px; font-size: 15px; }
}

/* SignInRevoked 3461: the two calm sentences in a soft panel. */
.revoked-note { display: flex; flex-direction: column; gap: 10px; padding: 14px 16px; border-radius: 8px; background: var(--surface-2); }
.revoked-note > span { font-size: 14.5px; line-height: 1.45; color: var(--text-2); }

/* SignInCode 2770. The code field and the links under the button. */
.code-field { gap: 8px; }
.code-field .lbl.row { gap: 4px; }
.codeboxes-input:focus { outline: none; }

/* Profiles 2792 and PhoneProfiles 2860. Three tiles across on the desktop,
   rows on the phone. */
.picker-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.picker-list { display: flex; flex-direction: column; gap: 10px; }
.picker-row:hover { text-decoration: none; }
.picker-row.add { min-height: 64px; padding: 8px 14px; border-style: dashed; background: transparent; color: var(--text-2); }
.picker-row.add .picker-name { font-size: 15px; }
.picker-add-circle.sm { width: 48px; height: 48px; }

/* ProfilePin 2801 and PhonePin 2872. The phone shows the nav bar and a fixed
   footer with the sign-out; the desktop shows the wordmark bar and a card
   with the sign-out under a rule. */
.pin-screen .auth-top { display: none; }
.pin-screen .auth-wrap { padding-top: 28px; }
.pin-card { gap: 24px; }
.pin-who { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.pin-who .avatar { width: 72px !important; height: 72px !important; font-size: 30px !important; }
.pin-who-text { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.pin-name { font-weight: 600; font-size: 22px; }
.pin-screen .codebox { width: 60px !important; height: 70px !important; font-size: 28px !important; }
.pin-screen .codeboxes { gap: 12px !important; }
.pin-forgot { font-size: 15px; font-weight: 600; color: var(--text); text-align: center; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
.auth-foot { display: flex; flex-direction: column; gap: 8px; }
.auth-out { gap: 6px; font-size: 14px; font-weight: 500; min-height: 44px; justify-content: center; }
/* build.mjs:2832 phoneFooter */
.pin-screen .auth-foot, .phone-footer { position: fixed; left: 0; right: 0; bottom: 0; z-index: 3; padding: 12px 16px max(34px, env(safe-area-inset-bottom)); background: var(--bg); border-top: 1px solid var(--line-soft); display: flex; flex-direction: column; gap: 8px; }
.pin-screen .auth-card { padding-bottom: 120px; }
.forgot-screen .auth-wrap { padding-top: 8px; }
.forgot-screen .codebox { width: 50px !important; height: 59px !important; font-size: 23px !important; }
.forgot-screen .codeboxes { gap: 6px !important; }
.forgot-screen .auth-card { padding-bottom: 160px; }
.phone-footer .btn { height: 50px; justify-content: center; font-size: 16px; width: 100%; }
@media (min-width: 900px) {
  .pin-screen .auth-top { display: flex; }
  .pin-screen .auth-wrap { padding-top: 48px; }
  .pin-card { gap: 22px; }
  .pin-who .avatar { width: 64px !important; height: 64px !important; font-size: 27px !important; }
  .pin-name { font-size: 20px; }
  .pin-screen .codebox { width: 56px !important; height: 66px !important; font-size: 26px !important; }
  .pin-screen .codeboxes { gap: 10px !important; }
  .pin-forgot { font-size: 14.5px; }
  .pin-screen .auth-foot, .phone-footer { position: static; padding: 0; background: transparent; border-top: 0; }
  .pin-screen .auth-card, .forgot-screen .auth-card { padding-bottom: 28px; }
  .forgot-screen .auth-wrap { padding-top: 48px; }
  .forgot-screen .codebox { width: 52px !important; height: 61px !important; font-size: 24px !important; }
  .forgot-screen .codeboxes { gap: 8px !important; }
  .auth-out { font-size: 13.5px; min-height: 32px; }
  .forgot-foot { gap: 22px; }
}

/* The switcher: profileMenu 2730 hangs under the chip; the sheet is ui.js. */
.topbar { position: relative; }
.profile-menu { position: absolute; z-index: 6; }
.sheet-rows .profile-row { min-height: 60px; }
.sheet-item:hover { text-decoration: none; }
/* sheetItem, build.mjs:2426: a rule under each item, 14px to the icon. */
.sheet-items .sheet-item { gap: 14px; padding: 6px 4px; border-radius: 0; border-bottom: 1px solid var(--line-soft); }

/* A settings screen's page: the head, the breadcrumb. */
.page { gap: 20px; }
.page-head { display: flex; flex-direction: column; gap: 6px; }
.page-head-row { justify-content: space-between; align-items: flex-end; gap: 16px; }
/* searchBox, build.mjs:958. The design's box: 40px tall, 240px wide, the glass
   inside it. On the phone it takes the full width under the page head and
   grows to 44px, the tap target every control gets there, and to 16px type,
   because iOS zooms the whole page into any field set smaller than that.
   Written after .input on purpose: same weight, so these sizes win. */
.search-box { height: 40px; width: 240px; flex: none; gap: 8px; padding: 0 6px 0 12px; font-size: 14px; cursor: text; }
.search-ico { display: inline-flex; flex: none; }
.search-box input { flex: 1; min-width: 0; height: 100%; border: 0; outline: 0; padding: 0; background: transparent; color: var(--text); font: inherit; -webkit-appearance: none; appearance: none; }
.search-box input::placeholder { color: var(--text-3); opacity: 1; }
/* The browser's own clear cross is switched off in favour of one that looks the
   same everywhere and is big enough to hit on a phone. */
.search-box input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; display: none; }
.search-clear { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 6px; color: var(--text-3); flex: none; }
.search-clear:hover { color: var(--text); background: var(--surface-2); }
.on-search { justify-content: flex-end; gap: 10px; }
/* The search box and the sort control sit together at the right of the page
   head, 10px apart (build.mjs:957). */
.head-tools { gap: 10px; flex: none; }
/* sortBox, build.mjs:958. The design's outlined button, and inside it a real
   <select> with its own chrome removed. The select's right padding runs under
   the arrow, and the arrow ignores the pointer, so clicking the arrow opens the
   menu like the rest of the control. The focus ring is the input's. */
.sort-box { gap: 6px; padding: 0 12px 0 14px; cursor: pointer; }
.sort-box select { -webkit-appearance: none; appearance: none; border: 0; outline: 0; margin: 0 -20px 0 0; padding: 0 20px 0 0; background: transparent; color: inherit; font: inherit; cursor: pointer; }
.sort-box select option { background: var(--surface); color: var(--text); font-weight: 400; }
.sort-box svg { flex: none; pointer-events: none; color: var(--text-2); }
.sort-ico { display: inline-flex; flex: none; }
.sort-box:focus-within { border-color: var(--text); box-shadow: 0 0 0 3px var(--surface-2); }
/* A phone group is a box of its own so that a search can hide the heading
   together with its rows. It lays out exactly as the bare rows did. */
.phone-group-wrap { display: flex; flex-direction: column; }
@media (max-width: 899.98px) {
  .page-head-row.has-search { flex-wrap: wrap; align-items: stretch; }
  /* One line on the phone: the search box takes what room there is and the
     sort control takes what it needs. Both are 44px, the tap target, and both
     are set in 16px, because iOS zooms the whole page into any field or
     picker set smaller than that. */
  .head-tools { width: 100%; gap: 8px; flex-wrap: wrap; }
  /* Every pixel is spoken for at 390 wide: with "highest amount" chosen the
     sort control is at its widest, and the search box must still show all of
     "Search expenses". The spacing below is what makes that fit. On a
     narrower phone (360 wide) it cannot, so the two wrap instead: the search
     box asks for 165px, and when the line cannot give it that beside the sort
     control, it takes a line of its own and the sort control sits beneath. */
  .search-box { flex: 1 1 165px; width: auto; min-width: 0; height: 44px; font-size: 16px; gap: 6px; padding: 0 2px 0 10px; }
  .sort-box { height: 44px; font-size: 16px; font-weight: 500; gap: 5px; padding: 0 9px 0 11px; }
  .sort-box select { margin-right: -18px; padding-right: 18px; }
  .search-clear { width: 44px; height: 44px; }
}
.settings-crumb { gap: 8px; color: var(--text-3); font-size: 14px; }
.settings-crumb-leaf { color: var(--text); font-weight: 500; }
/* THE TOP BAR GIVES WAY, NOT THE PAGE (2026-09-30). Between 900px, where the
   top bar appears, and about 1030px, a settings or admin breadcrumb made the
   left side wider than the room, and the right side (profile, gear, Add
   expense) was pushed past the edge: the page scrolled sideways by 4 to 63px.
   The right side keeps its size; the breadcrumb shrinks and clips its end
   instead. The page's own heading below says where you are in full. */
.topbar { gap: 16px; }
.topbar-left { min-width: 0; }
.topbar-right { flex: none; }
.topbar .settings-crumb { min-width: 0; overflow: hidden; white-space: nowrap; }
.topbar .settings-crumb > * { flex: none; }
.topbar .settings-crumb > .settings-crumb-leaf { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
@media (min-width: 900px) { .page { padding: 36px 40px 56px; gap: 28px; } }

/* Account 3080, PhoneAccount 3128, signOutRow 3065. */
.account-grid { display: flex; flex-direction: column; gap: 14px; }
.account-card { padding: 16px; display: flex; flex-direction: column; gap: 6px; }
.account-email { font-weight: 600; font-size: 16px; word-break: break-all; }
.signout-card { padding: 4px 16px 2px; gap: 0; }
.signout-eyebrow { padding: 12px 0 10px; }
.signout-row { justify-content: space-between; gap: 16px; padding: 14px 0; border-top: 1px solid var(--line-soft); flex-wrap: wrap; }
.signout-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.signout-title { font-weight: 600; }
.signout-text .hint { font-size: 13px; }
.signout-btn { height: 44px; flex: none; width: 100%; justify-content: center; }
@media (min-width: 900px) {
  .account-screen .page { gap: 28px; }
  .account-grid { display: grid; grid-template-columns: 420px 1fr; gap: 32px; align-items: start; max-width: 1100px; }
  .account-card { padding: 20px 22px; }
  .account-email { font-size: 18px; }
  .signout-card { padding: 6px 22px 4px; }
  .signout-eyebrow { padding: 14px 0 12px; }
  .signout-row { padding: 16px 0; flex-wrap: nowrap; }
  .signout-btn { width: auto; }
}

/* ProfilesSettings 3007 and its phone boards 3095, 3118, 3119. */
.settings-profiles .page { padding-top: 4px; gap: 6px; padding-bottom: 120px; }
.profiles-panes { display: flex; flex-direction: column; gap: 12px; }
.pane { display: flex; flex-direction: column; gap: 12px; }
.settings-profiles.mode-list .pane-edit { display: none; }
.settings-profiles.mode-edit .pane-list { display: none; }
.list-intro { font-size: 13.5px; padding-bottom: 6px; }
.profile-list { overflow: hidden; padding: 0; background: transparent; border: 0; border-radius: 0; }
.profile-list-row { justify-content: space-between; gap: 12px; min-height: 68px; padding: 0; border-bottom: 1px solid var(--line-soft); color: inherit; }
.profile-list-row:hover { text-decoration: none; }
.profile-list-row.dragging { background: var(--surface-2); opacity: 0.85; }
.profile-list-main { gap: 10px; min-width: 0; }
.grip { display: inline-flex; padding: 10px 4px; cursor: grab; touch-action: none; min-height: 44px; align-items: center; }
.profile-list-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.profile-list-name { gap: 8px; font-weight: 600; font-size: 15px; }
.profile-list-row > .faint { display: inline-flex; }
.edit-card { padding: 0; display: flex; flex-direction: column; gap: 20px; background: transparent; border: 0; border-radius: 0; }
.edit-head { display: flex; align-items: center; gap: 14px; }
.new-card .edit-head { flex-direction: column; align-items: flex-start; gap: 2px; }
.edit-head-text { display: flex; flex-direction: column; gap: 2px; }
.edit-name { gap: 4px; font-weight: 600; font-size: 17px; }
.pin-block { display: flex; flex-direction: column; gap: 10px; }
.pin-block-head { justify-content: space-between; }
.pin-change-row { justify-content: space-between; gap: 12px; min-height: 56px; padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
.pin-change-text { display: flex; flex-direction: column; gap: 2px; }
.pin-change-label { font-weight: 500; }
.pin-change-row .btn-ghost { height: 44px; flex: none; }
.pin-change { display: flex; flex-direction: column; gap: 14px; padding-top: 6px; }
/* The current PIN, asked for before it is removed or its profile deleted. */
.pin-ask { display: flex; flex-direction: column; gap: 12px; padding-top: 6px; }
.pin-ask[hidden] { display: none; }
.pin-ask-acts { gap: 10px; }
.pin-ask-acts .btn, .pin-ask-acts .btn-ghost { height: 44px; }
.edit-actions { gap: 10px; flex-wrap: wrap; }
.edit-actions .btn, .edit-actions .btn-ghost { height: 44px; padding: 0 20px; }
.settings-profiles .edit-actions .edit-save { display: none; }
.delete-link { gap: 6px; font-size: 14px; font-weight: 500; min-height: 44px; }
.delete-card { padding: 20px 22px; display: flex; flex-direction: column; gap: 16px; border-color: var(--danger); box-shadow: var(--shadow-menu); }
.delete-card .delete-title { font-size: 17px; }
.delete-card .delete-head .faint { display: inline-flex; width: 40px; height: 40px; align-items: center; justify-content: center; margin: -10px -10px 0 0; }
.delete-choices { display: flex; flex-direction: column; gap: 8px; }
.delete-choices .btn { height: 44px; justify-content: center; }
.delete-choices .delete-go { justify-content: center; }
.sheet .delete-head { flex-direction: column; align-items: stretch; gap: 6px; }
.sheet .delete-title.sheet-title { font-size: 19px; letter-spacing: -0.01em; }
.sheet .delete-loss { gap: 6px; }
.sheet .delete-sentence { font-size: 15px; }
.sheet .delete-choices .btn { height: 50px; font-size: 16px; }
@media (min-width: 900px) {
  .settings-profiles .page { padding: 36px 40px 56px; gap: 28px; }
  .profiles-panes { display: grid; grid-template-columns: 400px 1fr 400px; gap: 32px; align-items: start; }
  .settings-profiles.mode-list .pane-edit, .settings-profiles.mode-edit .pane-list { display: flex; }
  .profile-list { padding: 0 6px; background: var(--surface); border: 1px solid var(--line-soft); border-radius: 10px; }
  .profile-list-row { min-height: 64px; padding: 0 12px 0 6px; }
  .edit-card { padding: 22px 24px; background: var(--surface); border: 1px solid var(--line-soft); border-radius: 10px; }
  .pin-block-head { padding-bottom: 6px; }
  .pin-block .lock-note { padding-top: 10px; }
  .pin-change-row .btn-ghost { height: 40px; }
  .settings-profiles .edit-actions .edit-save { display: inline-flex; }
  .delete-link { margin-left: auto; font-size: 13.5px; }
  .pane-delete[hidden] { display: none !important; }
}

/* ================================================= 6. unit 3: the expenses
 * core. The overview, the list, the form and the detail, with the board each
 * block comes from named on it.
 *
 * PHONE FIRST, then one @media (min-width: 900px) block per screen. The boards
 * are two artboards each and the app is one markup tree (SPEC.md section 5),
 * so the desktop board's inline sizes live in the media query and the phone
 * board's are the plain rules. Nothing here hides a control from one width
 * that the other can reach: .only-desktop and .only-phone only ever swap
 * WORDING or a second drawing of the same data.
 * ========================================================================= */

.page > section, .page > .card, .page > div { min-width: 0; }
.empty-note { margin: 0; padding: 22px 20px; font-size: 14px; }
.centered { text-align: center; }
.right { text-align: right; }

/* ------------------------------------------------- Dashboard 824 / 1202 */
.ov { gap: 22px; }
.ov-top { display: flex; flex-direction: column; gap: 14px; }
.ov-hero { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ov-hero-row { gap: 10px; flex-wrap: wrap; }
.ov-hero-money { font-size: 56px; }
.ov-line { gap: 6px; font-size: 13.5px; font-weight: 500; color: var(--text-2); min-height: 28px; flex-wrap: wrap; }
.ov-line.due { color: var(--due-ink); }
.ov-line.ok { color: var(--ok); }
.ov-line:hover { text-decoration: none; }
.ov-line .faint { font-weight: 400; }
.ov-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.ov-stat { display: flex; flex-direction: column; gap: 6px; padding-left: 12px; border-left: 1px solid var(--line); min-width: 0; }
.ov-stat .eyebrow { font-size: 11px; }
.ov-stat-money { font-size: 22px; }

/* endsBlock 3580 and staircase 3577 */
.ends-card { padding: 14px 16px; display: grid; gap: 20px; }
.ends-left { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.ends-sentence { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; }
.ends-right { display: flex; flex-direction: column; min-width: 0; }
.ends-row { display: grid; grid-template-columns: 1fr 120px 150px; gap: 20px; align-items: center; padding: 12px 0; border-bottom: 1px solid var(--line-soft); color: inherit; }
.ends-row:last-of-type { border-bottom: 0; }
.ends-row:hover { text-decoration: none; background: var(--surface-2); }
.ends-id { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.ends-namerow { gap: 10px; min-width: 0; flex-wrap: wrap; }
.ends-name { font-weight: 600; font-size: 16px; white-space: nowrap; }
.ends-cat { gap: 6px; font-size: 12.5px; color: var(--text-3); white-space: nowrap; }
.ends-bar { gap: 10px; }
.ends-mo { font-size: 18px; font-weight: 600; text-align: right; white-space: nowrap; }
.ends-per { font-size: 12px; font-weight: 500; }
.ends-until { display: flex; flex-direction: column; gap: 2px; align-items: flex-end; }
.ends-until-date { font-size: 13px; font-weight: 600; white-space: nowrap; }
.lender-strip { gap: 20px; padding-top: 12px; border-top: 1px solid var(--line-soft); flex-wrap: wrap; }
.lender-one { gap: 6px; font-size: 13px; color: var(--text-2); white-space: nowrap; }
.lender-name { font-weight: 600; color: var(--text); }

/* the day strip card 851-857 */
.strip-card { padding: 10px 10px 8px; display: flex; flex-direction: column; gap: 6px; }
.strip-head { justify-content: space-between; padding: 0 4px; gap: 12px; flex-wrap: wrap; }
.strip-title { font-weight: 600; font-size: 14px; }
.strip-legend-m { gap: 6px; font-size: 12px; color: var(--text-3); }

/* Coming up 859-878 */
.coming { display: flex; flex-direction: column; gap: 10px; }
.coming-head { justify-content: space-between; gap: 12px; }
.coming-note { display: block; margin: -4px 0 8px; }
.coming-title { font-weight: 600; font-size: 16px; }
.coming-all { gap: 2px; font-size: 13px; font-weight: 500; min-height: 44px; }
.coming-all:hover { text-decoration: none; }
.tickets { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
.tickets-m { display: flex; flex-direction: column; gap: 8px; }
.ticket:hover { text-decoration: none; }
.ticket-tick { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: -12px -12px -12px 0; flex: none; cursor: pointer; }
.ticket-tick[data-busy] { opacity: 0.5; }
.ticket-figs-row { gap: 8px; }
.ticket.compact .ticket-tick { margin: -10px 0 -10px -10px; order: -1; }

.further { padding: 14px 16px; display: grid; gap: 12px; border-color: var(--due); }
.further-head { display: flex; flex-direction: column; gap: 2px; padding-top: 2px; }
.due-eyebrow { gap: 8px; color: var(--due-ink); }
.further-rows { display: flex; flex-direction: column; }
.further-row { display: grid; grid-template-columns: 1fr auto 120px; gap: 20px; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--line-soft); color: inherit; }
.further-row:last-child { border-bottom: 0; }
.further-row:hover { text-decoration: none; background: var(--surface-2); }
.further-id { gap: 12px; min-width: 0; flex-wrap: wrap; }
.further-name { font-weight: 600; font-size: 16px; white-space: nowrap; }
.further-line { gap: 6px; font-size: 12.5px; color: var(--text-3); }
.further-when { gap: 10px; font-size: 13px; font-weight: 600; color: var(--due-ink); text-transform: uppercase; letter-spacing: 0.04em; }
.further-days { font-weight: 500; text-transform: none; letter-spacing: 0; }
.further-amt { font-size: 18px; font-weight: 600; text-align: right; }
.further-m { padding: 8px 12px; display: flex; justify-content: space-between; align-items: center; gap: 10px; border-color: var(--due); color: inherit; min-height: 60px; }
.further-m:hover { text-decoration: none; }
.further-m-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.further-m-top { gap: 8px; min-width: 0; flex-wrap: wrap; }
.further-m .further-name { font-size: 14px; }
.further-m .further-amt { font-size: 15px; }

/* Where it goes 880-889 */
.where { display: flex; flex-direction: column; gap: 10px; }
.where-head { justify-content: space-between; align-items: baseline; }
.where-title { gap: 8px; font-weight: 600; font-size: 16px; }
.band-strip { gap: 14px; font-size: 12px; color: var(--text-2); overflow-x: auto; white-space: nowrap; scrollbar-width: none; min-height: 44px; }
.band-strip::-webkit-scrollbar { display: none; }
.band-one { gap: 6px; color: inherit; flex: none; }
.band-one:hover { text-decoration: none; }
.band-more { flex: none; }

/* --------------------------------------------- Expenses 948 / 1261 */
.expenses-screen .page { gap: 16px; }
.expense-table { overflow: hidden; }
.expense-cols, .cancelled-cols { display: grid; grid-template-columns: 2.2fr 1.1fr 2fr 1.3fr 1fr; gap: 16px; padding: 12px 20px; border-bottom: 1px solid var(--line); font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-3); font-weight: 600; }
.cancelled-cols { grid-template-columns: 2fr 1.3fr 2fr 1.2fr 1fr 1fr; }
.totals-foot { justify-content: flex-end; gap: 28px; padding: 16px 20px; font-size: 13.5px; color: var(--text-2); flex-wrap: wrap; }

/* rowM 1273: the phone list */
.phone-list { display: flex; flex-direction: column; }
.phone-group { padding: 14px 0 6px; }
/* The empty slot at the end of a row is there so the amounts line up with the
   rows that DO carry a box. When no row on the list carries one (everything
   pays itself, which is the whole of Laurent's list today), the slot is 36px of
   nothing on every row, and it is exactly the room "Financed, 2 of 4 paid, next
   25 Sept" needs to stay on one line on a phone. */
.phone-list.no-ticks .phone-row > .tickbox { display: none; }
/* A row WITH a box: its 44px hit area overhangs the 24px box equally on every
   side, so in the layout it is the same 24px as the empty slot on the rows
   around it and every amount ends on one line (it was 8px out). The same cure
   as .schedule-row .ticket-tick. */
.phone-row > .ticket-tick { width: 44px; height: 44px; min-width: 0; min-height: 0; margin: -10px; }
.phone-row { gap: 12px; min-height: 60px; padding: 8px 0; border-bottom: 1px solid var(--line-soft); color: inherit; }
.phone-row:hover { text-decoration: none; }
.phone-initial { width: 38px; height: 38px; border-radius: 9px; background: var(--surface-2); display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; color: var(--text-2); flex: none; position: relative; }
.phone-id { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.phone-namerow { gap: 6px; min-width: 0; }
.phone-name { font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.phone-line { gap: 6px; font-size: 12.5px; color: var(--text-3); }
.phone-line.due { color: var(--due-ink); font-weight: 600; }
.phone-figs { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: none; }
.phone-amt { font-weight: 600; font-size: 15px; }
.phone-per { font-size: 11.5px; white-space: nowrap; }

/* Cancelled 1830 / 2205 */
.saving-card { padding: 12px 14px; display: flex; gap: 12px; align-items: center; border-color: var(--ok); }
.saving-mark { width: 40px; height: 40px; border-radius: 10px; background: var(--ok-bg); color: var(--ok); display: inline-flex; align-items: center; justify-content: center; flex: none; }
.saving-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.saving-line { font-weight: 600; font-size: 15px; }
.cancelled-row { display: grid; grid-template-columns: 2fr 1.3fr 2fr 1.2fr 1fr 1fr; gap: 16px; align-items: center; min-height: 60px; padding: 0 20px; border-bottom: 1px solid var(--line-soft); color: inherit; }
.cancelled-row:hover { text-decoration: none; background: var(--surface-2); }
.cancelled-when { font-size: 13.5px; color: var(--text-2); }
.cancelled-periods { font-size: 13.5px; color: var(--text-2); }
.cancelled-paid { gap: 8px; font-weight: 600; font-size: 15px; }
.cancelled-was { gap: 6px; color: var(--text-2); }
.cancelled-go { gap: 6px; justify-self: end; height: 36px; padding: 0 12px; font-size: 13px; font-weight: 600; border: 1px solid var(--line); border-radius: 8px; white-space: nowrap; }

/* --------------------------------- AddExpense 983 / MobileAdd 1306 / Edit 1352 */
.form-screen .page { padding-bottom: 170px; }
.form-grid { display: flex; flex-direction: column; gap: 18px; }
.form-main { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
/* The phone board gives Category, Kind and Frequency a row each and only
   pairs the amount with its date (build.mjs:1306). A three-way segment in half
   of 375px loses its last tile off the edge. */
.form-two { display: grid; grid-template-columns: 1fr; gap: 12px; }
.form-two.pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.amount-input { gap: 6px; }
/* The bare input inside the amount row fills the row: a 21px text box inside a
   48px control leaves most of the control deaf to a thumb. */
.amount-field { flex: 1; align-self: stretch; min-width: 0; border: 0; background: transparent; color: var(--text); font-size: 18px; font-weight: 600; outline: none; padding: 0; }
.amount-cur { font-size: 13px; }
.day-field .input { font-weight: 600; }
.freq-seg { background: transparent; padding: 0; gap: 8px; flex-wrap: wrap; }
.freq-seg > div { flex: 0 0 auto; height: 44px; padding: 0 14px; border-radius: 6px; border: 1px solid var(--line); background: transparent; font-size: 14px; cursor: pointer; }
.freq-seg > div.on { background: var(--text); color: var(--bg); border-color: var(--text); box-shadow: none; }
.seg > div { cursor: pointer; }
/* NO HIGHLIGHT ON THE AUTO-PAYMENT CARD, his call on 2026-09-18. The design
   drew it amber with the switch off and mint with it on, a preview of how the
   expense would look; the port had kept only the amber, so the card said "you
   pay this one yourself" even when the switch said the opposite. The switch
   turns mint on its own and the sentence under it changes, so the state is
   already said twice; a border was a third voice, and amber on a control
   blurred what amber means everywhere else. Same plain border as the
   instalments switch beside it. */
.auto-card { padding: 14px 16px; display: flex; flex-direction: column; gap: 14px; }
.auto-top { justify-content: space-between; gap: 16px; }
.auto-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.auto-title { gap: 4px; font-weight: 600; }
.toggle-btn { border: 0; background: transparent; padding: 10px 0 10px 10px; min-height: 44px; cursor: pointer; flex: none; }
.method-new { gap: 8px; }
.method-new .input { flex: 1; }
.method-new .btn-ghost { height: 48px; flex: none; }
.form-actions { gap: 10px; flex-wrap: wrap; padding-top: 6px; }
.form-actions .btn, .form-actions .btn-ghost { height: 44px; padding: 0 20px; }
.form-end { margin-left: auto; font-size: 14px; font-weight: 500; min-height: 44px; display: inline-flex; align-items: center; }
.form-foot-figs { justify-content: space-between; font-size: 13px; color: var(--text-2); padding: 0 2px; gap: 10px; }
.form-aside { display: flex; flex-direction: column; gap: 16px; }
.means-card { padding: 18px; display: flex; flex-direction: column; gap: 14px; }
.means-row { justify-content: space-between; gap: 12px; }
/* The figure keeps its weight whether or not it is amber. .due-ink carries 500
   for running text and is written after .strong, so it used to win, and the
   design's 600 (build.mjs:1077) only ever showed on a plain figure. Now that
   "Next due" is amber for some expenses and plain for others, the two must
   not differ in weight as well. */
.means-row .num.strong { font-weight: 600; }
.means-rule { height: 1px; background: var(--line-soft); }
.means-figs { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }

/* the price-change question 1024-1039 */
.price-question { padding: 14px; display: flex; flex-direction: column; gap: 12px; }
.price-q-head { font-weight: 600; font-size: 14.5px; }
.price-choices { display: grid; gap: 12px; align-items: start; }
.price-choice { padding: 12px; border-radius: 8px; border: 1px solid var(--line); display: flex; flex-direction: column; gap: 8px; cursor: pointer; }
.price-choice.on { border-color: var(--text); background: var(--surface-2); }
.price-choice-top { gap: 10px; min-height: 28px; }
.price-choice-title { font-weight: 600; }
.price-radio { position: absolute; opacity: 0; width: 0; height: 0; }
.price-choice .input { height: 44px; background: var(--surface); }
.price-from-field { gap: 6px; }

/* ---------------------------------------------- Detail 1178 / DetailBody 1092 */
.detail-screen .page { gap: 20px; padding-bottom: 40px; }
.detail-top { display: flex; flex-direction: column; gap: 10px; }
.detail-head { justify-content: space-between; align-items: flex-start; gap: 16px; }
.detail-id { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.detail-name { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; }
.kind-pills { gap: 8px; flex-wrap: wrap; }
.detail-actions { gap: 10px; position: relative; }
.detail-stats { display: flex; flex-direction: column; gap: 12px; }
.hero-stat { padding: 14px 16px; display: flex; flex-direction: column; gap: 8px; }
.detail-hero-money { font-size: 40px; }
.detail-next { font-size: 22px; font-weight: 600; line-height: 1; }
.detail-body { display: flex; flex-direction: column; gap: 16px; }
.detail-history { padding: 14px 16px; display: flex; flex-direction: column; gap: 14px; }
.detail-since { justify-content: space-between; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.detail-since-id { display: flex; flex-direction: column; gap: 6px; min-width: 0; flex: 1 1 150px; }
.detail-span { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
.detail-paid { display: flex; flex-direction: column; gap: 4px; align-items: flex-end; flex: 1 1 auto; min-width: 0; }
.detail-paid-fig { gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.detail-paid-money { font-size: 26px; }
.detail-prices { display: flex; flex-direction: column; gap: 10px; }
.detail-side { display: flex; flex-direction: column; gap: 16px; }
.detail-rows { display: flex; flex-direction: column; }
.detail-row { justify-content: space-between; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--line-soft); min-height: 44px; }
.detail-val { font-weight: 500; text-align: right; }
/* A value made of parts (a category dot and its name, a card, its label and
   "1 more on it") had no space between them: the boards set that gap inline on
   each one and the port kept the row without it, so on a phone it read
   "Debit 01361 more on it". */
.detail-val .row { gap: 6px; justify-content: flex-end; flex-wrap: wrap; }
/* A link keeps its parts on one line, or its little arrow drops to a line of
   its own; a long card label is cut with dots instead. */
.detail-val a.row { flex-wrap: nowrap; min-width: 0; }
.detail-val a.row > .num { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 0.001 auto; }
/* When the line is short of room, "1 more on it" gives way long before the card
   does: the card is the answer, the count is a footnote to it. The arrow, the
   last part, never shrinks. */
.detail-val a.row > .faint { white-space: nowrap; flex: none; }
.detail-val a.row > .faint:not(:last-child) { flex: 0 20 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.detail-row > .detail-val { min-width: 0; }
/* The label keeps its one line and the VALUE gives way: shrinking both alike
   broke "Payment method" over two lines and still cut the card short. */
.detail-row > .muted { flex: none; }
.detail-end { font-size: 14px; font-weight: 500; min-height: 44px; display: inline-flex; align-items: center; }
.detail-periods { display: flex; flex-direction: column; gap: 10px; }
.seq-gap { display: inline-flex; flex-direction: column; align-items: center; font-size: 11px; gap: 2px; white-space: nowrap; }
.turn-on-card { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.turn-on-title { font-weight: 600; }
.turn-on-card .btn { height: 44px; }
.charges-card { padding: 6px 16px 14px; display: flex; flex-direction: column; }
.charges-head { justify-content: space-between; gap: 12px; padding: 12px 0 6px; flex-wrap: wrap; }
.charges-title { font-weight: 600; }
.charge-row { gap: 12px; min-height: 48px; border-bottom: 1px solid var(--line-soft); }
.charge-row:last-of-type { border-bottom: 0; }
.charge-when { font-weight: 500; font-size: 14px; flex: 1; white-space: nowrap; }
.charge-note { white-space: nowrap; }
.charge-amt { font-weight: 600; width: 84px; text-align: right; }
.charge-amt.open { color: var(--due-ink); }

/* Cancel 1719 / PhoneCancel 2185, and the delete dialogs 2321 / 2438 */
.overlay-layer { position: fixed; inset: 0; z-index: 40; }
.overlay-scrim { position: absolute; inset: 0; background: var(--scrim); }
.overlay-panel { position: absolute; left: 0; right: 0; bottom: 0; max-height: 92vh; overflow-y: auto; background: var(--surface); border-radius: 18px 18px 0 0; border-top: 1px solid var(--line); padding: 16px 16px max(34px, env(safe-area-inset-bottom)); box-shadow: var(--shadow-sheet); }
.overlay-panel .delete-dialog, .overlay-panel .fresh-confirm { width: auto; border: 0; background: transparent; box-shadow: none; padding: 0; }
.end-dialog { display: flex; flex-direction: column; gap: 18px; }
.end-head-text { display: flex; flex-direction: column; gap: 4px; }
.end-changes { display: flex; flex-direction: column; gap: 8px; }
.change-line { gap: 10px; align-items: flex-start; }
.change-icon { color: var(--text-2); display: inline-flex; flex: none; margin-top: 1px; }
.change-text { font-size: 13.5px; line-height: 1.45; color: var(--text-2); }
.end-actions { gap: 8px; flex-direction: column; align-items: stretch; }
.end-actions .btn { height: 50px; justify-content: center; font-size: 16px; }
.end-actions .btn-ghost { height: 46px; justify-content: center; }
.delete-go.busy { opacity: 0.5; }
.fresh-actions .btn, .fresh-actions .btn-ghost { height: 46px; justify-content: center; flex: 1; }
.more-menu.anchored { position: absolute; top: 46px; right: 0; z-index: 20; }

/* ---------------------------------------------------------- 900px and up */
@media (min-width: 900px) {
  .ov { gap: 36px; }
  .ov-top { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px; align-items: end; }
  .ov-hero { gap: 14px; }
  /* the board draws it at 112px in a 1440 frame; it has to give way before
     it can push the page sideways, so the ceiling is the board and the floor
     is the phone. */
  .ov-hero-money { font-size: clamp(56px, 7.8vw, 112px); }
  .ov-line { gap: 8px; font-size: 15px; }
  .ov-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
  .ov-stat { gap: 10px; padding-left: 20px; }
  .ov-stat .eyebrow { font-size: 12px; }
  .ov-stat-money { font-size: 40px; }

  .ends-card { padding: 20px 22px; grid-template-columns: 1fr 1.2fr; gap: 40px; align-items: start; }
  .ends-left { gap: 12px; }
  .ends-sentence { font-size: 22px; }

  .strip-card { padding: 20px 22px 18px; gap: 14px; }
  .strip-head { padding: 0; }
  .strip-title { font-size: 15px; }

  .coming { gap: 14px; }
  .coming-title { font-size: 17px; }
  .coming-all { font-size: 14px; }
  .further { grid-template-columns: 220px 1fr; gap: 20px; align-items: start; }
  .where-title { font-size: 17px; }

  .expenses-screen .page { gap: 22px; }
  .cancelled-row { min-height: 60px; }
  .saving-card { padding: 18px 22px; gap: 16px; }
  .saving-mark { width: 44px; height: 44px; }
  .saving-line { font-size: 18px; }

  .form-screen .page { padding-bottom: 56px; align-items: center; }
  /* The form column gives way before the summary beside it does: a fixed 640px
     left the summary too little room between 900 and about 1000px wide, and
     an edit form scrolled sideways by up to 96px (2026-09-30). */
  .form-grid { width: 1040px; max-width: 100%; display: grid; grid-template-columns: minmax(0, 640px) minmax(280px, 1fr); gap: 40px; align-items: start; }
  .form-main { gap: 22px; }
  .form-two { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
  .form-aside { position: sticky; top: 24px; }
  .price-choices { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .price-question { padding: 16px 18px 18px; gap: 14px; }
  .price-q-head { font-size: 15px; }
  .price-choice { padding: 14px; gap: 10px; }
  .freq-seg { background: var(--surface-2); padding: 3px; gap: 2px; flex-wrap: nowrap; }
  .freq-seg > div { flex: 1; height: 42px; padding: 0 8px; border: 0; border-radius: 6px; }
  .freq-seg > div.on { background: var(--surface); color: var(--text); border-color: transparent; box-shadow: var(--shadow-seg); }
  .day-field .input { font-size: 16px; }

  .detail-screen .page { gap: 32px; align-items: center; }
  .detail-top, .detail-stats, .detail-body, .charges-card, .detail-screen .form-msg { width: 1040px; max-width: 100%; }
  .detail-top { gap: 18px; }
  .detail-name { font-size: 40px; font-weight: 700; letter-spacing: -0.03em; line-height: 1; }
  .detail-stats { display: grid; grid-template-columns: 1.3fr 1fr 1fr; gap: 24px; }
  .hero-stat { padding: 22px 24px; gap: 14px; }
  .detail-hero-money { font-size: 56px; }
  .detail-next { font-size: 30px; }
  .detail-body { display: grid; grid-template-columns: 1.3fr 1fr; gap: 40px; align-items: start; }
  .detail-history { padding: 22px 24px; gap: 20px; }
  .detail-span { font-size: 22px; }
  .detail-paid-money { font-size: 34px; }
  .detail-row { padding: 14px 0; }
  .charges-card { padding: 8px 24px 18px; }

  .overlay-layer { display: flex; align-items: flex-start; justify-content: center; padding: 120px 20px 20px; }
  .overlay-panel { position: relative; left: auto; right: auto; bottom: auto; width: 580px; max-width: 100%; border-radius: 12px; border: 1px solid var(--line-soft); padding: 24px 26px 26px; box-shadow: var(--shadow-dialog); }
  .overlay-panel .delete-dialog { border-color: transparent; }
  /* The dialog inside loses its own border to this wrapper, so the wrapper is
     what has to carry the warning. :has is what lets the frame know what is
     in it. */
  .overlay-panel:has(.delete-dialog), .overlay-panel:has(.fresh-confirm) { border-color: var(--danger); }
  .end-actions { flex-direction: row; align-items: center; }
  .end-actions .btn, .end-actions .btn-ghost { height: 44px; padding: 0 20px; font-size: 14px; flex: none; }
  .fresh-actions .btn, .fresh-actions .btn-ghost { flex: none; height: 40px; }
}

/* The period and price sequences sit in a 400px column on the desktop and the
   whole width on a phone. They wrap, and a step that cannot shrink pushes its
   dates out of the card, so they shrink and the dates wrap inside them. */
.priceseq { align-items: flex-start; }
.priceseq .pricestep { flex: 0 1 auto; min-width: 0; }
.pricestep .hint { overflow-wrap: anywhere; }

/* ------------------------------------------------- 44px, on a phone, always
 * SPEC.md section 5: every tappable thing on the phone is at least 44px tall.
 * The boards draw some of these smaller because an artboard is a picture and a
 * picture is never missed by a thumb. This block is the audit, one line per
 * control that was under the floor, and it only ever applies below 900px so
 * the desktop keeps the board's own sizes.
 * ------------------------------------------------------------------------ */
@media (max-width: 899.98px) {
  .chip, .chip.sm { min-height: 44px; height: auto; }
  .mobile-add { height: 44px; }
  .band-one, .band-more { min-height: 44px; }
  a.ov-line { min-height: 44px; }
  .kind-pills a.pill { min-height: 44px; }
  .cancelled-go { min-height: 44px; }
  .fresh-actions .btn, .fresh-actions .btn-ghost { min-height: 46px; }
  .seg > div { min-height: 44px; }
  .overlay-panel .delete-head > .faint { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: -10px -10px 0 0; flex: none; }
  /* The Where it goes bar stays the board's 16px; its bands get the missing
     28px as an invisible hit area above and below, so a thumb has 44 and the
     drawing is unchanged. */
  .spectrum > a { position: relative; }
  .spectrum > a::after { content: ""; position: absolute; left: 0; right: 0; top: -14px; bottom: -14px; }
}

/* =============================================================================
 * UNIT 4: financed purchases, and yearly bills paid in instalments.
 *
 * Same rule as everything above: ONE markup tree, and this file decides what
 * the width makes of it (SPEC.md section 5). The phone layout is the default
 * and the 900px block below is the board's desktop arrangement, so a narrow
 * window is never the broken half of a design drawn for a wide one.
 * ========================================================================== */

/* ------------------------------------------------- the financed detail */

.detail-pills { gap: 10px; flex-wrap: wrap; align-items: center; }
.detail-hero-row { gap: 8px; flex-wrap: wrap; align-items: baseline; }

.plan-card { padding: 14px 16px; display: flex; flex-direction: column; gap: 14px; }
.plan-top { justify-content: space-between; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.plan-top-id { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.plan-progress { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
.plan-top-figs { display: flex; flex-direction: column; gap: 4px; align-items: flex-end; }
.plan-paid-money { font-size: 26px; }
.plan-close { padding-top: 14px; }
.plan-facts { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
.plan-facts .detail-rows { gap: 0; }

.anchor-note, .heavy-note { color: var(--text-2); }
.schedule-cell.tappable { cursor: pointer; }
.schedule-cell.tappable:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

.first-field .input { font-weight: 600; }
/* A DATE FIELD ON AN IPHONE IGNORES width: 100%. iOS draws it at a width of its
   own and centres the date, so "First payment" ran off the right edge of his
   screen while every other field stopped at the margin (his screenshots,
   2026-09-19). Taking the field's native dress off is what makes it obey the
   box it is in; it still opens the phone's own date picker. Every date field
   in the app gets this, not only the one he happened to photograph.

   CONFIRMED ON THE PHONE 2026-09-20: it stops at the margin. But that fix also
   said display: block, and the date then sat against the TOP of the field
   instead of in the middle of it. An iPhone centres a date by making the field
   a flex row (its own stylesheet says align-items: center), and "block"
   switched that off. So the field is a flex row again, which .input already
   is: still as wide as its box, and the date back on the centre line. The
   phone's own margin around the value goes too, so it starts where the text of
   every other field starts. */
input[type="date"].input { display: flex; align-items: center; -webkit-appearance: none; appearance: none; min-width: 0; max-width: 100%; text-align: left; }
input[type="date"].input::-webkit-date-and-time-value { text-align: left; margin: 0; align-self: center; }
/* "Each payment" sits alone under the Total and Payments pair, half as wide on
   a wide screen so it lines up with the box above it. */
.each-field { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
/* What the app worked out, in a sentence. It runs the full width, because a
   sentence in a half column breaks into a ladder of short lines on a phone, and
   it takes no room at all while it has nothing to say. */
.each-note { grid-column: 1 / -1; }
.each-note:empty { display: none; }
.cadence-seg > div { cursor: pointer; }

/* ------------------------------------------------- what stops when */

.ends-page { gap: 16px; }
.ends-hero { display: flex; flex-direction: column; gap: 6px; }
.ends-hero-money { font-size: 40px; }
.ends-list { display: flex; flex-direction: column; gap: 8px; }
.ends-lenders, .ends-normal { display: flex; flex-direction: column; gap: 6px; }

.ends-card-m { padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; color: inherit; }
.ends-card-m:hover { text-decoration: none; }
.ends-card-m.manual { border-color: var(--due); }
.ends-m-top { justify-content: space-between; gap: 10px; align-items: flex-start; }
.ends-m-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ends-m-namerow { gap: 8px; min-width: 0; }
.ends-m-name { font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ends-m-cat { gap: 6px; font-size: 12px; color: var(--text-3); }
.ends-m-mo { font-size: 16px; font-weight: 600; white-space: nowrap; }
.ends-m-foot { justify-content: space-between; font-size: 12px; color: var(--text-2); gap: 10px; }
.ends-m-until { font-weight: 600; white-space: nowrap; }

.normal-month { display: flex; flex-direction: column; }
.normal-row { justify-content: space-between; gap: 12px; min-height: 44px; border-bottom: 1px solid var(--line-soft); }
.normal-row.last { border-bottom: 0; }
.normal-when { font-weight: 500; font-size: 14px; }
.normal-row.last .normal-when { font-weight: 600; }
.normal-row.last .normal-when .faint { font-weight: 400; }
.normal-amt { font-weight: 600; }

.ends-open { display: inline-flex; align-items: center; gap: 4px; min-height: 44px; padding: 0; border: 0; background: transparent; font: inherit; font-size: 13.5px; font-weight: 500; cursor: pointer; align-self: flex-start; }
.ends-open[aria-expanded="true"] svg { transform: rotate(90deg); }
.ends-card .ends-normal { padding-top: 4px; }

.method-card-m { justify-content: space-between; padding: 12px 14px; color: inherit; min-height: 44px; }
.method-card-m:hover { text-decoration: none; }
.method-card-val { gap: 8px; }
.method-card-val .faint { font-size: 12px; }

/* ------------------------------------------------- the instalment editor */

.inst-card { padding: 14px; display: flex; flex-direction: column; gap: 14px; }
.inst-head-row { justify-content: space-between; gap: 12px; }
.inst-title { gap: 4px; font-weight: 600; }
/* minmax(0, 1fr), not 1fr: a grid track defaults to min-content, and the
   amount field inside this one refuses to go under its own text, so a plain
   1fr pushed the stepper off a 375px screen. */
.inst-two { display: grid; grid-template-columns: minmax(0, 1fr) 128px; gap: 12px; align-items: end; }
.inst-rows { display: flex; flex-direction: column; gap: 8px; }
.inst-body { display: flex; flex-direction: column; gap: 8px; }
.inst-row { display: grid; grid-template-columns: 32px minmax(0, 1fr) 110px 24px; gap: 8px; align-items: center; }
.inst-cols { color: var(--text-3); }
.inst-k { font-size: 13px; font-weight: 600; }
.inst-when { height: 44px; justify-content: space-between; padding: 0 10px; font-size: 14px; font-weight: 600; }
.inst-amt { height: 44px; gap: 6px; padding: 0 10px; font-size: 14px; }
.inst-amt-field { flex: 1; min-width: 0; border: 0; background: transparent; color: var(--text); font-size: 14px; font-weight: 600; outline: none; padding: 0; }
.inst-tie { gap: 8px; font-weight: 600; font-size: 14px; align-items: flex-start; }
.inst-tie svg { flex: none; margin-top: 1px; }
.inst-tie.ok { color: var(--ok); }
.inst-tie.off { color: var(--due-ink); }
.inst-tie.waiting { color: var(--text-3); font-weight: 500; }
.inst-switch { padding: 14px 16px; display: flex; flex-direction: column; gap: 14px; }

.inst-parts { display: flex; flex-direction: column; gap: 4px; }
.inst-editor { display: flex; flex-direction: column; gap: 12px; }
.inst-editor-head { justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.inst-year-chips { gap: 8px; flex-wrap: wrap; }
.inst-year-chips .chip { cursor: pointer; font-family: inherit; }
.inst-editor-form { display: flex; flex-direction: column; gap: 12px; }

/* ------------------------------------------------- the new year's question */

.new-year-card { padding: 16px; display: flex; flex-direction: column; gap: 14px; border-color: var(--due); }
.new-year-eyebrow { gap: 8px; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600; color: var(--due-ink); }
.new-year-ask { font-size: 19px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; }
.new-year-parts { display: flex; flex-direction: column; gap: 8px; }
.new-year-part { justify-content: space-between; gap: 10px; padding: 10px 12px; border-radius: 8px; background: var(--surface-2); min-height: 44px; }
.new-year-when { gap: 10px; }
.new-year-when .faint { font-size: 13px; font-weight: 600; }
.new-year-when .num { font-weight: 600; }
.new-year-amt { gap: 8px; }
.new-year-amt .num { font-weight: 600; }
.new-year-actions { gap: 10px; flex-direction: column; align-items: stretch; }
.new-year-actions .btn, .new-year-actions .btn-ghost { height: 46px; justify-content: center; }
.new-year-later { min-height: 44px; border: 0; background: transparent; font: inherit; font-size: 14px; font-weight: 500; cursor: pointer; }

.new-year-nudge { padding: 14px 16px; display: flex; flex-direction: column; gap: 8px; border-color: var(--due); color: inherit; }
.new-year-nudge:hover { text-decoration: none; }
.new-year-nudge-ask { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; }
.new-year-nudge > .row.faint { gap: 4px; font-size: 13.5px; font-weight: 500; min-height: 24px; }

.new-year-grid { display: flex; flex-direction: column; gap: 16px; }
.new-year-until { display: flex; flex-direction: column; gap: 10px; }
.estimate-samples { display: flex; flex-direction: column; gap: 10px; }
.est-sample { justify-content: space-between; align-items: center; gap: 12px; }
.est-sample.due { padding: 12px 14px; border-color: var(--due); }
.est-sample.flat { padding: 10px 14px; border-radius: 8px; border: 1px solid var(--line-soft); background: var(--surface); }
.est-sample-id { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.est-sample-name { font-weight: 600; font-size: 15px; }
.est-sample-line { gap: 6px; font-size: 12px; color: var(--text-3); }
.est-sample-figs { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.est-sample-figs > .row { gap: 8px; }
.est-sample-amt { font-size: 16px; font-weight: 600; }
.est-sample-due { gap: 5px; font-size: 11px; font-weight: 600; color: var(--due-ink); text-transform: uppercase; letter-spacing: 0.04em; }
.est-sample.flat > .row { gap: 8px; min-width: 0; }
.est-sample .faint { font-size: 12.5px; }

/* ------------------------------------------------- the desktop arrangement */

@media (min-width: 900px) {
  .plan-card { padding: 22px 24px; gap: 20px; }
  .plan-progress { font-size: 22px; }
  .plan-paid-money { font-size: 34px; }
  .plan-facts { padding: 18px 20px; }

  .ends-page > .ends-hero, .ends-page > .ends-card, .ends-page > .ends-lenders,
  .ends-page > .ends-normal, .ends-page > .back-link { width: 1040px; max-width: 100%; }
  .ends-page { align-items: center; }
  .ends-hero-money { font-size: 56px; }
  .ends-screen .ends-card { padding: 20px 22px; }

  .inst-card { padding: 18px 18px 20px; gap: 16px; }
  .inst-two { grid-template-columns: minmax(0, 1fr) 160px; }
  .inst-row { grid-template-columns: 44px minmax(0, 1fr) 180px 28px; gap: 12px; }
  .inst-when, .inst-amt { font-size: 15px; }
  .inst-amt-field { font-size: 15px; }
  .inst-editor { width: 1040px; max-width: 100%; }

  .new-year-card { padding: 22px 24px; gap: 18px; }
  .new-year-ask { font-size: 22px; }
  .new-year-actions { flex-direction: row; align-items: center; }
  .new-year-actions .btn, .new-year-actions .btn-ghost { padding: 0 20px; }
  .new-year-later { margin-left: auto; }
  .new-year-page { align-items: center; }
  .new-year-page > .page-head, .new-year-page > .new-year-grid { width: 1040px; max-width: 100%; }
  .new-year-grid { display: grid; grid-template-columns: 560px 1fr; gap: 40px; align-items: start; }
  .new-year-nudge { width: 1040px; max-width: 100%; }
}

/* ------------------------------------------------- 44px, unit 4's share
 * The same audit as the block above, for the controls this unit added. Every
 * one of them is a thing a thumb has to find on a 375px screen.
 * ------------------------------------------------------------------------ */
@media (max-width: 899.98px) {
  .cadence-seg > div { min-height: 44px; }
  .inst-when, .inst-amt { min-height: 44px; }
  .inst-year-chips .chip { min-height: 44px; }
  .schedule-row { min-height: 44px; }
  .schedule-cell.tappable { min-height: 44px; }
  .new-year-part { min-height: 44px; }
  .new-year-nudge { min-height: 44px; }
  .ends-card-m { min-height: 44px; }
  .method-card-m { min-height: 48px; }
  .plan-close { padding-top: 10px; }
  /* A SCHEDULE ROW DID NOT FIT A PHONE. Number, date, note, amount and box in
     one line come to about 390px and the card has about 310, and nothing in
     the row could shrink or wrap. So it ran out of the card, the boxes landed
     wherever each row's note pushed them (some outside the card), and the
     overflow made the whole page slide sideways (his screenshot, 2026-09-20).
     On a phone the note goes UNDER the date, and the row is a grid whose last
     two columns are the amount and a fixed 22px for the box, so every box is in
     one column whatever the note says. An empty note takes no height. */
  .schedule-row, .schedule-row.sm { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto 22px; grid-template-areas: "k when amt tick" "k note amt tick"; column-gap: 10px; row-gap: 1px; align-items: center; padding: 7px 0; }
  .schedule-k { grid-area: k; }
  .schedule-when { grid-area: when; align-self: end; }
  .schedule-note { grid-area: note; align-self: start; white-space: normal; }
  .schedule-note:empty { display: none; }
  .schedule-when:has(+ .schedule-note:empty), .schedule-when:not(:has(+ .schedule-note)) { grid-row: 1 / span 2; align-self: center; }
  .schedule-amt, .schedule-row.sm .schedule-amt { grid-area: amt; width: auto; white-space: nowrap; }
  .schedule-row > .tickbox, .schedule-row > .ticket-tick { grid-area: tick; justify-self: center; }
}

/* The phone board puts the big figure FIRST (build.mjs:3831): what is still to
   pay, full width, then Each payment and Next side by side under it. The
   desktop board leads with Each payment. One markup tree, so the order is a
   CSS decision and not a second template. */
@media (max-width: 899.98px) {
  .financed-screen .detail-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .financed-screen .detail-stats > .hero-stat:nth-child(1) { order: 2; }
  .financed-screen .detail-stats > .hero-stat:nth-child(2) { order: 3; }
  .financed-screen .detail-stats > .hero-stat:nth-child(3) { order: 1; grid-column: 1 / -1; }
  .financed-screen .detail-stats > .hero-stat:nth-child(3) .num { font-size: 40px; }
}

/* Two more the audit at 375 caught, and both are the same mistake: a control
   drawn at the board's size inside a box that is already 44. The stepper's
   plus and minus fill their box, and the amount input stretches to the field
   it sits in, so tapping the field is tapping the input and not its border. */
@media (max-width: 899.98px) {
  .stepper .iconbtn { width: 44px; height: 44px; }
  .inst-amt-field { align-self: stretch; }
}

/* =============================================================================
 * UNIT 5: categories, payment methods and spending history.
 *
 * Same rule as everything above: ONE markup tree, and this file decides what
 * the width makes of it (SPEC.md section 5). The phone layout is the default
 * and the 900px block near the end is the boards' desktop arrangement.
 * ========================================================================== */

/* ------------------------------------------------- the panel, both screens
 * components.js panelLayer(). Below 900px it is a bottom sheet (a rename,
 * PhoneMethodRename 4012) or a full pane (a new category, PhoneCategoryEdit
 * 2129); at 900px it becomes the middle column the desktop boards draw.
 * ------------------------------------------------------------------------ */
.panel-layer { position: fixed; inset: 0; z-index: 30; }
.panel-scrim { position: absolute; inset: 0; background: var(--scrim); }
.panel-body {
  position: absolute; left: 0; right: 0; bottom: 0; max-height: 92vh; overflow-y: auto;
  background: var(--surface); border-radius: 18px 18px 0 0; border-top: 1px solid var(--line);
  padding: 14px 16px max(34px, env(safe-area-inset-bottom));
  box-shadow: var(--shadow-sheet);
}
/* A pane is a screen, not a sheet: it has the phone bar above it already. */
.panel-layer.pane { position: static; }
.panel-layer.pane .panel-scrim { display: none; }
.panel-layer.pane .panel-body { position: static; max-height: none; border-radius: 0; border-top: 0; background: transparent; box-shadow: none; padding: 0; }

.panel-form { display: flex; flex-direction: column; gap: 16px; }
.panel-head { justify-content: space-between; align-items: flex-start; gap: 12px; display: flex; }
.panel-title { font-weight: 600; font-size: 17px; display: inline-flex; align-items: center; gap: 4px; }
.panel-foot { gap: 10px; flex-wrap: wrap; }
.panel-foot .btn, .panel-foot .btn-ghost { height: 48px; justify-content: center; flex: 1 1 auto; }
.panel-danger { gap: 8px; min-height: 44px; font-size: 14px; font-weight: 500; }
.panel-preview { display: flex; flex-direction: column; gap: 8px; }
.hue-field { gap: 12px; }
.hue-field-head { justify-content: space-between; gap: 10px; }
.hue { cursor: pointer; }
.hue:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; border-radius: 8px; }

/* A pane fills the phone screen, so the list it came from is not drawn behind
   it and the head above it would be the wrong head. The action is pinned to
   the bottom edge (build.mjs:2141) by the .panel-bottom rule further down. */
@media (max-width: 899.98px) {
  .panel-open .cat-left, .panel-open .method-left,
  .panel-open .page-head-row, .panel-open .cat-foot { display: none; }
  .panel-open .page { padding-bottom: 150px; }
  .panel-layer.pane .panel-foot .btn { height: 50px; font-size: 16px; flex: 1; }
}

/* ------------------------------------------------- Categories 1643 / 2128 */

.cat-page { gap: 16px; }
.cat-head { align-items: flex-start; }
.cat-shared { gap: 10px; font-size: 14px; color: var(--text-2); padding-top: 4px; flex-wrap: wrap; align-items: flex-start; }
.cat-shared-pill { gap: 5px; flex: none; }
.cat-cols { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.cat-left, .method-left { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.cat-left-head, .method-left-head { gap: 8px; }
.cat-list, .method-list { overflow: hidden; }
.cat-row-wrap, .method-row-wrap { position: relative; border-bottom: 1px solid var(--line-soft); }
.cat-row-wrap:last-child, .method-row-wrap:last-child { border-bottom: 0; }
.cat-row, .method-row { justify-content: space-between; gap: 12px; min-height: 56px; padding: 0 16px; color: inherit; flex: 1; min-width: 0; }
.cat-row:hover, .method-row:hover { text-decoration: none; background: var(--surface-2); }
.method-row.none { opacity: 0.6; }
.cat-row-id, .method-row-id { gap: 12px; min-width: 0; }
.cat-row-text, .method-row-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cat-row-name { font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.method-row-label { font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cat-row-n, .method-row-n { font-size: 12px; }
.cat-row-figs, .method-row-figs { gap: 8px; flex: none; }
.cat-row-amt, .method-row-amt { font-weight: 600; font-size: 14px; }
.cat-row-per, .method-row-per { font-size: 11.5px; }
.method-row-gap { width: 16px; flex: none; }
.cat-edit, .method-edit { width: 40px; height: 40px; flex: none; margin-right: 10px; border: 0; background: transparent; }
.cat-edit:hover, .method-edit:hover { background: var(--surface-2); text-decoration: none; }
.method-edit-gap { width: 50px; flex: none; }
.cat-ceiling { margin: 0; }
.cat-add-full { color: var(--text-3); cursor: default; }
.cat-preview { justify-content: space-between; gap: 10px; padding: 10px 12px; border-radius: 8px; background: var(--surface-2); min-height: 44px; }
.cat-preview-id { gap: 10px; min-width: 0; }
.cat-preview-name { font-weight: 500; }
.cat-preview-figs { gap: 12px; flex: none; }
.cat-preview-figs .num { font-weight: 600; }
.cat-preview-pct { font-size: 12px; width: 44px; text-align: right; }
.cat-delete .delete-choices, .method-delete .delete-choices { display: flex; flex-direction: column; gap: 8px; }
.cat-delete .delete-choices .btn, .cat-delete .delete-choices .btn-ghost,
.method-delete .delete-choices .btn, .method-delete .delete-choices .btn-ghost { height: 46px; justify-content: center; }

/* ------------------------------------------- the category drill-in 1557 / 2077 */

.cat-drill { gap: 16px; }
.drill-top { display: flex; flex-direction: column; gap: 10px; }
.drill-head { justify-content: space-between; align-items: flex-start; gap: 16px; }
.drill-id { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.drill-name { gap: 10px; font-size: 26px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; }
.drill-edit { display: none; }
.drill-edit-m { height: 46px; justify-content: center; }
.drill-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.drill-stats > .hero-stat.wide { grid-column: 1 / -1; }
.drill-hero-row { gap: 10px; flex-wrap: wrap; }
.drill-hero-money { font-size: 44px; }
.drill-stat-money { font-size: 22px; }
.drill-in { display: flex; flex-direction: column; gap: 10px; }
.drill-in-head { gap: 8px; font-weight: 600; font-size: 16px; }
.drill-bottom { display: flex; flex-direction: column; gap: 16px; }
.drill-lands { padding: 10px 10px 8px; display: flex; flex-direction: column; gap: 6px; }
.drill-lands-head { justify-content: space-between; padding: 0 4px; }
.drill-all { display: flex; flex-direction: column; gap: 12px; }
.drill-all-head { justify-content: space-between; align-items: baseline; }
.drill-all-title { gap: 8px; font-weight: 600; font-size: 16px; }

/* inCatRow 1565 / 2062: four columns on the desktop, two rows on the phone. */
.in-cat-table { overflow: hidden; }
.in-cat-cols, .in-cat-foot { display: grid; grid-template-columns: 2.2fr 1.6fr 1fr 1fr; gap: 16px; padding: 12px 20px; font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-3); font-weight: 600; }
.in-cat-cols { border-bottom: 1px solid var(--line); }
.in-cat-foot { padding: 14px 20px; font-size: 13.5px; letter-spacing: 0; text-transform: none; color: var(--text-2); font-weight: 400; }
.in-cat-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: center; min-height: 60px; padding: 10px 14px; border-bottom: 1px solid var(--line-soft); color: inherit; }
.in-cat-row:last-of-type { border-bottom: 0; }
.in-cat-row:hover { text-decoration: none; background: var(--surface-2); }
.in-cat-line { gap: 6px; font-size: 12.5px; color: var(--text-3); }
.in-cat-line.due { color: var(--due-ink); font-weight: 600; }
.in-cat-next { display: flex; flex-direction: column; gap: 2px; font-size: 13px; color: var(--text-2); grid-column: 1; }
.in-cat-next.due { color: var(--due-ink); font-weight: 600; }
.in-cat-year { gap: 6px; justify-content: flex-end; font-weight: 600; font-size: 15px; grid-row: 1; grid-column: 2; }
.in-cat-avg { font-size: 13px; text-align: right; grid-column: 2; }
.in-cat-none { font-weight: 500; font-size: 12.5px; }
.in-cat-sub { font-size: 11.5px; }

/* ------------------------------------- payment methods 3925 / 4011 / 4034 */

.method-page { gap: 16px; }
.method-why { gap: 10px; font-size: 14px; color: var(--text-2); padding-top: 4px; }
.method-cols { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.rename-follows { padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; background: var(--surface-2); }
.rename-follows-head { font-weight: 600; font-size: 14.5px; line-height: 1.35; }
.rename-follows-names { gap: 6px; flex-wrap: wrap; }
.rename-chip { background: var(--surface); }
.method-answers { display: flex; flex-direction: column; gap: 8px; }
.method-answer { padding: 12px; border-radius: 8px; border: 1px solid var(--line); display: flex; flex-direction: column; gap: 10px; cursor: pointer; }
.method-answer.on { border-color: var(--text); background: var(--surface-2); }
.method-answer-top { gap: 10px; min-height: 28px; }
.method-answer-title { font-weight: 600; }
.method-answer-hint { padding-left: 30px; }
.method-answer .input { height: 44px; background: var(--surface); }

.method-on { gap: 16px; }
.on-top { display: flex; flex-direction: column; gap: 10px; }
.on-card { padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }
.on-card-id { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.on-card-title { gap: 8px; font-size: 18px; font-weight: 600; letter-spacing: -0.01em; align-items: flex-start; }
.on-card-hint { font-size: 13.5px; color: var(--text-2); }
.on-card-figs { display: flex; flex-direction: column; gap: 4px; }
.on-card-money { font-size: 40px; }
.on-also { gap: 10px; flex-wrap: wrap; }
.on-also-chip { gap: 8px; }
.on-also-chip.none { opacity: 0.6; }
.on-rename { height: 46px; justify-content: center; }

/* ============================================= the history charts 2482-2568 */

.splitbar { display: flex; flex-direction: row; gap: 2px; width: 100%; height: 100%; }
.splitbar.vertical { flex-direction: column; justify-content: flex-end; }
.splitbar.fixed { width: auto; height: auto; }
.splitseg { display: block; min-width: 0; min-height: 0; }
.splitseg.fixed { flex: none; }
.legend-sw { width: 10px; height: 10px; border-radius: 2px; display: inline-block; flex: none; }
.legend-hatch { color: var(--text); }
.chart-legend { gap: 16px; font-size: 12px; color: var(--text-2); flex-wrap: wrap; }
.chart-legend.sm { gap: 10px; }
.chart-legend > span { gap: 6px; }

.monthbars { display: flex; flex-direction: column; gap: 6px; }
.monthbars-row { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 6px; padding-top: 18px; border-bottom: 1px solid var(--line); }
.monthbars-row.sm, .monthbars-labels.sm { gap: 2px; }
.monthbars-labels { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 6px; }
.monthcol { position: relative; display: flex; align-items: flex-end; justify-content: center; gap: 2px; }
.monthcol-fig { position: absolute; left: 50%; transform: translateX(-50%); font-size: 11px; font-weight: 600; white-space: nowrap; }
.monthcol-fig.sm { font-size: 10px; }
.monthbar { width: 18px; display: flex; flex-direction: column; justify-content: flex-end; }
.monthbar.sm { width: 9px; }
.monthlab { font-size: 11px; font-weight: 600; letter-spacing: 0.04em; color: var(--text-3); text-align: center; line-height: 1.25; display: flex; flex-direction: column; }
.monthlab.sm { font-size: 10px; }
.monthlab.on { color: var(--text); }
.monthlab-sofar { font-weight: 500; letter-spacing: 0; text-transform: none; }

.catbars { overflow: hidden; }
.catbar-cols, .catbar-foot, .catbar-row { display: grid; grid-template-columns: 150px 1fr 100px 104px 84px; gap: 16px; align-items: center; }
.catbars.nocompare .catbar-cols, .catbars.nocompare .catbar-foot, .catbars.nocompare .catbar-row { grid-template-columns: 150px 1fr 100px; }
.catbar-cols { padding: 12px 20px; border-bottom: 1px solid var(--line); font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-3); font-weight: 600; }
.catbar-row { min-height: 52px; padding: 0 20px; border-bottom: 1px solid var(--line-soft); color: inherit; }
.catbar-row:hover { text-decoration: none; background: var(--surface-2); }
.catbar-foot { padding: 14px 20px; font-size: 13.5px; color: var(--text-2); }
.catbar-name { gap: 10px; min-width: 0; font-weight: 500; }
.catbar-name > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.catbar-track { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.catbar-cur { height: 12px; }
.catbar-prev { height: 4px; border-radius: 0 2px 2px 0; }
.catbar-prev.solid { background: var(--text-3); }
.catbar-prev.hatch { color: var(--text-3); }
.catbar-amt { font-weight: 600; font-size: 15px; }
.catbar-prev-amt { font-size: 14px; color: var(--text-2); }
.catbar-change { font-size: 13px; color: var(--text-2); }
.catbar-none { font-weight: 500; font-size: 13px; }
.catbars-m { display: flex; flex-direction: column; }
.catbar-m { display: flex; flex-direction: column; gap: 6px; padding: 10px 0; border-bottom: 1px solid var(--line-soft); color: inherit; }
.catbar-m:hover { text-decoration: none; }
.catbar-m-top { justify-content: space-between; gap: 10px; }
.catbar-m-amt { font-weight: 600; font-size: 14px; }

.yearbars { display: flex; flex-direction: column; }
.yearbar { display: grid; grid-template-columns: 88px 1fr 110px; grid-template-areas: "y track amt" "y note amt"; gap: 4px 16px; align-items: center; padding: 9px 0; border-bottom: 1px solid var(--line-soft); color: inherit; }
.yearbar:hover { text-decoration: none; }
.yearbar-y { grid-area: y; font-weight: 500; font-size: 13px; color: var(--text-2); white-space: nowrap; }
.yearbar.on .yearbar-y { font-weight: 700; color: var(--text); }
.yearbar-amt { grid-area: amt; font-weight: 600; font-size: 14px; text-align: right; }
.yearbar-track { grid-area: track; height: 12px; min-width: 2px; }
.yearbar-note { grid-area: note; }
.yearbar.sm { grid-template-columns: 1fr auto; grid-template-areas: "y amt" "track track" "note note"; gap: 6px 10px; padding: 10px 0; }

/* ------------------------------------------------- History 2587 / 2643 / 2672 */

.history { gap: 16px; }
.history-head { flex-direction: column; align-items: stretch; gap: 12px; }
.year-chips { gap: 8px; }
.year-chips-all { gap: 8px; flex-wrap: wrap; }
.year-chips-few { gap: 8px; overflow-x: auto; scrollbar-width: none; margin-right: -16px; padding-right: 16px; }
.year-chips-few::-webkit-scrollbar { display: none; }
.hist-hero { display: flex; flex-direction: column; gap: 6px; }
.hist-hero-row { gap: 10px; flex-wrap: wrap; }
.hist-hero-money { font-size: 44px; }
.hist-line { font-size: 13.5px; color: var(--text-2); gap: 8px; align-items: flex-start; line-height: 1.45; }
.hist-line svg { flex: none; margin-top: 2px; }
.hist-months { padding: 12px 10px 10px; display: flex; flex-direction: column; gap: 10px; }
.hist-months-head { justify-content: space-between; padding: 0 4px; gap: 8px; flex-wrap: wrap; }
.hist-months-title { gap: 8px; font-weight: 600; font-size: 14px; }
.hist-bottom { display: flex; flex-direction: column; gap: 20px; }
.hist-cats, .hist-years { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.hist-sub-head { justify-content: space-between; align-items: baseline; gap: 12px; }
.hist-sub-title { font-weight: 600; font-size: 16px; }
.history-empty { padding: 16px; display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
.history-years { gap: 14px; }
.hist-years-note { font-size: 13.5px; }
.hist-years-foot { justify-content: space-between; font-size: 13px; color: var(--text-2); padding: 4px 2px; gap: 12px; }

/* ------------------------------------------------- the desktop arrangement
 * The three boards are three-column screens at 1440 (Categories 1643,
 * PaymentMethods 3925) and two-column ones (History 2587). Below 900px every
 * one of them is the single column above, in the boards' own phone order.
 * ------------------------------------------------------------------------ */
@media (min-width: 900px) {
  .cat-page, .method-page { gap: 28px; }
  .cat-cols, .method-cols { display: grid; grid-template-columns: 400px minmax(0, 560px); gap: 32px; align-items: start; justify-content: start; }
  .panel-layer { position: static; display: contents; }
  .panel-scrim { display: none; }
  .panel-body {
    position: static; max-height: none; overflow: visible;
    background: var(--surface); border: 1px solid var(--line-soft); border-radius: 10px;
    padding: 22px 24px; box-shadow: none;
  }
  .panel-foot .btn, .panel-foot .btn-ghost { height: 44px; padding: 0 20px; flex: none; }
  .cat-row, .method-row { min-height: 56px; }

  .cat-drill { gap: 32px; align-items: center; }
  .cat-drill > .drill-top, .cat-drill > .drill-stats, .cat-drill > .drill-in,
  .cat-drill > .drill-bottom, .cat-drill > .form-msg { width: 1040px; max-width: 100%; }
  .drill-name { font-size: 40px; font-weight: 700; letter-spacing: -0.03em; line-height: 1; }
  .drill-edit { display: inline-flex; }
  .drill-stats { grid-template-columns: 1.3fr 1fr 1fr; gap: 24px; }
  .drill-stats > .hero-stat.wide { grid-column: auto; }
  .drill-hero-money { font-size: 56px; }
  .drill-stat-money { font-size: 30px; }
  .drill-bottom { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: start; }
  .drill-lands { padding: 20px 22px 18px; gap: 14px; }
  .drill-lands-head { padding: 0; }
  .in-cat-row { grid-template-columns: 2.2fr 1.6fr 1fr 1fr; gap: 16px; padding: 0 20px; }
  .in-cat-next { grid-column: auto; }
  .in-cat-year { grid-row: auto; grid-column: auto; }
  .in-cat-avg { grid-column: auto; }

  .method-on { gap: 22px; }
  .on-card { padding: 18px 20px; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 24px; align-items: center; }
  .on-card-title { font-size: 20px; align-items: center; }
  .on-card-figs { align-items: flex-end; }
  .on-card-money { font-size: 30px; }

  .history { gap: 32px; }
  .history-head { flex-direction: row; align-items: flex-end; gap: 24px; }
  .hist-hero { gap: 14px; }
  .hist-hero-money { font-size: clamp(56px, 6vw, 88px); }
  .hist-line { font-size: 15px; }
  .hist-months { padding: 20px 22px 18px; gap: 18px; }
  .hist-months-head { padding: 0; align-items: flex-start; flex-wrap: nowrap; }
  .hist-months-title { font-size: 15px; }
  .hist-bottom { display: grid; grid-template-columns: 1.4fr 1fr; gap: 40px; align-items: start; }
  .hist-sub-title { font-size: 17px; }
  .history-years { align-items: center; }
  .history-years > * { width: 720px; max-width: 100%; }
}

/* ------------------------------------------------- 44px, unit 5's share
 * The same audit as units 3 and 4: every control this unit added, measured at
 * 375px. The boards draw some of them smaller because an artboard is a picture
 * and a picture is never missed by a thumb.
 * ------------------------------------------------------------------------ */
@media (max-width: 899.98px) {
  .cat-row, .method-row { min-height: 56px; }
  .cat-edit, .method-edit { width: 44px; height: 44px; }
  .hue-chip { min-width: 44px; min-height: 44px; }
  .method-answer { min-height: 44px; }
  .catbar-m { min-height: 44px; }
  .catbar-row { min-height: 44px; }
  .yearbar { min-height: 44px; }
  .on-also-chip { min-height: 44px; }
  .year-chips .chip { min-height: 44px; }
  .panel-danger { min-height: 44px; }
  .cat-preview { min-height: 44px; }
  .in-cat-row { min-height: 60px; }
  /* A hue swatch is drawn at 48px, so it already clears the floor; the label
     under it is part of the same target and gets the tap through the cell. */
  .hue { min-height: 44px; }
}

/* The desktop board lays the ready hues out seven across and the phone board
   five (build.mjs:1677 and 2138). hueGrid() writes the phone's count inline,
   so the desktop's is the override: an inline style is beaten by nothing else. */
@media (min-width: 900px) {
  .panel-body .hue-grid { grid-template-columns: repeat(7, minmax(0, 1fr)) !important; }
}

/* The desktop boards put the name and the count on ONE line (build.mjs:1645
   and 3928) and the phone boards stack them (2116 and 4003). One tree, so the
   stack is the default and the line is the override. */
@media (min-width: 900px) {
  .cat-row-text, .method-row-text { flex-direction: row; align-items: baseline; gap: 12px; }
  .cat-row-n, .method-row-n { font-size: 13px; white-space: nowrap; }
  .cat-cols, .method-cols { grid-template-columns: 444px minmax(0, 496px); }  /* 400px of row, as the boards draw it, plus the 44px rename control */
}

/* THE HUE SQUARE ON AN INITIAL IS POSITIONED AGAINST THE INITIAL, and without
   this it is positioned against the page: catDot()'s extra style is
   `position: absolute; right: -2px`, and `.expense-initial` carried no
   position of its own (the list's `.expense-row` did, so unit 3 never met it).
   The square landed 2px past the right edge of the window and put a horizontal
   scrollbar on the whole screen. Found at 1440 on the category drill-in. */
.in-cat-row .expense-initial { position: relative; }

/* THE PHONE BAR ALREADY CARRIES THE TITLE on these two screens, so the heading
   under it would be the same word twice (build.mjs:2119 and 3999 draw neither).
   The expenses list keeps its own heading because its phone bar is the
   wordmark, not a nav bar, so there it is the only title on the screen. */
@media (max-width: 899.98px) {
  .cat-page .page-head .h1, .method-page .page-head .h1 { display: none; }
  .cat-page .page-head, .method-page .page-head { gap: 6px; }
}

/* The phone pane pins its preview AND its action to the bottom edge as one bar
   (build.mjs:2141); the desktop stacks them in the column. */
.panel-bottom { display: flex; flex-direction: column; gap: 10px; }
.cat-preview-none { margin-left: auto; }
@media (max-width: 899.98px) {
  .panel-layer.pane .panel-bottom {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 3; gap: 8px;
    padding: 10px 16px max(34px, env(safe-area-inset-bottom));
    background: var(--bg); border-top: 1px solid var(--line-soft);
  }
  .panel-layer.pane .panel-bottom .panel-foot .btn { height: 50px; font-size: 16px; }
  .panel-layer.pane .panel-preview .cat-preview { background: transparent; padding: 0 2px; min-height: 0; font-size: 13px; }
}
@media (min-width: 900px) {
  .cat-preview-none { margin-left: 0; }
}

/* The rename control is a real 44px target at every width, which is 44px the
   boards never had to find room for (they draw the row alone). On a 375px
   screen that is the difference between "Joint account" and "Joint acc...", so
   the row's own padding and gaps give the name the pixels back. */
@media (max-width: 899.98px) {
  .cat-row, .method-row { padding: 0 10px; }
  .cat-row-figs, .method-row-figs { gap: 6px; }
  .cat-edit, .method-edit { margin-right: 4px; }
  .method-edit-gap { width: 48px; }
}

/* And the chevron goes with it. A row that carries a pencil already says it is
   a control, so the chevron beside it is a second arrow for the same idea and
   the 20px it costs is exactly what "Joint account" needs to print in full. */
@media (max-width: 899.98px) {
  .cat-row-figs > .faint:last-child, .method-row-figs > .faint:last-child { display: none; }
}

/* The phone sheet's grab pill (build.mjs:2061). A pane has none: it is a
   screen, not something to pull down. */
.panel-body > .sheet-grab { display: block; margin: 0 auto 10px; }

/* The phone board draws this row in three facts (name, line, amount) and the
   app draws four: it keeps the next date and the per-month figure, which are
   the two columns the DESKTOP board has and which a person on a phone has no
   other way to see on this screen. The cost is a taller row, so the padding
   and the type come back down to keep it near the board's rhythm. */
@media (max-width: 899.98px) {
  .in-cat-row { padding: 8px 12px; gap: 2px 10px; min-height: 60px; }
  .in-cat-row .expense-name { font-size: 15px; }
  .in-cat-line { font-size: 12px; }
  .in-cat-next { font-size: 12.5px; }
  .in-cat-year { font-size: 14px; }
  .in-cat-avg { font-size: 12.5px; }
  .in-cat-table { overflow: hidden; }
}

/* =============================================================================
 * 9. UNIT 6B: the settings hub, currency and appearance, feedback as a
 * conversation, and the admin area.
 *
 * SettingsHub 2983 / MobileSettings 1396, MobileCurrency 1410, Feedback 3163 /
 * PhoneFeedback 3186, FeedbackThread 3236 / PhoneFeedbackThread 3268, AdminHub
 * 3291 / PhoneAdminHub 3477, AdminMessages 3313 / PhoneAdminMessages 3484 /
 * PhoneAdminThread 3491, AdminInvite 3356 / PhoneAdminInvite 3500, AdminUsers
 * 3441 / PhoneAdminUsers 3516 / PhoneAdminUsersRevoke 3519.
 *
 * Phone first, as the rest of this file is, and the desktop arrangement in one
 * min-width block at the end of each part.
 * ========================================================================= */

/* --------------------------------------------------------- the hub 2983/1396
 * A hub of tiles, never one long scrolling page. Two columns on a phone, three
 * on the desktop board; .settings-grid above already carries the phone's two
 * and a four-column desktop default, so the hub asks for three of its own.
 */
.settings-hub .settings-grid { gap: 12px; }
.maker-only { display: flex; flex-direction: column; gap: 12px; }
.maker-only-head { gap: 8px; }
.settings-tile .hint { line-height: 1.35; }

@media (min-width: 900px) {
  .settings-hub .pad { gap: 28px; }
  .settings-hub .settings-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; max-width: 1100px; }
  .settings-hub .maker-only { max-width: 1100px; }
}

/* ------------------------------------------------- currency and appearance
 * build.mjs:1411. The rows are 56px on the board and the 44px rule makes that
 * a floor rather than a target, so they stay 56 everywhere.
 */
.choose-list { display: flex; flex-direction: column; gap: 8px; }
.choose-row {
  justify-content: space-between; gap: 12px; min-height: 56px; padding: 8px 14px;
  border-radius: 8px; border: 1px solid var(--line); background: transparent;
  color: var(--text); text-align: left; width: 100%;
}
.choose-row.on { border-color: var(--text); background: var(--surface-2); }
.choose-lead { gap: 12px; min-width: 0; }
.choose-code { font-weight: 700; width: 44px; flex: none; }
.choose-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.choose-label { font-weight: 500; }
.choose-tick { width: 20px; height: 20px; flex: none; }
.choose-icon { color: var(--text-2); display: inline-flex; flex: none; }
/* build.mjs:1420. The sample card: the same three figures, so the choice can
   be seen rather than imagined. */
.sample-card { padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }
.sample-row { justify-content: space-between; }
.sample-row .strong { font-weight: 600; }

@media (min-width: 900px) {
  .choose-page .choose-list, .choose-page .sample-card, .choose-page .tipline { max-width: 560px; }
}

/* ============================================== feedback, the person's side */

.feedback-form { display: flex; flex-direction: column; gap: 20px; }
.message-box {
  height: auto; min-height: 150px; align-items: flex-start; padding: 12px 14px;
  font-size: 15px; line-height: 1.45; resize: vertical; font-family: inherit;
}
.message-box::placeholder { color: var(--text-3); }

/* build.mjs:3138-3140. The attach control, empty and filled. */
.attach-empty {
  gap: 12px; min-height: 60px; padding: 10px 14px; border-radius: 8px;
  border: 1px dashed var(--line); color: var(--text-2); cursor: pointer;
}
.attach-empty:focus-within { border-color: var(--text); box-shadow: 0 0 0 3px var(--surface-2); }
/* The real input, kept reachable by the keyboard rather than display: none. */
.attach-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.attach-text { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.attach-title { font-weight: 600; font-size: 14px; color: var(--text); overflow-wrap: anywhere; }
.attach-filled {
  gap: 12px; min-height: 76px; padding: 8px 10px 8px 8px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--surface-2);
}
.attach-remove { display: inline-flex; width: 44px; height: 44px; align-items: center; justify-content: center; flex: none; }
.shot-thumb { width: 60px; height: 60px; border-radius: 6px; border: 1px solid var(--line); background: var(--bg); object-fit: cover; flex: none; }

/* build.mjs:3143. What happens next: three lines, and the third is the promise. */
.next-card { padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }
.next-lines { display: flex; flex-direction: column; gap: 10px; font-size: 13.5px; line-height: 1.45; color: var(--text-2); }
.next-line { gap: 10px; align-items: flex-start; }
.next-line > svg { margin-top: 2px; flex: none; color: var(--text); }

/* build.mjs:3157. Sent. */
.sent-card { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 24px 0 8px; text-align: center; }
.sent-tick { width: 56px; height: 56px; border-radius: 999px; background: var(--ok); color: var(--bg); justify-content: center; flex: none; }
.sent-text { display: flex; flex-direction: column; gap: 6px; }
.sent-title { font-weight: 600; font-size: 22px; letter-spacing: -0.01em; }
.sent-card .muted { font-size: 14.5px; line-height: 1.45; max-width: 420px; }

/* build.mjs:3213-3215. */
.kind-pill { gap: 5px; flex: none; }
.tiny-pill { height: 20px; font-size: 11px; gap: 4px; }
.unread-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--text); flex: none; }
.unread-dot.slot { background: transparent; }

.shot-row { gap: 10px; padding: 6px 10px 6px 6px; border-radius: 8px; border: 1px solid var(--line); background: var(--surface); max-width: 88%; }

/* build.mjs:3218-3227. The conversation. Your own words on the right, in ink. */
.thread { display: flex; flex-direction: column; gap: 14px; }
.post { display: flex; flex-direction: column; gap: 5px; align-items: flex-start; }
.post.mine { align-items: flex-end; }
.post-body {
  max-width: 88%; padding: 10px 14px; border-radius: 12px; border-bottom-left-radius: 4px;
  background: var(--surface-2); color: var(--text); font-size: 14.5px; line-height: 1.45;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.post.mine .post-body { border-bottom-left-radius: 12px; border-bottom-right-radius: 4px; background: var(--text); color: var(--bg); }
.new-divider { gap: 10px; }
.new-rule { flex: 1; height: 1px; background: var(--line); }
.new-pill { background: var(--text); color: var(--bg); height: 22px; }

/* build.mjs:3228. The reply box. */
.reply-box { display: flex; flex-direction: column; gap: 8px; }
.reply-text { min-height: 72px; }
.reply-foot { gap: 8px; flex-direction: column; align-items: stretch; }
.reply-foot .btn { height: 50px; justify-content: center; font-size: 16px; }
.reply-foot .hint { text-align: center; }

/* build.mjs:3232. Your own threads. */
.my-message-list { padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.my-message-head { padding: 12px 12px 8px; }
.my-message-row { justify-content: space-between; gap: 12px; min-height: 60px; padding: 8px 12px; border-radius: 8px; color: inherit; }
.my-message-row.on { background: var(--surface-2); }
.my-message-row:hover { text-decoration: none; }
.my-message-main { gap: 10px; min-width: 0; }
.my-message-text { display: flex; flex-direction: column; gap: 2px; }
.my-message-title { gap: 8px; font-weight: 600; font-size: 14px; }

/* THE PHONE SHOWS ONE PANE AT A TIME, the desktop both, and the route decides
   which one the phone is on: exactly the shape profiles-settings already uses,
   so the two settings screens behave the same way under a thumb. */
.feedback-panes { display: flex; flex-direction: column; gap: 20px; }
@media (max-width: 899.98px) {
  .mode-list .pane-thread, .mode-thread .pane-list { display: none; }
}

@media (min-width: 900px) {
  .message-box { min-height: 180px; font-size: 15.5px; }
  .attach-empty { min-height: 64px; }
  .next-card { padding: 20px 22px; }
  .next-lines { font-size: 14.5px; }
  .sent-card { padding: 12px 0; }
  .sent-title { font-size: 24px; }
  .thread { gap: 16px; }
  .post-body { max-width: 78%; font-size: 15px; }
  .shot-row { max-width: 78%; }
  .reply-text { min-height: 100px; }
  .reply-foot { flex-direction: row; align-items: center; gap: 14px; }
  .reply-foot .btn { height: 44px; padding: 0 20px; font-size: 14px; width: auto; }
  .reply-foot .hint { text-align: left; }
  .feedback-page { gap: 28px; }
  .feedback-panes { display: grid; grid-template-columns: 620px 1fr; gap: 40px; align-items: start; max-width: 1100px; }
  .feedback-card { padding: 22px 24px; display: flex; flex-direction: column; gap: 20px; }
  .pane-side { display: flex; flex-direction: column; gap: 16px; }
}

/* ==================================================================== admin */

.admin-note { font-size: 13.5px; }
@media (min-width: 900px) {
  .admin-hub .settings-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; max-width: 1100px; }
}

/* build.mjs:3298. The two inboxes, with what is waiting in each. */
.admin-seg > div { gap: 6px; }
@media (min-width: 900px) { .admin-seg { width: 440px; } }

/* build.mjs:3300. One row of the inbox. */
.message-list { padding: 0; display: flex; flex-direction: column; }
.message-row { gap: 12px; min-height: 68px; padding: 10px 12px; border-bottom: 1px solid var(--line-soft); color: inherit; }
.message-row:last-child { border-bottom: 0; }
.message-row:hover { text-decoration: none; }
.message-row-text { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.message-row-who { gap: 8px; font-weight: 500; font-size: 14.5px; overflow-wrap: anywhere; flex-wrap: wrap; }
.message-row.unread .message-row-who { font-weight: 600; }
.message-row.unread .message-row-n { color: var(--text-2); }
.message-row-n { gap: 4px; flex: none; }
.message-row.on { background: var(--surface-2); }

/* build.mjs:3308. */
.thread-head { justify-content: space-between; gap: 12px; }
.thread-head-who { gap: 12px; min-width: 0; }
.thread-head-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.thread-head-email { font-weight: 600; font-size: 14.5px; overflow-wrap: anywhere; }
.thread-gone { padding: 12px 14px; border-radius: 8px; background: var(--surface-2); font-size: 14px; line-height: 1.45; color: var(--text-2); }

@media (min-width: 900px) {
  .admin-panes { display: grid; grid-template-columns: 440px 1fr; gap: 32px; align-items: start; }
  .admin-panes .pane-list { display: flex; flex-direction: column; gap: 12px; }
  .message-list { padding: 6px; gap: 2px; }
  .message-row { border-bottom: 0; border-radius: 8px; min-height: 64px; }
  .thread-head { padding-bottom: 16px; border-bottom: 1px solid var(--line-soft); }
  .thread-head-email { font-size: 16px; }
}
@media (max-width: 899.98px) {
  .admin-panes.mode-list .pane-thread, .admin-panes.mode-thread .pane-list { display: none; }
}

/* ------------------------------------------------------- invite 3356 / 3500 */
.invite-form { padding: 16px; display: flex; flex-direction: column; gap: 14px; }
.invite-form .btn { height: 50px; justify-content: center; font-size: 15px; }
.invite-list { padding: 0 6px; overflow: hidden; }
.invite-row {
  display: grid; grid-template-columns: 32px 1fr auto; gap: 10px; align-items: center;
  min-height: 72px; padding: 10px 0; border-bottom: 1px solid var(--line-soft);
}
.invite-row:last-child { border-bottom: 0; }
.invite-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.invite-email { gap: 8px; font-weight: 600; font-size: 14px; overflow-wrap: anywhere; }
.invite-state { display: flex; flex-direction: column; gap: 3px; align-items: flex-end; }
/* The phone board puts "take it back" under the address, as a full-width
   control, rather than in a fourth column it has no room for (build.mjs:3348). */
.invite-act { grid-column: 2 / -1; }
.invite-revoke { gap: 6px; font-size: 13px; font-weight: 500; min-height: 44px; }

@media (min-width: 900px) {
  /* minmax, not fixed widths: at 900 to about 1000px wide the fixed 400px
     form and the list's fixed columns added up to more than the room, and the
     page scrolled sideways (2026-09-30). Both now give way before the edge. */
  .invite-cols { display: grid; grid-template-columns: minmax(300px, 400px) minmax(0, 1fr); gap: 32px; align-items: start; max-width: 1240px; }
  .invite-left { display: flex; flex-direction: column; gap: 28px; }
  .invite-form { padding: 22px 24px; gap: 18px; }
  .invite-form .btn { height: 48px; }
  .invite-right { display: flex; flex-direction: column; gap: 12px; }
}
/* The list's four columns need about 1200px of window; below that (900 to
   1199) each row keeps the phone's shape, "take it back" under the address,
   which reads well in the narrower column. Squeezing four columns in turned
   an address into one letter per line (2026-09-30). */
@media (min-width: 1200px) {
  .invite-row { grid-template-columns: 40px minmax(0, 1fr) 180px 230px; gap: 14px; min-height: 68px; padding: 10px 12px; }
  .invite-state { align-items: flex-start; }
  .invite-act { grid-column: auto; justify-self: start; }
}

/* -------------------------------------------------------- users 3441 / 3516
 * The phone stacks the whole row under the avatar; the desktop lays it out in
 * eight columns. One tree, and the grid template is the only thing that moves.
 */
.user-list { padding: 0 6px 6px; overflow: hidden; }
.user-row {
  display: grid; grid-template-columns: 32px 1fr; gap: 10px; align-items: start;
  padding: 12px 0; border-bottom: 1px solid var(--line-soft);
}
.user-row:last-child { border-bottom: 0; }
.user-row.revoked .user-avatar, .user-row.revoked .user-who, .user-row.revoked .user-can,
.user-row.revoked .user-cell { opacity: 0.5; }
.user-avatar { display: inline-flex; padding-top: 2px; }
.user-who { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.user-email { gap: 8px; flex-wrap: wrap; font-weight: 600; font-size: 14px; overflow-wrap: anywhere; }
.user-sub { gap: 8px; flex-wrap: wrap; }
.user-cell { display: flex; flex-direction: column; gap: 2px; }
.user-cell-v { font-size: 14px; }
.user-est { gap: 6px; }
.user-controls { gap: 12px; padding-top: 6px; flex-wrap: wrap; }
.user-controls .small-btn { height: 44px; padding: 0 14px; font-size: 13px; gap: 6px; }
.user-delete { gap: 6px; font-size: 13px; font-weight: 500; min-height: 44px; padding: 0 6px; }
.user-head { display: none; }

@media (max-width: 899.98px) {
  /* Everything but the avatar is one stacked column under it, which is what
     the phone board draws (build.mjs:3398). The desktop's separate cells
     become lines in that column. */
  .user-row > .user-can, .user-row > .user-cell, .user-row > .user-controls { grid-column: 2; }
  .user-cell { flex-direction: row; gap: 6px; align-items: baseline; font-size: 13px; }
  .user-cell .hint { font-size: 12px; }
  .user-cell-v { font-size: 13px; color: var(--text-2); }
  .user-paid { flex-wrap: wrap; }
  .user-can { padding-top: 2px; }
}

@media (min-width: 900px) {
  .users-left { display: flex; flex-direction: column; gap: 12px; }
  /* build.mjs:3382 USER_COLS, with one column added for Paid, which the board
     carries only inside its delete confirmation. The address keeps the 1fr:
     it is the longest thing on the row and the only one that cannot be
     shortened without losing which account it is. */
  .user-head, .user-row {
    display: grid;
    grid-template-columns: 40px minmax(220px, 1fr) 88px 112px 128px 96px 124px 176px;
    gap: 12px; align-items: center;
  }
  .user-head { padding: 14px 12px 6px; }
  .user-row { min-height: 68px; padding: 10px 12px; }
  .user-email { font-size: 14.5px; }
  .user-controls { justify-content: flex-end; padding-top: 0; gap: 8px; flex-wrap: nowrap; }
  .user-controls .small-btn { height: 32px; padding: 0 10px; }
  .user-delete { min-height: 32px; }
}

/* ------------------------------------------------- the admin confirmations
 * build.mjs:3421 and 3431. On the desktop board they sit in the right column
 * as cards; live they are a decision, so they are an overlay over the list on
 * the desktop and the bottom sheet on a phone (PhoneAdminUsersRevoke 3519).
 */
.admin-dialog { width: min(520px, 100%); padding: 20px 22px; display: flex; flex-direction: column; gap: 16px; box-shadow: var(--shadow-dialog); }
.admin-dialog.danger { border-color: var(--danger); }
.dialog-body { display: flex; flex-direction: column; gap: 8px; }
.dialog-title { font-weight: 600; font-size: 19px; letter-spacing: -0.01em; }
.dialog-line { font-size: 15px; line-height: 1.45; color: var(--text-2); gap: 8px; flex-wrap: wrap; }
.dialog-acts { display: flex; flex-direction: column; gap: 8px; }
.dialog-acts .btn, .dialog-acts .btn-ghost { height: 50px; justify-content: center; }
.dialog-danger { gap: 8px; font-size: 14px; font-weight: 500; min-height: 44px; justify-content: center; }

@media (min-width: 900px) {
  .dialog-title { font-size: 17px; }
  .dialog-line { font-size: 15.5px; }
  .dialog-acts .btn, .dialog-acts .btn-ghost { height: 44px; }
}
@media (max-width: 899.98px) {
  /* The overlay becomes the sheet: pinned to the bottom edge, where the thumb
     already is, and clear of the home indicator. */
  .overlay-layer.as-sheet { display: flex; align-items: flex-end; }
  .overlay-layer.as-sheet .overlay-panel { width: 100%; }
  .overlay-layer.as-sheet .admin-dialog {
    width: 100%; border-radius: 16px 16px 0 0; border-bottom: 0;
    padding: 18px 16px max(28px, env(safe-area-inset-bottom));
  }
}

/* ------------------------------------------------- 44px, unit 6b's share
 * Every tappable thing on a phone is at least 44px tall (SPEC.md section 5).
 * These are the ones this unit adds whose natural height is smaller.
 */
@media (max-width: 899.98px) {
  .choose-row { min-height: 56px; }
  .kind-seg > div, .admin-seg > div { min-height: 44px; }
  .settings-tile { min-height: 112px; }
  .my-message-row, .message-row { min-height: 60px; }
  .invite-revoke, .user-delete, .attach-remove { min-height: 44px; }
  .reply-foot .btn, .invite-form .btn { min-height: 50px; }
  .admin-seg > div .badge { min-width: 18px; height: 18px; }
}

/* --------------------------------------------- the last few of unit 6b's own
 * Written after the screens, which is why they sit under the block above: the
 * parts a board draws as a one-off rather than as a component.
 */
.settings-tile.still { cursor: default; }
.settings-hub .page-head p { overflow-wrap: anywhere; }
.feedback-send { gap: 14px; flex-wrap: wrap; }
/* NOT flex: 1. The row is column-reverse on a phone (the sentence above the
   button, as phoneFooter draws it), and in a column a flex-basis of 0 is a
   basis on the HEIGHT: the button collapsed to 22px and the 44px audit at 375
   caught it. .phone-footer .btn already gives it its 50px and full width. */
.feedback-send .btn { padding: 0 20px; justify-content: center; flex: none; }
.sent-acts { gap: 10px; justify-content: center; flex-wrap: wrap; }
.sent-acts .btn, .sent-acts .btn-ghost { height: 44px; }
.thread-top { justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.thread-top-id { gap: 10px; }
.mobile-nav-new { height: 44px; padding: 0 8px; font-weight: 600; color: var(--text); }
.admin-page .page-head { max-width: 1100px; }
.invite-done, .users-done { margin: 0; color: var(--text-2); }
.dialog-slot[hidden] { display: none; }

@media (max-width: 899.98px) {
  /* The reply box is pinned to the bottom edge on a phone (PhoneFeedbackThread
     3268 and PhoneAdminThread 3491), so the conversation needs room to scroll
     clear of it. */
  .thread-screen .pad, .admin-messages.mode-thread .pad { padding-bottom: 210px; }
  .thread-screen .reply-box, .admin-messages .pane-thread .reply-box {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 3;
    padding: 12px 16px max(28px, env(safe-area-inset-bottom));
    background: var(--bg); border-top: 1px solid var(--line-soft);
  }
  .feedback-card { padding: 0; background: transparent; border: 0; display: flex; flex-direction: column; gap: 16px; }
  .mode-compose .pane-list { display: none; }
  .mode-thread .pane-list, .mode-thread .pane-next { display: none; }
  .mode-list .pane-thread { display: none; }
  /* None of the four phone boards carries a page head: the bar above already
     says which screen this is, and the desktop sub-sentence would be a second
     copy of the one PhoneAdminHub prints on its own (3480). */
  .admin-page .page-head { display: none; }
  .invite-cols, .admin-panes { display: flex; flex-direction: column; gap: 16px; }
  .admin-panes .pane-list { display: flex; flex-direction: column; gap: 14px; }
}

@media (min-width: 900px) {
  /* .phone-footer unpins itself at 900px but keeps its column direction and
     its full-width button, which is right for every other screen that uses it
     and wrong here: the desktop board (3175) puts Send and the sentence on one
     row inside the card. */
  .phone-footer.feedback-send { flex-direction: row; gap: 14px; }
  .phone-footer.feedback-send .btn { width: auto; height: 44px; padding: 0 20px; font-size: 14px; flex: none; }
  .admin-page { gap: 28px; }
}

/* THE TILE HEIGHTS, now that they are CSS's rather than an inline style, and
   the phone hub's dropped notes. build.mjs:1402 draws the eight hub tiles on
   the phone WITHOUT their notes (settingsTile(...t[2], '', ...)) and with them
   on the desktop, because eight two-line tiles are a screen and eight
   three-line tiles are a scroll. The maker-only tile keeps its note on both,
   as ADMIN_TILE_PHONE does, and so does every tile on the admin hub, which is
   drawn with tile() on both boards. */
.settings-tile { min-height: 112px; }
@media (max-width: 899.98px) {
  .settings-hub .settings-grid:not(.maker-only-grid) .settings-tile .hint { display: none; }
}
@media (min-width: 900px) {
  .settings-hub .settings-tile { min-height: 132px; }
  .admin-hub .settings-tile { min-height: 148px; }
}

/* PhoneFeedback 3186 pins Send and the sentence above it to the bottom edge
   (phoneFooter), and the desktop board (3175) puts the button and the sentence
   on one row inside the card. One tree: .phone-footer already does the pinning
   and unpins itself at 900px, and column-reverse is what puts the sentence
   above the button on the phone and leaves the row alone on the desktop. */
@media (max-width: 899.98px) {
  .feedback-screen .mode-compose .pad { padding-bottom: 150px; }
  .phone-footer.feedback-send { flex-direction: column-reverse; gap: 8px; }
  .phone-footer.feedback-send .hint { text-align: center; }
}

/* PhoneAdminMessages 3484 draws the inbox as a plain list with a rule between
   rows, not as a card, and PhoneAdminThread 3491 drops the avatar from the
   thread head: there is no room for it beside an address on a 375px bar. */
@media (max-width: 899.98px) {
  .message-list, .invite-list, .user-list, .my-message-list { background: transparent; border: 0; padding: 0; }
  .thread-head .avatar { display: none; }
}

/* What an admin action did, printed once above the list it changed. */
.admin-notice { margin: 0; font-size: 14px; line-height: 1.45; color: var(--text); padding: 12px 14px; border-radius: 8px; background: var(--surface-2); }

/* ------------------------------------------------------------ the brand
   The front door's logo and name (components.js authBrand): the mark with its
   background cut away, and "Leech" in ink beside "list" in the app's green.

   THE SAME SIZE IN BOTH THEMES, his ask on 2026-09-15: switching Appearance
   must not make the logo jump. Only the glow changes between them.

   WHY A GLOW ON DARK AT ALL. The leech's body is near-black and the dark
   surface is near-black, so with nothing behind it the body dissolves and the
   screen shows a floating credit card with drips. The glow traces the mark's
   own outline (drop-shadow follows the alpha channel, not the box) and gives
   the body an edge to sit against. On the light theme there is nothing to
   separate, so a glow there is decoration, and a mint haze on near-white reads
   as an image that has not finished loading. Shown to him both ways; he chose
   green on dark, nothing on light. */
/* THE NAME SITS ON THE CENTRE LINE, his ask on 2026-09-15, with the mark
   hanging off its left rather than the pair being centred as a block.

   Three columns, and the outer two are minmax(0, 1fr): equal, and allowed to
   shrink to nothing, so the middle column holds the name exactly on the
   centre line at any width. The mark lives in the left column, pushed to its
   inner edge.

   max-width is what keeps this honest. Absolute positioning got the centring
   right and then ran the mark off the screen on a 320px phone, because
   nothing told it how much room it had. Here the mark is capped at its own
   column less the gap, so it stays 110px while there is space and shrinks
   quietly when there is not, rather than being clipped. */
.auth-brand { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; margin-bottom: 2px; }
.auth-logo { grid-column: 1; justify-self: end; margin-right: 14px; width: 110px; max-width: calc(100% - 14px); height: auto; display: block; }
.auth-name { grid-column: 2; }
.auth-name { font-weight: 700; font-size: 24px; letter-spacing: -0.02em; line-height: 1; }
/* The two tones, wherever the name is drawn: the bar, the front door, and
   anything later. Mint is the same token the tick marks use, and it is bright
   on dark and deep on light, so the word stays legible either way. */
.brand-a { color: var(--text); }
.brand-b { color: var(--ok); }

/* THE GLOW, dark only. The same four shapes the token blocks at the top of
   this file use: :root is dark, the media query is "follow the device", and
   the two [data-theme] rules are the Appearance tile's choice.

   🔁 TO SWITCH TO THE WHITE GLOW (his option 3, kept on 2026-09-15 in case he
   changes his mind): replace the value of --auth-glow below with
     drop-shadow(0 0 14px rgba(255, 255, 255, 0.28)) drop-shadow(0 0 3px rgba(255, 255, 255, 0.35))
   and change nothing else. It is quieter: the mark reads as lit from behind
   rather than as giving off the green. */
:root { --auth-glow: drop-shadow(0 0 18px rgba(65, 220, 165, 0.50)) drop-shadow(0 0 4px rgba(65, 220, 165, 0.35)); }
.auth-logo { filter: var(--auth-glow); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .auth-logo { filter: none; }
}
[data-theme="light"] .auth-logo { filter: none; }
[data-theme="dark"] .auth-logo { filter: var(--auth-glow); }

/* An effect nobody asked to watch. A person who has switched motion off gets
   the mark and no glow at all, which is the honest fallback: the drawing is
   still complete, it just sits flatter. */
@media (prefers-reduced-transparency: reduce) {
  .auth-logo { filter: none; }
}

/* THE FRONT DOOR IS CENTRED, his ask on 2026-09-15. The mark, the name, the
   heading and the sentence under it sit on the card's centre line; the form
   below them stays left, because a field's label belongs above the left edge
   of the box it names and a centred label floats away from it.

   .auth-head is shared with the profile picker and the PIN pane, which is
   deliberate: they are the same front door, one flow, and a heading that jumps
   left after the code screen reads as a different app. */
.auth-brand { justify-content: center; }
.auth-head { align-items: center; text-align: center; }
