/* ════════════════════════════════════════════════════════════════════════
   LEDGER LIGHT — the shell and the screens.

   Ported verbatim from the design pack's own stylesheets, with two changes and
   no third: the pack's private :root palette was removed because
   /static/tokens.css owns colour for every page in the product, and the
   literals in the rules below were replaced by the tokens that already held
   the same values.

   The pack drives mobile from a single `.is-mobile` class rather than media
   queries, so one matchMedia listener reproduces the whole mobile design. Do
   not convert those to @media — the class is what lets the same rules be
   exercised at any width.

   Load ORDER matters: tokens.css, then this file, then the page's own <style>.
   ════════════════════════════════════════════════════════════════════════ */

:root[data-theme="dark"] .is-mobile .main { background: var(--canvas); }
:root[data-theme="dark"] .stream__head.pos { background: linear-gradient(180deg, var(--pos-soft), transparent); }
:root[data-theme="dark"] .stream__head.neg { background: linear-gradient(180deg, var(--neg-soft), transparent); }

* , *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
html, body { min-height: 100%; }
body {
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--f-sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "ss01", "cv05";
}
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
/* …but not when it cannot answer. The line above hands EVERY button a hand
   cursor, disabled ones included, so a dead control still invited the tap —
   found 18/09/2026 on the calendar's arrows at the year zoom, which are
   correctly disabled, correctly faded and genuinely inert, and still looked
   clickable under the pointer. Fixed here rather than on `.cal__arrow`
   because the lie is not the calendar's: it belongs to the rule that made
   it, and the same screen carried it on the zoom label and on Show. */
button:disabled { cursor: default; }
input, textarea, select { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.tnum { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1, "lnum" 1; }
.serif { font-family: var(--f-serif); }
.mono  { font-family: var(--f-mono); }

/* ═════════ APP SHELL ═════════ */
.app {
  display: flex; flex-direction: column;
  min-height: 100vh;
}
.is-mobile .app { min-height: 0; flex: 1; }

/* ——— DESKTOP TOP NAV ——— */
.topnav {
  position: sticky; top: 0; z-index: 50;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: 16px;
  padding: 0 28px;
  background: rgba(var(--paper-rgb),.82);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid var(--line);
}
.is-mobile .topnav { display: none; }
.topnav__left { display: flex; align-items: center; gap: 14px; min-width: 0; }
/* The mark and the wordmark are ONE button — "click the logo, go home", which
   is the oldest convention on the web and the thing Karan asked for on
   09/09/2026. It is a flex item of .topnav__left, so the 14px gap above still
   separates it from the divider. */
.topnav__brand { display: inline-flex; align-items: center; gap: 14px; flex: none; }
.topnav__brand:active { transform: scale(.98); }
.topnav__left .logo-mk + .topnav__logo { margin-inline-start: -6px; }
/* flex: none + nowrap are load-bearing, not tidiness. `.topnav__left` carries
   `min-width: 0` so the business name beside it can ellipsis, and that also
   lets the LOGO be squeezed below its own text — which does not wrap or clip,
   it simply overflows and paints on top of the tabs. Measured at a 780px
   window: the wordmark is 123px wide inside a 93px box and ran to x=187 while
   the nav started at x=145, so "SunoMunshi" and "Home" were drawn over each
   other. */
.topnav__logo { flex: none; white-space: nowrap; font-family: var(--f-serif); font-weight: 600; font-size: 21px; letter-spacing: -.02em; color: var(--ink); line-height: 1; }
.topnav__logo span { color: var(--accent); }
.topnav__div { width: 1px; height: 26px; background: var(--line-strong); flex: none; }
.topnav__biz { display: flex; align-items: center; gap: 9px; min-width: 0; }
.topnav__biz .biz-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--pos); box-shadow: 0 0 0 3px var(--pos-soft); flex: none; }
.topnav__biz .biz-meta { display: flex; flex-direction: column; line-height: 1.18; min-width: 0; }
.topnav__biz .biz-meta b { color: var(--ink); font-weight: 700; font-size: 15px; letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topnav__biz .biz-meta span { font-size: 11px; color: var(--faint); white-space: nowrap; }
/* Same reason: the tabs are the navigation, so they hold their size and the
   things that can afford to give way do the giving. */
.topnav__nav { flex: none; display: flex; gap: 2px; margin-left: 8px; }
.tnav-item { display: flex; align-items: center; gap: 8px; padding: 8px 13px; border-radius: var(--r-sm); font-size: 13.5px; font-weight: 500; color: var(--muted); transition: background .14s, color .14s; }
.tnav-item svg { width: 18px; height: 18px; }
.tnav-item:hover { background: var(--canvas); color: var(--ink); }
.tnav-item[aria-current="true"] { background: var(--accent-soft); color: var(--accent-ink); }
.tnav-item[aria-current="true"] svg { color: var(--accent); }
.tnav-item__count { font-size: 11px; font-weight: 700; color: var(--faint); font-variant-numeric: tabular-nums; }
.tnav-item[aria-current="true"] .tnav-item__count { color: var(--accent); }
/* The person's corner: one avatar, same look as the phone bar's so the two
   sizes of this page do not have two ideas about where your account lives.
   34px to sit level with the bordered icon button beside it. */
.topnav__av { flex: none; width: 34px; height: 34px; margin-left: 2px; border-radius: 50%; background: var(--accent-soft); color: var(--accent); font-size: 12.5px; font-weight: 700; display: grid; place-items: center; letter-spacing: .02em; }
.topnav__av:hover { background: var(--accent); color: var(--on-accent); }
.topnav__av-name { display: none; }
/* Labelled where there is room (Karan, 23/09/2026: "label icon-only buttons").
   The search button says Search, and the person's button says their first name
   INSTEAD of their initials — never both. Below 1000px the bar has no room for
   either word and both go back to the icon, as on the phone. */
@media (min-width: 1000px) {
  /* Two classes, because the generic .icon-btn rule sits later in this sheet
     and would otherwise pin the width back to 36px. */
  .icon-btn.topnav__search { width: auto; padding: 0 13px 0 10px; grid-auto-flow: column; gap: 7px; font-size: 13px; font-weight: 500; }
  .topnav__av { width: auto; max-width: 160px; padding: 0 14px; border-radius: 999px; letter-spacing: 0; font-size: 13px; font-weight: 600; }
  .topnav__av-ini { display: none; }
  .topnav__av-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
@media (max-width: 999px) { .topnav__search-label { display: none; } }
@media (max-width: 1180px) { .topnav__biz .biz-meta span { display: none; } }
@media (max-width: 940px) { .topnav__div, .topnav__biz { display: none; } }
/* The last thing to give way, and the reason there IS a last thing: below this
   the wordmark, four tabs and four controls do not fit on one bar at any type
   size, and something has to go rather than overlap. The mark stays, so the
   corner is still ours — this is the same trade every app bar makes at a
   narrow window. Sized off the measurement above: 900px is where the bar stops
   fitting, not a round number. */
/* The wordmark goes first, not the shop's name. Between 940 and 1180px the
   name was being ellipsised to "Shalini General …" while 123px of wordmark sat
   beside it — and the wordmark says the same thing on every screen in the
   product, where the name is the one piece of text that tells this shop the
   books are theirs. The round mark stays, so the brand is still present and
   still the button that goes home. */
@media (max-width: 1180px) { .topnav__logo { display: none; } }

/* ——— MOBILE TOP BAR ——— */
.topbar { display: none; }
.is-mobile .topbar {
  display: flex; align-items: center; gap: 10px;
  height: var(--topbar-h); padding: 0 16px;
  background: rgba(var(--paper-rgb),.86);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 40; flex: none;
}
.topbar__logo { font-family: var(--f-serif); font-weight: 600; font-size: 19px; letter-spacing: -.02em; color: var(--ink); }
.topbar__logo span { color: var(--accent); }
.topbar__spacer { flex: 1; }
.topbar__icon { width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center; color: var(--muted); }
/* The avatar that replaced the phone bar's theme and sign-out icons. Same
   round tile as every other avatar on the page, so it reads as "you" without a
   label, and it is the seat Settings took when it left the bottom bar. */
.topbar__av { width: 32px; height: 32px; border-radius: 50%; background: var(--accent-soft); color: var(--accent); font-size: 12px; font-weight: 700; display: grid; place-items: center; letter-spacing: .02em; }
.topbar__av:hover { background: var(--accent); color: var(--on-accent); }
/* Whose account this is. Reads as a heading rather than a row, because it is
   not somewhere to tap. */
.acct-who { padding: 2px 2px 10px; font-size: 15px; font-weight: 700; color: var(--ink); letter-spacing: -.01em; }
.acct-row { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; padding: 13px 14px; border-radius: 11px; background: var(--paper-2); text-align: left; }
.acct-row:hover { background: var(--canvas); }
.acct-row__l { font-size: 14.5px; font-weight: 600; color: var(--ink); }
.acct-row__s { font-size: 11.5px; color: var(--faint); }
.topbar__icon:hover { background: var(--canvas); color: var(--ink); }

/* ═════════ MAIN ═════════ */
.main {
  min-width: 0;
  background:
    radial-gradient(1000px 540px at 100% -5%, var(--glow) 0%, transparent 55%),
    var(--canvas);
}
.is-mobile .main { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; background: var(--canvas); }
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 26px 28px 64px; }
.is-mobile .wrap { padding: 16px 16px calc(var(--botnav-h) + 22px); }

/* view transition */
.view { animation: viewIn .26s var(--spring); }
@keyframes viewIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .view, .stagger > * { animation: none !important; } }

.stagger > * { animation: rise .42s var(--spring) backwards; }
.stagger > *:nth-child(1){animation-delay:.02s}
.stagger > *:nth-child(2){animation-delay:.05s}
.stagger > *:nth-child(3){animation-delay:.08s}
.stagger > *:nth-child(4){animation-delay:.11s}
.stagger > *:nth-child(5){animation-delay:.14s}
.stagger > *:nth-child(6){animation-delay:.17s}
.stagger > *:nth-child(7){animation-delay:.20s}
.stagger > *:nth-child(8){animation-delay:.23s}
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ═════════ PAGE HEAD ═════════ */
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
/* On a phone the title and the layout switcher cannot share a line without
   squeezing the shop's name into three wrapped lines, so the switcher drops
   below. It is NOT stretched to the width there: it was until 09/09/2026, and
   that made the same control read as two different controls depending on the
   screen — "why is this soo big in mobile but small in desktop" (Karan). The
   pack has no stretch rule either; it draws one `.seg` at one size. */
.is-mobile .page-head { flex-wrap: wrap; align-items: flex-start; gap: 12px; }
.is-mobile .page-head > * { min-width: 0; }
.page-title { font-family: var(--f-serif); font-weight: 500; font-size: 30px; letter-spacing: -.02em; line-height: 1.05; }
.is-mobile .page-title { font-size: 24px; }
.page-sub { color: var(--muted); font-size: 13.5px; margin-top: 4px; }
.page-head__actions { display: flex; gap: 8px; align-items: center; }
.page-head--stack { flex-direction: column; align-items: flex-start; gap: 12px; }

/* ═════════ BUTTONS ═════════ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 15px; border-radius: var(--r-sm);
  font-weight: 600; font-size: 13.5px; white-space: nowrap;
  border: 1px solid transparent;
  transition: background .15s var(--ease), color .15s, border-color .15s, box-shadow .15s, transform .1s var(--spring);
}
.btn:active { transform: scale(.97); }
.btn--primary { background: var(--accent); color: var(--on-accent); box-shadow: var(--sh-1), inset 0 1px 0 rgba(var(--glass-hi-rgb),.2); }
.btn--primary:hover { background: var(--accent-2); }
.btn--primary:disabled { background: var(--ghost); box-shadow: none; cursor: not-allowed; transform: none; }
.btn--ghost { background: var(--paper); color: var(--ink-2); border-color: var(--line-strong); box-shadow: var(--sh-1); }
.btn--ghost:hover { background: var(--canvas); border-color: var(--ghost); }
.btn--quiet { background: transparent; color: var(--muted); }
.btn--quiet:hover { background: var(--canvas); color: var(--ink); }
.btn--danger { background: var(--paper); color: var(--neg); border-color: var(--neg-line); }
.btn--danger:hover { background: var(--neg-soft); }
.btn--danger-solid { background: var(--neg); color: var(--on-accent); }
.btn--danger-solid:hover { filter: brightness(.95); }
/* WhatsApp reminder — the customer-facing green nudge on a Due card. Tokens
   only (never a raw green) so it stays calm beside Mark paid in both themes. */
.btn--wa { background: rgba(var(--pos-rgb), .12); color: var(--pos); border-color: rgba(var(--pos-rgb), .40); box-shadow: var(--sh-1); display: inline-flex; align-items: center; gap: 5px; }
.btn--wa:hover:not(:disabled) { background: rgba(var(--pos-rgb), .20); }
/* The pack disables a red button until a delete is confirmed but only gives a
   disabled PRIMARY its dead look, so a dead red button read as live and
   invited the click it was refusing. Same treatment as the primary; ghost
   too, because a check button is disabled while its answer is on the way. */
.btn--danger-solid:disabled { background: var(--ghost); box-shadow: none; cursor: not-allowed; transform: none; filter: none; }
.btn--ghost:disabled { color: var(--faint); box-shadow: none; cursor: not-allowed; transform: none; }
.btn--sm { padding: 6px 11px; font-size: 12.5px; border-radius: var(--r-xs); }
.btn--lg { padding: 13px 20px; font-size: 15px; }
.icon-btn { width: 36px; height: 36px; border-radius: var(--r-xs); display: grid; place-items: center; color: var(--muted); border: 1px solid transparent; transition: background .15s, color .15s; }
.icon-btn:hover { background: var(--canvas); color: var(--ink); }
.icon-btn--bordered { border-color: var(--line-strong); background: var(--paper); box-shadow: var(--sh-1); }

/* ═════════ CHIP / SEGMENTED / BADGE ═════════ */
.seg { display: inline-flex; gap: 3px; padding: 4px; background: var(--canvas-2); border-radius: var(--r-sm); border: 1px solid var(--line-strong); }
.seg button { padding: 8px 16px; border-radius: 8px; font-size: 13.5px; font-weight: 600; color: var(--muted); transition: all .15s var(--ease); white-space: nowrap; }
.seg button:hover { color: var(--ink); }
.seg button[aria-pressed="true"] { background: var(--paper); color: var(--ink); box-shadow: var(--sh-2); }
.seg button .seg-n { opacity: .55; margin-left: 4px; font-variant-numeric: tabular-nums; }

.chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 999px; font-size: 12.5px; font-weight: 600; color: var(--muted); background: var(--paper); border: 1px solid var(--line-strong); transition: all .15s; white-space: nowrap; }
.chip:hover { border-color: var(--ghost); color: var(--ink); }
.chip[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
.chip .chip-n { opacity: .6; font-variant-numeric: tabular-nums; }

.badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: .01em; background: var(--canvas); color: var(--muted); border: 1px solid var(--line); white-space: nowrap; }
/* A badge with nothing true to say is hidden rather than drawn empty: the
   hero's direction badge, over a figure that carries no direction. */
.badge[hidden] { display: none; }
.badge--admin { background: var(--accent-soft); color: var(--accent-ink); border-color: var(--accent-line); }
.badge--staff { background: var(--sky-soft); color: var(--sky); border-color: transparent; }
.badge--pos { background: var(--pos-soft); color: var(--pos); border-color: var(--pos-line); }
.badge--neg { background: var(--neg-soft); color: var(--neg); border-color: var(--neg-line); }
.badge--amber { background: var(--amber-soft); color: var(--amber); border-color: transparent; }
/* OURS, not the pack's: the filled half of the pair, for a limit a shop has
   already crossed as opposed to one it is approaching. The two were amber and
   RED until 09/09/2026, which spent a second hue on a difference the two words
   beside it already make. */
.badge--amber-strong { background: var(--amber); color: var(--on-amber); border-color: var(--amber); }
.badge--sky { background: var(--sky-soft); color: var(--sky); border-color: transparent; }

/* money tone text */
.t-pos { color: var(--pos); }
.t-neg { color: var(--neg); }
.t-ink { color: var(--ink); }
.t-muted { color: var(--muted); }

/* ═════════ AVATAR ═════════ */
/* ONE TONE FOR EVERY TILE, and it lives here so all five surfaces that draw
   an avatar agree — the party row, an attention card, a Streams row, the
   account hero and the layout miniature.

   It carried a per-name hue until 09/09/2026 (`avatarHue`, a hash into 360
   hues) and that bought nothing. The hash collided in plain sight — two
   parties on one screen drew pixel-identical tiles — and THREE of the five
   surfaces had no background rule at all, so the hue was computed, written
   into a style attribute and thrown away. What it did cost was two more
   colours beside the six that mean something. Slate says "this is a party"
   and gets out of the way of green, red and amber.

   It is also the ground a profile photo will sit in, which a per-name hue
   behind a photo would only have shown as a rim. */
.avatar {
  border-radius: 13px; display: grid; place-items: center; font-weight: 700; flex: none;
  letter-spacing: -.01em; position: relative; overflow: hidden;
  background: var(--canvas-2); color: var(--ink-2);
}
.avatar::after { content:''; position:absolute; inset:0; background: linear-gradient(180deg, rgba(var(--glass-hi-rgb),.35), transparent 50%); }
/* A party's photo (G11) lies over the initials, which stay underneath as the
   fallback when it cannot load. Under the gloss above, so a photo sits in the
   same finish as a tile of initials. */
.avatar__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* The party page's avatar is the button to its photo: the whole square is the
   tap target, the camera mark on its corner says what it does. The mark sits
   outside the avatar's rounded clip, which is why the button, not the avatar,
   carries it. */
.phero__avbtn { position: relative; flex: none; display: block; padding: 0; border: 0; background: none; border-radius: 13px; cursor: pointer; }
.phero__cam {
  position: absolute; right: -5px; bottom: -5px; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; background: var(--paper); color: var(--ink-2);
  border: 1px solid var(--line); box-shadow: var(--sh-1);
}
.phero__avbtn:hover .phero__cam { color: var(--accent); border-color: var(--accent); }

/* ═════════ SPARKLINE ═════════ */
.spark { display: block; }
.spark path.line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.spark path.area { stroke: none; opacity: .14; }

/* ═════════ SEARCH ═════════ */
.search { position: relative; }
.search svg { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--faint); pointer-events: none; }
.search input {
  width: 100%; padding: 12px 14px 12px 42px;
  background: var(--paper); border: 1px solid var(--line-strong);
  border-radius: var(--r-sm); font-size: 14.5px; color: var(--ink);
  box-shadow: var(--sh-1); transition: border-color .15s, box-shadow .15s;
}
.search input::placeholder { color: var(--faint); }
.search input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.search .clr { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center; color: var(--faint); }
.search .clr:hover { background: var(--canvas); color: var(--ink); }

/* ═════════ CARD ═════════ */
.card { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-1); }

/* ═════════ MODAL / SHEET ═════════ */
/* `display: none` where the pack has `display: grid`: its sheets are created
   at the moment they open and destroyed on close, ours live in the page and
   are toggled by `.open`. Everything else in the rule is the pack's. */
.scrim { position: fixed; inset: 0; z-index: 500; background: rgba(var(--scrim-rgb),.34); backdrop-filter: blur(3px); display: none; place-items: center; padding: 24px; animation: fade .16s var(--ease); }
.scrim.open { display: grid; }
.is-mobile .scrim { padding: 0; align-items: flex-end; }
/* The one confirmation (askConfirm) is asked FROM other windows — the Edit
   window's bin among them — so it sits above every sheet. At their level,
   whichever came later in the page was drawn on top, and the Edit window hid
   "Delete this entry?" under itself (browser walk, 11/09/2026). Still below
   the command palette and the toasts, which never raise it. */
.scrim--ask { z-index: 550; }
.sheet {
  background: var(--paper); border-radius: var(--r-xl);
  width: 100%; max-width: 540px; max-height: calc(100vh - 48px);
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: var(--sh-pop); border: 1px solid var(--line);
  animation: pop .24s var(--spring);
}
.is-mobile .sheet { max-width: 100%; max-height: 94%; border-radius: 26px 26px 0 0; animation: up .26s var(--spring); }
.sheet__grab { display: none; }
.is-mobile .sheet__grab { display: block; width: 38px; height: 4px; border-radius: 999px; background: var(--line-strong); margin: 9px auto 2px; flex: none; }
.sheet__head { padding: 18px 22px 14px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border-bottom: 1px solid var(--line-soft); }
.sheet__title { font-family: var(--f-serif); font-weight: 500; font-size: 21px; letter-spacing: -.01em; }
.sheet__body { padding: 20px 22px; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; }
.sheet__foot { padding: 14px 22px calc(14px + var(--safe-bot)); border-top: 1px solid var(--line-soft); display: flex; justify-content: flex-end; gap: 9px; background: var(--paper-2); }
/* The red panel the pack puts above a type-to-confirm delete (its delete-party
   sheet), as a class instead of the pack's inline style. */
.dangerpanel { padding: 14px; background: var(--neg-soft); border: 1px solid var(--neg-line); border-radius: var(--r-sm); color: var(--neg); font-size: 13.5px; line-height: 1.5; }
.dangerpanel__title { font-weight: 700; margin-bottom: 6px; }
.dangerpanel + .field { margin-top: 16px; }
/* font-size: the pack closes with a 16px icon, ours with a literal ×. */
.sheet__close { width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; color: var(--faint); font-size: 19px; line-height: 1; }
.sheet__close:hover { background: var(--canvas); color: var(--ink); }

@keyframes fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes pop { from { opacity: 0; transform: scale(.96) translateY(10px) } to { opacity: 1; transform: none } }
@keyframes up { from { transform: translateY(100%) } to { transform: none } }

/* ═════════ FORM ═════════ */
/* TWO SPELLINGS, ONE RULE. The pack names a form row's parts with classes;
   this app's forms name them by element inside `.field`, which is several
   hundred labels and inputs that would gain a class and change nothing else.
   So each rule takes both spellings, and each pair stays together — adding a
   rule for one spelling only is what splits a look in half.
   DESCENDANT, not child: the phone modal's country-code split, the operator
   rename row and the admin rows built in JS all nest their input one level
   deeper, and a `>` would drop the styling from exactly those three. The
   amount box is excluded by name because it is a field with its own skin. */
.field { display: flex; flex-direction: column; gap: 7px; }
.field__label,
.field label { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.field__hint { font-size: 12px; color: var(--faint); }
.input, .select, .textarea,
.field input:not(.amountbox__in), .field select, .field textarea {
  background: var(--canvas); border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  padding: 11px 13px; font-size: 14.5px; color: var(--ink); width: 100%; transition: border-color .15s, box-shadow .15s, background .15s;
}
.input:focus, .select:focus, .textarea:focus,
.field input:not(.amountbox__in):focus, .field select:focus, .field textarea:focus { outline: none; background: var(--paper); border-color: var(--accent); box-shadow: var(--ring); }
.input::placeholder,
.field input::placeholder, .field textarea::placeholder { color: var(--faint); }
.field textarea { resize: none; min-height: 84px; }
/* Four things the pack's sample forms have no example of: a required marker,
   a select whose native arrow has been suppressed (--select-arrow is defined
   in both palettes for exactly this), the option list behind it, and the
   country-code / number split in the phone sheet — a plain `<div>` with no
   rule at all until now, which stacked the two full-width. */
.field .req { color: var(--neg); font-weight: 700; margin-left: 2px; }
.field select { appearance: none; background-image: var(--select-arrow); background-repeat: no-repeat; background-position: right 13px center; padding-right: 34px; }
.field select option { background: var(--paper); }
/* The country code carries its own arrow padding, so the column has to hold
   "India +91" AND that gutter — at 104px the last digit sat under the arrow. */
.phone-split { display: grid; grid-template-columns: 124px 1fr; gap: 8px; }
.phone-modal-hint { font-size: 12px; color: var(--faint); line-height: 1.45; }
.phone-remove { align-self: flex-start; padding: 0; font-size: 12.5px; font-weight: 600; color: var(--neg); }
.phone-remove:hover { text-decoration: underline; }
/* A `.field` is a flex column, so a button inside one stretches the full width
   and starts reading as a second primary action. These are subordinate — they
   sit at their own width, on the left, where they were before the field
   gained a layout. */
.field > .btn { align-self: flex-start; }

/* ═════════ TOAST ═════════ */
.toast-wrap { position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%); z-index: 700; display: flex; flex-direction: column; gap: 8px; align-items: center; pointer-events: none; }
.is-mobile .toast-wrap { bottom: calc(var(--botnav-h) + 18px); }
.toast {
  display: flex; align-items: center; gap: 10px; padding: 11px 15px 11px 13px;
  background: var(--toast-bg); color: var(--toast-ink); border-radius: var(--r); font-size: 13.5px; font-weight: 500;
  box-shadow: var(--sh-3); animation: toastIn .3s var(--spring); pointer-events: auto;
}
.toast .toast-ic { width: 22px; height: 22px; border-radius: 7px; display: grid; place-items: center; flex: none; }
.toast--ok .toast-ic { background: var(--pos); color: var(--on-accent); }
.toast--err .toast-ic { background: var(--neg); color: var(--on-accent); }
@keyframes toastIn { from { opacity: 0; transform: translateY(14px) scale(.96) } to { opacity: 1; transform: none } }

/* ═════════ SKELETON ═════════ */
.sk { position: relative; overflow: hidden; background: var(--canvas-2); border-radius: 7px; }
.sk::after { content:''; position:absolute; inset:0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, rgba(var(--paper-rgb),.7), transparent); animation: shimmer 1.25s infinite; }
@keyframes shimmer { 100% { transform: translateX(100%) } }

/* ═════════ EMPTY ═════════ */
.empty { padding: 56px 24px; text-align: center; }
.empty__ic { width: 56px; height: 56px; margin: 0 auto 16px; border-radius: 16px; display: grid; place-items: center; background: var(--accent-soft); color: var(--accent); }
.empty__title { font-family: var(--f-serif); font-weight: 500; font-size: 19px; margin-bottom: 6px; }
.empty__desc { color: var(--muted); font-size: 13.5px; max-width: 34ch; margin: 0 auto 18px; }

/* ═════════ BOTTOM NAV (mobile) ═════════ */
.botnav { display: none; }
.is-mobile .botnav {
  position: absolute; bottom: 0; left: 0; right: 0;
  height: calc(var(--botnav-h) + var(--safe-bot)); padding-bottom: var(--safe-bot);
  background: rgba(var(--paper-rgb),.92); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid var(--line);
  display: grid; align-items: stretch; z-index: 45;
  /* ONE destination each side of the notch, and the SAME bar for every role.
     A bar with a docked centre FAB splits evenly — one destination each side
     or two; an odd count leans, and the lean is what a person sees even when
     the plus itself is at the exact centre.

     This bar had FIVE destinations, then FOUR, then — when Activity became a
     view inside the Ledger on 16/09/2026 — THREE, which rendered as
     `Home Ledger | + | Settings`: 2 on the left against 1 on the right. The
     plus still measured dead centre (245px of a 490px bar, measured
     18/09/2026), and it still looked wrong, because centring an object is not
     the same as balancing a composition. Karan photographed the gap.

     Settings left the bar rather than a fourth destination being invented to
     fill the seat. It is a monthly errand and the bar is for the three things
     a shop does hourly; it now lives behind the avatar, top right, with the
     rest of a person's own settings. Desktop is untouched — its nav is a row
     with no notch to balance around, and Settings is still a tab there.

     There is nothing to re-order any more: the markup reads Home, FAB, Ledger
     and renders in that order, so the rule that used to push Ledger past the
     FAB for non-admins is gone with the role-specific template it partnered.
     `test_botnav_role_columns.py` now fails an odd bar for ANY role, so this
     cannot drift back by someone removing a destination. */
  grid-template-columns: 1fr 64px 1fr;
}
.botnav__btn { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; color: var(--faint); font-size: 10px; font-weight: 600; }
.botnav__btn svg { width: 23px; height: 23px; }
.botnav__btn[aria-current="true"] { color: var(--accent); }
.botnav__fab { margin: auto; width: 54px; height: 54px; border-radius: 18px; background: var(--accent); color: var(--on-accent); display: grid; place-items: center; box-shadow: var(--sh-2), inset 0 1px 0 rgba(var(--glass-hi-rgb),.25); transition: transform .14s var(--spring); }
.botnav__fab:active { transform: scale(.92); }

/* ═════════ COMMAND PALETTE ═════════ */
.cmdk-scrim { position: fixed; inset: 0; z-index: 600; background: rgba(var(--scrim-rgb),.32); backdrop-filter: blur(3px); display: flex; justify-content: center; align-items: flex-start; padding: 12vh 20px 20px; animation: fade .14s; }
.cmdk { width: 100%; max-width: 580px; background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-pop); overflow: hidden; animation: pop .2s var(--spring); }
.cmdk__in { display: flex; align-items: center; gap: 12px; padding: 16px 18px; border-bottom: 1px solid var(--line-soft); }
.cmdk__in svg { color: var(--faint); flex: none; }
.cmdk__in input { flex: 1; border: 0; background: transparent; font-size: 17px; outline: none; }
.cmdk__in input::placeholder { color: var(--faint); }
.cmdk__in kbd { font-family: var(--f-mono); font-size: 10.5px; color: var(--faint); border: 1px solid var(--line); border-radius: 6px; padding: 2px 6px; }
.cmdk__list { max-height: 52vh; overflow-y: auto; padding: 8px; }
.cmdk__sec { font-size: 10.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--faint); padding: 10px 12px 6px; }
.cmdk__row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: var(--r-sm); cursor: pointer; }
.cmdk__row[data-active="true"] { background: var(--accent-soft); }
.cmdk__row .cmdk-ic { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; background: var(--canvas); color: var(--muted); flex: none; }
.cmdk__row[data-active="true"] .cmdk-ic { background: var(--paper); color: var(--accent); }
.cmdk__row .cmdk-main { flex: 1; min-width: 0; }
.cmdk__row .cmdk-t { font-size: 14px; font-weight: 500; }
.cmdk__row .cmdk-s { font-size: 11.5px; color: var(--faint); }
.cmdk__row .cmdk-amt { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; }
.cmdk__empty { padding: 34px; text-align: center; color: var(--faint); font-size: 13px; }

/* utility */
.row { display: flex; align-items: center; gap: 10px; }
.row-wrap { flex-wrap: wrap; }
.between { justify-content: space-between; }
.grow { flex: 1; min-width: 0; }
.eyebrow { font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }
.divline { height: 1px; background: var(--line-soft); }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 6px; border: 2px solid var(--canvas); }
::-webkit-scrollbar-thumb:hover { background: var(--ghost); }

/* ════════════════════════════════════════════════════════════════
   SunoMunshi — screen-specific styles
   Home (Pulse / Streams / Feed), Party detail, Add, History, Settings
   ════════════════════════════════════════════════════════════════ */

/* ═════════ MONEY HERO (shared) ═════════ */
.hero {
  display: grid; grid-template-columns: 1.15fr 1fr; gap: 0;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-xl);
  box-shadow: var(--sh-2); overflow: hidden; margin-bottom: 18px;
}
.is-mobile .hero { grid-template-columns: 1fr; }
.hero__net { padding: 24px 26px; position: relative; }
.hero__net::after { content:''; position: absolute; right: 0; top: 18px; bottom: 18px; width: 1px; background: var(--line-soft); }
.is-mobile .hero__net::after { display: none; }
.hero__eyebrow { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.hero__net-val .cur { white-space: nowrap; }
.hero__net-val { font-family: var(--f-serif); font-weight: 500; font-size: 46px; line-height: .98; letter-spacing: -.025em; display: flex; align-items: baseline; gap: 2px; }
.is-mobile .hero__net-val { font-size: 38px; }
/* The pack's markup called this span `.cur`; ours comes out of rs(), the
   page's ONE money renderer, which names it `.rs`. Both are styled so the
   rule survives either — deleting the pack's name would silently unstyle
   any screen still using its markup. */
.hero__net-val .cur,
.hero__net-val .rs { font-size: .56em; color: var(--faint); font-weight: 500; margin-inline-end: .1em; }
.hero__net-val .sgn { color: var(--pos); }
.hero__net-cap { color: var(--muted); font-size: 13px; margin-top: 10px; display: flex; align-items: center; flex-wrap: wrap; gap: 7px; }
.hero__spark { margin-top: 16px; }
/* The sparkline is generated at a fixed 320x42 so its path coordinates are
   whole numbers, then scaled by its viewBox to whatever the card actually
   is. Without this it overflowed the card on any screen narrower than 320px
   of content — every phone. */
.hero__spark svg { display: block; width: 100%; height: auto; }

.hero__split { display: grid; grid-template-rows: 1fr 1fr; }
.is-mobile .hero__split { grid-template-rows: none; grid-template-columns: 1fr; border-top: 1px solid var(--line-soft); }
.hero__cell { padding: 18px 24px; display: flex; align-items: center; gap: 14px; }
.hero__cell + .hero__cell { border-top: 1px solid var(--line-soft); }
.is-mobile .hero__cell + .hero__cell { border-top: 1px solid var(--line-soft); border-left: 0; }
.hero__cell-ic { width: 40px; height: 40px; border-radius: 12px; display: grid; place-items: center; flex: none; }
.hero__cell-ic.pos { background: var(--pos-soft); color: var(--pos); }
.hero__cell-ic.neg { background: var(--neg-soft); color: var(--neg); }
.hero__cell-l { font-size: 12px; color: var(--muted); font-weight: 600; margin-bottom: 3px; }
.hero__cell-v { font-family: var(--f-serif); font-weight: 500; font-size: 23px; letter-spacing: -.01em; line-height: 1; }
.hero__cell-s { font-size: 11.5px; color: var(--faint); margin-top: 4px; }
/* Advances: their own lines, under both columns, across the whole card. */
.hero__adv { grid-column: 1 / -1; border-top: 1px solid var(--line-soft); padding: 12px 24px; display: flex; flex-wrap: wrap; gap: 8px 28px; }
.hero__adv-line { display: flex; align-items: baseline; gap: 8px; font-size: 13px; }
.hero__adv-l { color: var(--muted); font-weight: 600; }
.hero__adv-v { font-family: var(--f-serif); font-weight: 500; font-size: 16px; color: var(--ink); }
.hero__adv-s { color: var(--faint); font-size: 11.5px; }
.hero__adv-net { flex-basis: 100%; color: var(--faint); font-size: 11.5px; }

/* mini stat row (Pulse) */
.minis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 18px; }
/* A shop without Kharcha has no Spent card: two cards, half each. */
.minis.minis--two { grid-template-columns: repeat(2, 1fr); }
/* On a phone a third of the width cannot hold a lakh figure, so Spent takes
   its own row under the other two. */
.is-mobile .minis { grid-template-columns: repeat(2, 1fr); }
.is-mobile .mini--spent { grid-column: 1 / -1; }
/* A day with no activity at all shows nothing rather than three zeros. Its own
   class, never sec12-hidden — that one is renderHomeLayout's. */
.minis.is-quiet { display: none; }
.mini { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r); padding: 15px 16px; box-shadow: var(--sh-1); display: flex; flex-direction: column; gap: 6px; }
.mini__l { font-size: 11.5px; color: var(--muted); font-weight: 600; display: flex; align-items: center; gap: 7px; white-space: nowrap; }
.mini__l .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
/* The pack set these two colours with an inline style attribute. SEC-12
   still permits that, but new code uses a class so the eventual
   style-src tightening has nothing left to migrate here. */
.dot--amber { background: var(--amber); }
.dot--pos   { background: var(--pos); }
.dot--neg   { background: var(--neg); }
.mini__v { font-family: var(--f-serif); font-weight: 500; font-size: 24px; letter-spacing: -.01em; line-height: 1; }
.mini__s { font-size: 11.5px; color: var(--faint); }

/* ═════════ ATTENTION STRIP (Pulse) ═════════ */
/* A shop with nothing pressing sees no rail at all — see .minis.is-quiet
   for why this is not sec12-hidden. */
/* A staff account is shown no figure that spans the whole shop — Karan's
   ruling, given more than once. The server refuses to compute them
   (sees_shop_totals in api/dashboard_routes.py), so these blocks would have
   nothing to print anyway; this is the screen agreeing with that rather than
   leaving an empty hero where the shop's position used to be. It is a class
   of its own and NOT `sec12-hidden`, because on the hero and the "today"
   cards that class belongs to renderHomeLayout() alone. */
body[data-role="staff"] .shop-total { display: none !important; }

/* The fold under the rail: every open promise, searchable, closed by default.
   One line when shut, so the front page is the rail and not two lists of the
   same three facts. */
/* The two tabs sit where the block's title was — the title IS the choice now,
   so there is no heading above them repeating whichever one is on. */
.attn__tabs { flex-wrap: wrap; }
.attn__tabs button[hidden] { display: none; }
/* One pane at a time. The rail keeps `display: flex` when it is the one on
   (Karan: "keep the scroll feature"), so it is switched by its own class
   rather than by a shared `.pane` rule that would flatten it to a block. */
.attn__rail.is-off { display: none; }
/* BOTH panes scroll sideways (Karan, 12/09/2026: "wasn't this supposed to be
   swipeable", on top of his earlier "keep the scroll feature" recorded at
   `.attn__rail` above). This pane wrapped until then, on the reasoning that the
   rail is a handful while this can be forty promises and a horizontal scroll of
   forty reads as a filing cabinet -- that was a judgment about HIS surface, and
   he has ruled. The search, filter and sort bar directly above is what reaches a
   distant promise; the scroll is for browsing, not for finding.
   Deliberately the same declarations as `.attn__rail`: `.attn-card` already
   carries `flex: 0 0 292px` and `scroll-snap-align: start`, which a grid
   container ignored, so the two panes cannot drift apart in card width. */
.attn__grid { display: flex; gap: 12px; overflow-x: auto; padding: 4px 2px 8px; scroll-snap-type: x mandatory; }
.attn__grid::-webkit-scrollbar { height: 0; }
/* The phone gets the same rail rather than a stacked column: one thumb flick is
   the gesture he asked for, and it matches the attention rail on the same page. */
.is-mobile .attn__grid { display: flex; }
.due-body { display: none; }
.due-body.is-open { display: block; }

.attn { margin-bottom: 22px; }
.attn.is-empty { display: none; }
/* Folded until a tab is tapped (Karan, 25/09/2026). The chevron says which way a
   tab will go; Close sits beside the tabs on a laptop and under the cards on a
   phone, and neither is drawn while there is nothing open to close. */
.attn__chev::after { content: "\25BE"; margin-left: 6px; opacity: .6; }
.attn__tabs button[aria-expanded="true"] .attn__chev::after { content: "\25B4"; }
.attn__close { font-size: 13.5px; padding: 8px 12px; flex: none; }
/* Folded, both tabs read as buttons to press rather than one chosen, one not. */
.attn.is-folded .attn__tabs button { background: var(--paper); color: var(--ink); }
.attn__close-row { display: none; justify-content: flex-end; margin-top: 10px; }
.attn.is-folded .attn__close { display: none; }
.is-mobile .attn__close--side { display: none; }
.is-mobile .attn:not(.is-folded) .attn__close-row { display: flex; }
.attn__rail { display: flex; gap: 12px; overflow-x: auto; padding: 4px 2px 8px; scroll-snap-type: x mandatory; }
.attn__rail::-webkit-scrollbar { height: 0; }
/* One width. `.attn-card--slip` widened only the two promise cards to fit
   "Tap again to confirm", which drew two sizes of card side by side on one
   rail; every card carries a button now, so every card is that width. */
.attn-card {
  scroll-snap-align: start; flex: 0 0 292px; min-width: 292px;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r); padding: 14px 15px;
  box-shadow: var(--sh-1); cursor: pointer; transition: transform .14s var(--spring), box-shadow .14s, border-color .14s;
  text-align: left;
  /* Contents start at the TOP. The card is a <button>, and the rail stretches
     every card to the tallest one's height; a button centres its contents in
     that height, so a card without the over-limit bar sat 8px lower than its
     neighbour (Karan, 24/09/2026: "why these cards are not aligned"). */
  display: flex; flex-direction: column; justify-content: flex-start;
}
/* THE CARD TAKES ITS BAND'S COLOUR UNDER THE POINTER — Karan, 09/09/2026:
   "should the entire card color change when the mouse is brought on top?".
   The badge already names the band in 10.5px; the tint says the same thing
   without being read, so pointing at a card is enough to know which kind of
   problem it is.

   Written as two variables set by the band class rather than six `:hover`
   rules, so a seventh band added later falls back to the neutral hover instead
   of silently having none. Always the SOFT tone, including for the two bands
   whose badge is solid: a filled card would drown the figure printed on it,
   and that figure is what the shop opened the screen to read.

   TWO tints, not four (09/09/2026). A tint that means "this card wants
   something" is worth having; four tints that each mean a different KIND of
   wanting is four more colours on a screen that already had six too many.

   There were two more bands here until 10/09/2026 — Top receivable and You owe
   — which took no tint at all and only lit their border, because nothing about
   them needed doing. They are off the strip entirely now, which is why every
   card left takes the amber: the strip is one hue, and it means act. */
.attn-card--late, .attn-card--over,
.attn-card--due,  .attn-card--near { --attn-tint: var(--amber-soft); --attn-edge: var(--amber); }
.attn-card { transition: transform .14s var(--spring), box-shadow .14s, border-color .14s, background .14s; }
.attn-card:hover {
  transform: translateY(-3px); box-shadow: var(--sh-2);
  background: var(--attn-tint, var(--paper));
  border-color: var(--attn-edge, var(--line-strong));
}
.attn-card:active { transform: translateY(-1px) scale(.99); }
.attn-card__top { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.attn-card__why { font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; padding: 3px 8px; border-radius: 999px; }
/* THREE TREATMENTS, ONE HUE. The six bands are declared in ATTN_LOOK in
   user_dashboard.html; this is only what the three weights look like.

     .why--now    solid amber + triangle   Overdue, Over limit
     .why--soon   soft amber  + clock      Due ..., Near limit
     .why--fyi    slate outline, no icon   nothing, since 10/09/2026

   Nothing carries `.why--fyi` any more: it dressed Top receivable and You owe,
   which were dropped from the strip when the filter chips above the party list
   began carrying them in full. It stays as the landing place for a band added
   later, which _attnCardHtml falls back to by name.

   It carried six colours until 09/09/2026 — solid red, soft red, solid amber,
   soft amber, indigo and slate — and Karan, reading the real screen: "I see so
   many colors, and I'm unable to see ... what means what." Three readers given
   the same capture cold answered "how many need action?" with 4 against a
   badge that said 8, in five seconds, at 40% confidence.

   The icon is what carries severity without a second hue, and it is why the
   pill lays out as a flex row. `--on-amber` is the palette's dark ink for text
   on filled amber; white on amber does not pass contrast and is not an option
   here. */
.attn-card__why { display: inline-flex; align-items: center; gap: 4px; }
.attn-card__why.why--now  { background: var(--amber); color: var(--on-amber); }
.attn-card__why.why--soon { background: var(--amber-soft); color: var(--amber); }
/* Outlined rather than filled, and the ring rather than the fill is what makes
   it visible: --canvas-2 is a clear grey on white and all but identical to
   --paper in dark, so a fill alone would leave this pill invisible at night. */
.attn-card__why.why--fyi {
  background: var(--canvas-2); color: var(--ink-2);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
/* Mark paid, revealed when the pointer is near the card — the same rule
   .prow__quick follows on a party row. It sits at the end of the card's top
   line, so nothing moves when it fades in. */
.attn-card__act { margin-left: auto; opacity: 0; transform: translateX(4px); transition: opacity .15s, transform .15s; }
.attn-card:hover .attn-card__act { opacity: 1; transform: none; }
.attn-card__act:focus-within { opacity: 1; transform: none; }
/* A phone has no pointer to bring near, so it is simply there. */
body.is-mobile .attn-card__act { opacity: 1; transform: none; }
.attn-card__slip { color: var(--ink); font-variant-numeric: tabular-nums; }

.attn-card__name { font-size: 14.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.attn-card__bal { font-family: var(--f-serif); font-weight: 500; font-size: 22px; letter-spacing: -.01em; margin-top: 2px; }
.attn-card__meta { font-size: 11.5px; color: var(--faint); margin-top: 8px; display: flex; align-items: center; gap: 6px; }
/* The Due card's actions ride at the FOOT of the card, always visible: this tab
   exists to act on the reminders, so Send reminder / Mark paid are not hidden
   behind a hover the way the attention rail's single shortcut is. */
.attn-card__acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.attn-card__acts .btn { flex: 0 0 auto; }
.attn-card__bar { height: 5px; background: var(--canvas-2); border-radius: 999px; overflow: hidden; margin-top: 10px; }
/* The card is a <button>: its parts are spans, so they are told to lay out
   as blocks (same treatment as .prow). */
.attn-card__av { width: 36px; height: 36px; font-size: 13px; text-transform: uppercase; }
.attn-card__name, .attn-card__bal, .attn-card__bar { display: block; }
.attn-card__meta > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Starts empty and is grown by renderAttentionRail after the markup lands,
   so the bar animates in and no per-card width rides in a style attribute. */
.attn-card__fill { display: block; width: 0; height: 100%; border-radius: 999px; transition: width .5s var(--spring); }
/* The limit bar follows the pills: amber while the limit is the reason the
   card is here, plain grey while it is only context under a card that is here
   for some other reason. */
/* Every band left on the strip is a band that wants something done, so the
   bar is amber on all of them. It was split until 10/09/2026, when the two
   informational bands came off. */
.attn-card__fill.fill--late, .attn-card__fill.fill--over,
.attn-card__fill.fill--due,  .attn-card__fill.fill--near { background: var(--amber); }

/* ═════════ PARTY LIST ═════════ */
.list-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 4px 0 12px; }
.list-title { font-family: var(--f-serif); font-weight: 500; font-size: 18px; }
.toolbar { display: flex; gap: 10px; align-items: center; margin-bottom: 14px; }
.toolbar .search { flex: 1; }
.toolbar__add { flex: none; height: 44px; padding: 0 18px; }
.is-mobile .toolbar__add { padding: 0 14px; }
.is-mobile .toolbar__add span, .is-mobile .toolbar__add { white-space: nowrap; }

/* ON A PHONE THE SEARCH BOX TAKES THE WHOLE ROW, and the buttons drop below it.
   Reported by Karan 10/09/2026 at 390px: the box read "Search by name or last"
   and the rest of "…or last 4 digits" was cut off — so the one hint telling a
   shopkeeper they can search by PHONE NUMBER was the half that vanished.

   The arithmetic, because the fix has to survive somebody adding a fourth
   button here. At 390px the row holds three things: "Add entry" (~98px with its
   mobile padding), "Select" (~66px), and two 10px gaps — about 184px before the
   page's own side padding. What is left for the input is roughly 136px of text
   once the magnifying-glass inset is taken off, against a hint that needs about
   210px. It could never have fitted.

   Note which accounts saw it: `.toolbar__pick` ("Select") is admin-only, so an
   ADMIN lost ~76px that a staff account kept, and staff just cleared the hint.
   The person most likely to report it is the only one guaranteed to see it.

   Wrapping rather than shortening the text, hiding a button, or shrinking the
   font: the hint earns its length (a shop searching "4821" for a phone will not
   guess that works), and both buttons are real actions. It costs one row of
   height on the narrowest screens and nothing at all above them. */
.is-mobile .toolbar { flex-wrap: wrap; }
.is-mobile .toolbar .search { flex: 1 0 100%; }
/* The empty Customers list's pointer to Settings -> "Bring in your old
   register" (F27r, owner only; the import itself lives in Settings). */
.empty-import { margin-top: 14px; white-space: normal; }

/* ═════════ IMPORT AN OLD REGISTER (F27r) ═════════ */
.imp-drop { display: block; width: 100%; border: 1.5px dashed var(--line-strong); border-radius: 12px; padding: 18px; text-align: center; color: var(--muted); font: inherit; font-size: 13.5px; background: var(--paper-2); cursor: pointer; }
.imp-drop b { color: var(--ink); display: block; font-size: 15px; margin-bottom: 4px; }
.imp-thumbs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.imp-thumb { position: relative; aspect-ratio: 3/4; border-radius: 10px; border: 1px solid var(--line-soft); background: var(--paper-2); display: grid; place-items: end start; padding: 8px; font-size: 12px; color: var(--muted); overflow: hidden; }
.imp-thumb::before { content: ""; position: absolute; inset: 14px 12px 32px; background: repeating-linear-gradient(var(--paper) 0 13px, var(--line-strong) 13px 14px); border-radius: 4px; }
.imp-thumb__x { position: absolute; top: 6px; right: 6px; width: 24px; height: 24px; border-radius: 6px; background: var(--paper); border: 1px solid var(--line-strong); display: grid; place-items: center; font-size: 14px; color: var(--muted); cursor: pointer; z-index: 1; }
.imp-hint, .imp-note { font-size: 12.5px; color: var(--muted); }
.imp-wait { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 18px 4px; text-align: center; }
.imp-bar { width: 100%; height: 6px; border-radius: 999px; background: var(--line-soft); overflow: hidden; }
.imp-bar i { display: block; height: 100%; width: 40%; background: var(--accent); border-radius: 999px; animation: imp-slide 1.4s ease-in-out infinite; }
@keyframes imp-slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }
@media (prefers-reduced-motion: reduce) { .imp-bar i { animation: none; width: 55%; } }
.imp-group { display: flex; flex-direction: column; gap: 6px; }
.imp-group__head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 13px; font-weight: 700; color: var(--ink-2); }
.imp-group__head span { font-weight: 500; color: var(--muted); text-align: right; }
.imp-line { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 9px 12px; border-radius: 10px; background: var(--paper-2); border: 1px solid var(--line-soft); font-size: 14px; }
.imp-line__amt { font-variant-numeric: tabular-nums; font-weight: 600; text-align: right; }
.imp-line small { color: var(--muted); display: block; font-size: 12px; font-weight: 500; margin-top: 2px; }
.imp-line--ask { background: rgba(var(--neg-rgb), .05); border-color: rgba(var(--neg-rgb), .25); }
.imp-check { display: flex; gap: 8px; align-items: flex-start; font-size: 12.5px; color: var(--ink-2); background: var(--accent-soft); border-radius: 10px; padding: 9px 12px; }
.imp-fix { display: flex; flex-direction: column; gap: 9px; padding: 11px 12px; border-radius: 10px; border: 1px solid rgba(var(--neg-rgb), .35); background: var(--paper); }
.imp-fix__name { font-size: 14px; font-weight: 600; }
.imp-fix__name small { color: var(--muted); display: block; font-size: 12px; font-weight: 500; margin-top: 2px; }
.imp-fix__row { display: flex; flex-wrap: wrap; gap: 8px; }
.imp-fix__amt { height: 40px; padding: 0 12px; border-radius: var(--r-xs); border: 1px solid var(--line-strong); background: var(--paper); color: var(--ink); font: inherit; font-size: 15px; font-variant-numeric: tabular-nums; }
.imp-fix .btn { align-self: flex-start; }

.plist { display: grid; gap: 8px; }
/* DELIBERATE DIVERGENCE FROM THE PACK: its minimum is 300px, ours is 600.
   At 300 a second column appears from about a 620px window and the row cannot
   hold a name at that width — measured at a 726px window, two 326px columns,
   and ALL SIX names truncated.

   It was 420 until 10/09/2026, on this arithmetic: "the row needs 265px for the
   avatar, the trend line, the figure, the gaps and the padding before a single
   letter of the name is drawn". That was true of a THREE-column row. The limit
   badge moved into a column of its own the day before, costing about 130px
   more, and this number was not re-derived with it — so it sat ~130px too low,
   and the only thing that shows it is a book with a long name in it. Measured
   against a 151-party book: at 1440px, 8 of 26 flagged rows had the badge
   printed across the party's name; at 1180px, 16 of 26.

   RE-DERIVED for a flagged row, which no longer draws a sparkline:
     24 padding + 57 avatar+gap + 143 badge+gap + 118 figure+gap = 342 before
     any name, and the longest name in a real book is 36 chars — about 260px.
     A flagged row needs 602 to hold all four on ONE line.

   It cannot have 602 and still show more than one column. A 600px minimum was
   briefly tried and quietly drew ONE column at every width from 1024 to 1920,
   measured doing it. Under the current 1680px content cap the grid draws three
   columns of 536, and 536 — like the 588 the old 1240px cap allowed — is below
   the 602 a flagged row needs, so no workable minimum buys both a multi-column
   grid and a one-line flagged row. The badge gives up the line instead (see
   .prow__flag below).

   520, not the 420 it was, because the NAME still needs the room even once the
   badge has stopped competing for it. The window that mattered was 1024px — an
   iPad in landscape, a small laptop — where 420 still admitted two columns at
   480px each and 25 of the 151 names came back truncated. The bound is narrow
   and both ends are real: above 480 so 1024 draws ONE column, and at or below
   536 so the content area at the 1680px cap (1624px — see --maxw in tokens.css)
   draws THREE. Measured at 520: one column of 968 at 1024, two of 558 at 1180,
   two wider columns from 1280 up to the cap, and three of 536 once the 1680px
   cap binds — no name truncated at any of them. */
.plist--grid { grid-template-columns: repeat(auto-fill, minmax(520px, 1fr)); }
.is-mobile .plist--grid { grid-template-columns: 1fr; }

/* Avatar · name · open space · money. The name track is `auto` — sized to what
   the name needs and no more — and the empty `1fr` between it and the money is
   what holds the figure flush against the card's right edge. The limit warning
   is not a column: it lives inside the name block, under the name (see
   `.prow__flag` below). */
.prow {
  display: grid; grid-template-columns: 44px auto 1fr auto; gap: 13px; align-items: center;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r);
  padding: 12px 14px; box-shadow: var(--sh-1); cursor: pointer; text-align: left;
  transition: transform .13s var(--spring), box-shadow .14s, border-color .14s;
  position: relative;
}
.prow:hover { transform: translateY(-2px); box-shadow: var(--sh-2); border-color: var(--line-strong); }
.prow:active { transform: translateY(0) scale(.995); }
.prow__info { min-width: 0; display: block; }
/* The row is a <button>, whose content model is phrasing content — so every
   part of it is a <span>, not a <div> as the pack's JSX had it. These give
   those spans the block layout the rules below were written against. */
.prow__av { width: 44px; height: 44px; font-size: 14.5px; text-transform: uppercase; }
.prow__name { display: block; }
.prow__bal { display: block; }
.prow__bal-v, .prow__bal-l { display: block; }
.prow__spark { display: inline-flex; }
.prow__name { font-size: 14.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.25; }
/* THE LIMIT WARNING SITS UNDER THE NAME, INSIDE THE NAME BLOCK, and a card is
   one height with it or without it (Karan, 23/09/2026: "the design is outside
   your container, so the card size is looking off ... i always want clean
   synchronous design"). The pill is the one he approved on 09/09/2026 and is
   unchanged: same amber, same icon, same percentage, a size up from a list
   badge because it is a warning and not a label.

   It used to take a line of its own, centred across the card, and that line
   was PAID for. Measured 23/09/2026 on the three-up grid: a card with the pill
   stood 114px against 70px without, and every card sharing its grid row
   stretched to match — one warning made a whole row of the list 44px taller,
   with the pill floating in the middle of the card, aligned to nothing.

   Now the name and the pill stack inside the 44px the avatar already sets:
   name 18px (line-height 1.25 above — one line centred on the avatar looks the
   same at 1.25 as at 1.5), 2px, pill 24px (line-height 1.2 inside it).
   Measured: every card 70px, flagged or not, at 1624px and 1116px; the pill's
   left edge on the name's; nothing clipped, no name newly cut. A phone needs no
   rule of its own: the money column sets its row height, and a two-line name
   plus the pill fits inside it the same way.

   `display: flex`, not block, is load-bearing: in a block the inline badge sits
   on a text line whose strut adds about 1.5px under it, which is exactly the
   room a two-line name on a phone does not have.

   `max-width` + `overflow` are the backstop: the badge is `white-space: nowrap`,
   so in a name block narrower than the pill it is clipped inside the card
   rather than spilling across the money. */
.prow__flag { display: flex; margin-top: 2px; min-width: 0; max-width: 100%; overflow: hidden; }
.prow__flag .badge { font-size: 11.5px; padding: 4px 11px; line-height: 1.2; }
/* The money is TOLD which column it is in. Auto-placement would drop it into
   the first free cell, the empty `1fr`, and the figure would drift inward off
   the card's edge — measured once at 301px. Pinned to the last track, the
   empty `1fr` stays between the name and the money and holds the figure
   flush right. */
.prow__right { grid-column: -2 / -1; grid-row: 1; }
/* The limit reading inside a popup already narrowed to that limit state,
   where the pill would be on every row and so says nothing. It sits under the name, alongside where a
   matched phone number goes — same faint tone as the rest of the row's small
   print, tabular so a column of percentages lines up and can be read down, and
   nowrap so that if it ever did run out of room it would be CLIPPED rather
   than silently stacked into three lines, which is how the first attempt at
   this failed while every measurement said it was fine. */
.prow__limit { display: block; margin-top: 3px; font-size: 11.5px; color: var(--faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
.prow__right { display: flex; align-items: center; gap: 12px; }
.prow__bal { text-align: right; }
.prow__bal-v { font-family: var(--f-serif); font-weight: 500; font-size: 17px; letter-spacing: -.01em; font-variant-numeric: tabular-nums; line-height: 1; }
/* RECEIVABLE / PAYABLE is the only thing on the row that says which way the
   money points without using colour, and at 9.5px it was losing that job to
   the 17px coloured figure above it — a reader who cannot separate the greens
   from the reds had nothing left to read. 10.5px is where the word starts
   being taken in at a glance rather than deciphered. */
.prow__bal-l { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; margin-top: 4px; }
.prow__spark { width: 56px; height: 26px; flex: none; }
/* The pack's rule, and it is right. Tried the other way on 09/09/2026 on the
   theory that removing the entry count and the date had freed the room: it had
   not. The phone row stacks its right-hand column, so a third item there makes
   every card half again as tall and squeezes the name until "Gurpreet Kaur
   Traders" truncates — the line costs the one thing on the row that must never
   be in doubt, which is WHICH customer this is. The trend stays a desktop
   reading; the phone answers "which way" with the colour and the label. */
/* DELIBERATE DIVERGENCE FROM THE PACK, which hides this on a phone. Karan has
   asked for the line twice ("where is the line graph?", 09/09/2026) and he is
   right that it is the most useful thing on the row after the figure: it is
   the difference between "he owes 18,400" and "he owes 18,400 and it has been
   climbing all month".

   It goes UNDER the balance rather than beside it — `.prow__right` is already
   a column at this width — so it costs the row 26px of HEIGHT and nothing at
   all of WIDTH. An earlier attempt today put it in the row and the names went
   back to truncating; that was the button and the line competing for the same
   348px, and the button is gone from the phone now. */
.is-mobile .prow__spark { display: block; width: 56px; height: 18px; order: 2; }
/* IT SITS IN THE GAP THAT IS ALREADY THERE, immediately left of the figure,
   and covers nothing.

   It floated over the card's right edge until 16/09/2026, on the argument that
   a row is only ever hovered by someone reaching for the button. Measured
   against the live book of 96 parties, that argument loses: 62 of the 62 rows
   that HAVE a chip had it landing on the balance — not an edge case, every
   single one — so hovering a party to collect from them hid the amount being
   collected. Karan, 16/09/2026, looking at exactly that.

   The fix is NOT the reserved column tried on 09/09/2026 and reverted: that
   added a FOURTH track, which costs every row a permanent empty gutter and
   pulls every balance off the card's right edge. The row's THIRD track is a
   `1fr` that already exists, already empty on an unflagged row, and already
   paid for — anchoring the chip to the left edge of `.prow__right` puts it
   there. Nothing is reserved, nothing moves, the balance stays flush right.

   Re-measured with the rule applied, same 96 rows:
     single-column list (1642px rows)  62 covered -> 0, and 0 names touched
     three-up grid      (542px cards)  62 covered -> 0, 3 names touched by at
                                       most 57px, hover-only, and the chip is
                                       opaque with a border so it reads as a
                                       button sitting on top rather than as
                                       scrambled text.
   Three rows' name-tails against 62 rows' money is the trade, and it is not
   close. */
.prow__right { position: relative; }
.prow__quick { position: absolute; right: calc(100% + 12px); opacity: 0; transform: translateX(6px); transition: opacity .15s, transform .15s; }
.prow:hover .prow__quick { opacity: 1; transform: none; }
/* Keyboard users get it too — hover is not the only way to reach a row. */
.prow__quick:focus-within { opacity: 1; transform: none; }
/* Hidden on a phone, as the pack has it. We diverged for a while and kept it
   visible on the argument that hiding it puts the most common action behind
   opening the account. Measured at 390px on 09/09/2026, that argument loses:
   the button takes enough of the row that a name as ordinary as two words and
   a "Traders" truncates, and a list whose whole job is telling you WHO owes
   you must never be the screen that cannot print a name. Nothing is lost —
   tapping the row opens the account, whose primary button is Add entry.

   Kept hidden by Karan, 24/09/2026, after the alternatives were built and
   offered. Under the figure in the trend line's slot it fitted without growing
   a row, but he wants it to appear only when reached for, as on desktop — and
   a phone cannot hover. Swipe, first-tap-reveals and press-and-hold were each
   put to him as the touch stand-in, and he chose none. Do not re-propose one. */
body.is-mobile .prow__quick { display: none; }
/* Every pixel here is the NAME's. At 390px the row has 348 to divide between
   an avatar, a name and a balance, and the name is the only one of the three
   that can run out — a list whose job is telling a shopkeeper WHO owes them
   must print the who. The avatar shrinks the way `.phero__av` does one card up
   (56 → 46 there), the grid gap comes in from 13 to 10, and the side padding
   drops the 2px that a 14px gutter is spending on nothing at the narrowest
   size we draw. Measured 09/09/2026 at
   390px on the longest name in the set: the column goes 150 → 160 against the
   157 that name needs.

   THAT WAS STILL NOT ENOUGH, and the sentence that used to end this comment —
   "a longer one still ellipsises, and should, the alternative is a wrapped name
   and a list of uneven rows" — turned out to be wrong on its own terms.
   Measured 16/09/2026 at 390px on the live book: 9 of 96 names were being cut,
   the worst by 77px. So the rule below lets a name take a second line, and the
   objection it was refused for does not survive the measurement — the row's
   RIGHT column (figure over label over trend) is what sets the row's height, so
   a second name line fits inside the height the row already had. Re-measured
   with the clamp on: names cut 9 -> 0, and the row heights did not move, 90px
   on 94 rows and 103px on the two flagged ones, exactly as before.

   Two lines and no more: past that a name is long enough that the list turns
   into paragraphs, and the tail of a third line tells a shopkeeper nothing the
   first two did not. */
body.is-mobile .prow { gap: 10px; padding: 12px; }
/* Wrap rather than cut — see the measurement above. `-webkit-line-clamp` is the
   way to cap a wrap at two lines and put an ellipsis on the second. The
   `-webkit-` spelling is deliberate and is not a fallback for something else:
   it is the form every engine implements. On desktop the rows are
   1642px or 542px wide and the single line still fits, so nothing changes
   there. */
body.is-mobile .prow__name { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
body.is-mobile .prow__av { width: 36px; height: 36px; font-size: 13px; }
body.is-mobile .prow__right { flex-direction: column; align-items: flex-end; gap: 8px; }
body.is-mobile .prow__bal { text-align: right; }
/* No middle to be in the middle of: at 390px the row has 348px for an avatar,
   a name and a figure, and a fourth column is what put "Gurpreet Kaur Traders"
   back to truncating — which a list whose job is saying WHO owes you must never
   do. The limit warning needs no rule here: it lives inside the name block,
   under the name (see `.prow__flag`), and the MONEY column is what sets a phone
   row's height — figure over label over trend slot — so a two-line name plus
   the pill fits inside the height the row already had. Measured 23/09/2026 at a
   358px list: flagged rows went from 101px to the same 89px as every other row. */
body.is-mobile .prow { grid-template-columns: 36px minmax(0, 1fr) auto; }

/* ═════════ THE MONTH YOU CAN SWIPE ═════════
   Activity's date picker. A month grid rather than a pair of date boxes,
   because this screen is read on a phone and a native date box there is a
   spinner you cannot see a month in — you cannot tell, before committing,
   whether the day you are reaching for has anything on it.

   The marks are the whole point and they are what a picker over a real book
   can do that a generic one cannot: a dot under every day the shop actually
   traded, so the grid answers "which days are worth opening?" before a finger
   moves. Everything else here is the pattern a shopkeeper has already used in
   a calendar or a photos app — swipe for the next month, tap a day, tap a
   second for a stretch.

   Sizing is the 44px rule below, stated rather than inherited: a day cell is a
   grid child, so the `.chip`/`.btn` rule underneath does not reach it. */
.cal__nav { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
/* The label is a button, because it is the way up a level. It has to LOOK
   like one or the way out of a 104-month book stays invisible — so it carries
   the caret and lights up on hover, and goes flat and unclickable at the year
   level where there is nothing above. */
.cal__month {
  font-size: 15px; font-weight: 700; color: var(--ink); letter-spacing: -.01em;
  padding: 6px 10px; border-radius: 9px; background: transparent;
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  transition: background .14s;
}
.cal__month::after { content: '⌄'; font-size: 13px; line-height: 1; color: var(--faint); margin-top: -4px; }
.cal__month:hover:not(:disabled) { background: var(--accent-soft); }
.cal__month:disabled::after { content: none; }
.cal__month:disabled { color: var(--ink); }
.cal__arrow {
  width: 44px; height: 44px; border-radius: 50%; font-size: 20px; line-height: 1;
  color: var(--ink); background: var(--paper); border: 1px solid var(--line-strong);
  display: grid; place-items: center; transition: background .14s, opacity .14s;
}
.cal__arrow:hover:not(:disabled) { background: var(--accent-soft); border-color: var(--accent-line); }
/* Disabled at the ends of the shop's OWN history — swiping into a year it did
   not trade in is motion that answers nothing. Faded rather than removed, so
   the row keeps its shape and the month label stays centred. The cursor is
   handled once for every button, at `button:disabled` near the top of this
   file — deliberately not repeated here. */
.cal__arrow:disabled { opacity: .3; }
.cal__dow, .cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.cal__dow {
  gap: 2px; margin-bottom: 4px; text-align: center;
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; color: var(--faint);
}
/* No COLUMN gap, on purpose: a picked stretch has to read as one band across
   the week, and 2px of paper between each day turns it back into a row of
   separate squares somebody has to join up themselves. The row gap stays. */
.cal__grid { gap: 2px 0; }
.cal__pad { visibility: hidden; }
.cal__load { grid-column: 1 / -1; display: grid; place-items: center; min-height: 180px; }
.cal__day {
  position: relative; min-height: 44px; border-radius: 10px;
  font-size: 14px; font-weight: 600; color: var(--ink); background: transparent;
  border: 1px solid transparent; display: grid; place-items: center;
  transition: background .12s, color .12s, border-color .12s;
}
.cal__day:hover { background: var(--accent-soft); }
/* A day the shop wrote nothing on is still tappable — a shopkeeper checking
   that a day really is empty is asking a real question and deserves an
   answer, not a dead button. It is simply quieter. */
.cal__day.is-quiet { color: var(--faint); font-weight: 500; }
.cal__day.is-today { border-color: var(--accent-line); }
.cal__day.is-in { background: var(--accent-soft); color: var(--accent-ink); border-radius: 0; }
.cal__day.is-end { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.cal__day.is-end .cal__dot { background: var(--on-accent); }
.cal__dot {
  position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%);
  width: 4px; height: 4px; border-radius: 50%; background: var(--accent);
}
.cal__day.is-quiet .cal__dot { display: none; }
/* The zoomed-out grids. Three columns rather than seven, because a year or a
   month name is a word and not a number, and a taller cell because it carries
   two lines — the period and what the shop wrote in it. That count is the
   same promise the day dots make, one zoom up: it says which years are worth
   opening before a finger moves, which is the entire reason this beats a
   spinner on a book that runs back to 2018. */
/* `align-content: start` because three or four rows in a box sized for six
   otherwise stretch to fill it, and the cells then float in dead space that
   looks like a rendering fault rather than a decision. */
.cal__grid--period { grid-template-columns: repeat(3, 1fr); gap: 8px; align-content: start; padding: 4px 0; }
/* `.cal__day` is already a centred grid, so the label and the count stack on
   their own — no flex direction to set, and setting one here did nothing. */
.cal__period { min-height: 56px; gap: 1px; align-content: center; }
.cal__plabel { font-size: 14px; font-weight: 600; line-height: 1.1; }
.cal__pn { font-size: 11px; font-weight: 600; color: var(--muted); line-height: 1; }
.cal__period.is-in, .cal__period.is-end { border-radius: 10px; }
.cal__period.is-end .cal__pn, .cal__period.is-in .cal__pn { color: inherit; opacity: .8; }
.cal__said { margin-top: 12px; font-size: 13px; color: var(--muted); text-align: center; min-height: 20px; }

/* ═════════ A FINGER IS NOT A POINTER ═════════
   Measured 16/09/2026 in a real phone-width window, screen by screen rather
   than by reading selectors — which is the only method that ever finishes, and
   it found two of these on a screen the first two rounds never opened.

     Home       16 of 123 under 44px — five segmented buttons and five filter
                chips at 31px, three top-bar icons at 38px, three small buttons
                at 32px, the logo at 29px
     Ledger      1 — "Load more" at 39px, the button a shop presses to see the
                rest of its own book
     Settings    5 — the four section-nav buttons at 41px, the buffer field
                at 38px
     An account  2 — the back bar at 34px, which is the way OUT of the screen,
                and the edit-phone chip at 33px

   44px is roughly what a fingertip actually covers. Under it the shop taps the
   row behind the chip, or the chip beside the one it wanted — and on this page
   the row behind a chip opens somebody's account.

   These set a HEIGHT FLOOR and nothing else: no padding moves, so no text
   moves, and anything already taller is untouched. A filter chip drawn for a
   finger is simply a rounder pill, which is what every phone app already does.
   Phone only — a pointer is precise, and a 44px chip row on a desktop would be
   shouting. */
body.is-mobile .chip,
body.is-mobile .seg button,
body.is-mobile .set__nav button,
body.is-mobile .settings-number-input,
body.is-mobile .backbar,
body.is-mobile .phone-chip,
body.is-mobile .btn { min-height: 44px; }
body.is-mobile .topbar__icon { width: 44px; height: 44px; }
body.is-mobile .topbar__logo { min-height: 44px; display: inline-flex; align-items: center; }
/* `.btn`, not `.btn--sm`. The first draft named the small variant and left
   Ledger's own "Load more" short, because that one is a plain `.btn` that
   happened to be 39px. A floor never shrinks anything, so the wider selector
   costs nothing and does not have to be re-derived the next time somebody adds
   a button.

   The four spelled out above it are the ones no shared class reaches: Settings'
   section nav is bare `<button>`s inside `.set__nav`; the buffer field is an
   `<input type="number">`, a typing target that is tapped before it is typed
   into; `.backbar` is the bar itself, which carries the action; and
   `.phone-chip` is its own thing on the account card. */

/* ═════════ STREAMS layout ═════════ */
.streams { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.is-mobile .streams { grid-template-columns: 1fr; }
.stream--wide { grid-column: 1 / -1; }
.stream { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-1); overflow: hidden; }
.stream__head { padding: 16px 18px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line-soft); width: 100%; text-align: left; cursor: default; }
.stream--collapsible .stream__head { cursor: pointer; }
.stream.is-collapsed .stream__head { border-bottom: 0; }
.stream.is-collapsed .stream__body { display: none; }
.stream__chev svg { transition: transform .2s var(--spring); color: var(--faint); }
.stream:not(.is-collapsed) .stream__chev svg { transform: rotate(90deg); }
.stream__head.pos { background: linear-gradient(180deg, var(--pos-soft), transparent); }
.stream__head.neg { background: linear-gradient(180deg, var(--neg-soft), transparent); }
.stream__t { display: flex; align-items: center; gap: 10px; }
.stream__t-ic { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; }
.stream__t-ic.pos { background: var(--pos); color: var(--on-accent); }
.stream__t-ic.neg { background: var(--neg); color: var(--on-accent); }
.stream__t b { font-size: 14px; font-weight: 600; display: block; }
.stream__t span { font-size: 11.5px; color: var(--muted); }
/* On a narrow phone "2 suppliers to settle" wrapped onto a third line and
   grew the whole header. It shortens instead — the count is the part that
   matters and it is at the front. */
.stream__t { min-width: 0; }
.stream__t-txt { min-width: 0; }
.stream__t-sub { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stream__total { font-family: var(--f-serif); font-weight: 500; font-size: 22px; letter-spacing: -.01em; }
.stream__body { padding: 8px; display: flex; flex-direction: column; gap: 4px; max-height: 540px; overflow-y: auto; }
.srow { display: grid; grid-template-columns: 36px 1fr auto; gap: 11px; align-items: center; padding: 10px 11px; border-radius: var(--r-sm); cursor: pointer; transition: background .13s; }
.srow:hover { background: var(--canvas); }
.srow__name { font-size: 13.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.srow__meta { font-size: 11px; color: var(--faint); margin-top: 1px; }
.srow__bal { font-family: var(--f-serif); font-weight: 500; font-size: 15.5px; font-variant-numeric: tabular-nums; }
/* Same <button>-of-spans treatment as .prow and .attn-card. */
.srow { width: 100%; text-align: left; }
.srow__av { width: 36px; height: 36px; font-size: 13px; text-transform: uppercase; }
.srow > .grow { display: block; }
.srow__name, .srow__meta { display: block; }
.srow__meta:empty { display: none; }

/* ═════════ FEED layout ═════════ */
.feed { display: grid; grid-template-columns: 1.5fr 1fr; gap: 18px; align-items: start; }
.is-mobile .feed { grid-template-columns: 1fr; }
.feed__main { display: flex; flex-direction: column; gap: 14px; }
.daygroup__head { display: flex; align-items: baseline; justify-content: space-between; padding: 2px 2px 8px; }
.daygroup__date { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); white-space: nowrap; }
.daygroup__sum { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
.feedcard { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r); box-shadow: var(--sh-1); overflow: hidden; }
.daygroup__more { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 2px 0; font-size: 12px; color: var(--muted); }
.frow { display: grid; grid-template-columns: 38px 1fr auto; gap: 12px; align-items: center; padding: 12px 14px; cursor: pointer; transition: background .13s; }
.frow + .frow { border-top: 1px solid var(--line-soft); }
.frow:hover { background: var(--canvas); }
.frow__ic { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; flex: none; }
.frow__nar { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.frow__meta { font-size: 11.5px; color: var(--faint); margin-top: 2px; display: flex; gap: 7px; align-items: center; white-space: nowrap; flex-wrap: wrap; }
.frow__meta span { white-space: nowrap; }
.frow__amt { font-family: var(--f-serif); font-weight: 500; font-size: 16px; font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
/* The running balance, under the amount that moved it. Quiet on purpose: it is
   context for the figure above it, not a second figure competing with it. */
.frow__bal { display: block; font-family: var(--f-sans); font-size: 11px; font-weight: 400; color: var(--faint); margin-top: 3px; }
.frow { width: 100%; text-align: left; }
.frow > .grow { display: block; }
.frow__nar { display: block; }
/* A row Clear & carry forward tucked away: listed, faded as History fades it,
   and not a button — nothing to hover, nothing to press. Its figure keeps its
   sign and gives up its colour, as History's archived rows do. */
.frow--archived { cursor: default; opacity: .72; }
.frow--archived:hover { background: none; }
.frow--archived .frow__amt { color: var(--muted); }
/* The pack tints a flat glyph inside this tile (.tic). Ours is the
   full-colour entry icon Karan approved on 12/06/2026 — the same one the
   party ledger and the Ledger tab print — so the tile carries no tint and
   simply sizes it. */
/* One span of joined text, not one span per fact: with the pack's
   wrapping row a narrow phone broke the line AFTER a separator and left
   a bare · hanging at the end of it. This truncates cleanly at any
   width instead, and the facts stay in the design's order so what goes
   first is what a shopkeeper reads first. */
.frow__meta { flex-wrap: nowrap; overflow: hidden; }
.frow__meta > span { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.feed__side { display: flex; flex-direction: column; gap: 14px; position: sticky; top: 18px; }
.is-mobile .feed__side { position: static; }
.sidecard { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-1); padding: 18px; }
.sidecard__t { font-size: 13px; font-weight: 700; color: var(--ink); margin-bottom: 14px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* The net position again, at side-rail size. The currency glyph is sized
   apart from the digits exactly as the hero does it — rs() names that span
   `.rs`, the pack's markup called it `.cur`, and both are styled so either
   keeps working. */
.sidecard__net { font-family: var(--f-serif); font-weight: 500; font-size: 32px; letter-spacing: -.02em; line-height: 1; font-variant-numeric: tabular-nums; }
.sidecard__net .cur,
.sidecard__net .rs { font-size: .56em; color: var(--faint); font-weight: 500; margin-inline-end: .1em; }
.sidecard__spark { margin-top: 10px; }
.sidecard__spark svg { display: block; width: 100%; height: auto; }
.sidecard__rule { margin: 16px 0; }
.sidecard__line { margin-bottom: 10px; }
/* Top movers are .srow rows with no card padding of their own. */
.sidecard .srow { padding-left: 0; padding-right: 0; }

/* type icon tones */
.tic { display: grid; place-items: center; border-radius: 11px; }
.tic.pos { background: var(--pos-soft); color: var(--pos); }
.tic.neg { background: var(--neg-soft); color: var(--neg); }
.tic.amber { background: var(--amber-soft); color: var(--amber); }
.tic.sky { background: var(--sky-soft); color: var(--sky); }
.tic.slate { background: var(--canvas-2); color: var(--muted); }

/* ═════════ PARTY DETAIL ═════════ */
.backbar { display: inline-flex; align-items: center; gap: 9px; margin-bottom: 16px; color: var(--muted); font-size: 13px; font-weight: 500; padding: 6px 12px 6px 8px; border-radius: 999px; border: 1px solid transparent; transition: background .14s, border-color .14s; }
.backbar:hover { background: var(--paper); border-color: var(--line); color: var(--ink); box-shadow: var(--sh-1); }

.phero { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-xl); box-shadow: var(--sh-2); overflow: hidden; margin-bottom: 20px; }
.phero__top { padding: 24px 26px; display: grid; grid-template-columns: auto 1fr auto; gap: 18px; align-items: center; }
.is-mobile .phero__top { grid-template-columns: auto 1fr; gap: 14px; }
.phero__id { min-width: 0; }
/* DELIBERATE DIVERGENCE FROM THE PACK: its name is 28 and its balance 34.
   Those two are close enough to compete, and the name wins because it comes
   first — so the eye lands on a word the shopkeeper just tapped instead of on
   the figure they opened the account to read. The name drops to 21 and the
   balance rises to 40 (below), which makes the money the only object on the
   card. Karan, 09/09/2026: "the party view inside, i dont like it". */
/* `min-width: 0` on the name is what lets a long one ellipsis instead of
   pushing the card wider. The line briefly also carried three icon-only
   buttons; they are a labelled row under the numbers now — see
   `.phero__actions`. */
.phero__nameline { display: flex; align-items: center; gap: 10px; min-width: 0; }
.phero__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.phero__name { font-family: var(--f-serif); font-weight: 500; font-size: 21px; letter-spacing: -.02em; line-height: 1.15; }
.is-mobile .phero__name { font-size: 19px; }
.phero__sub { color: var(--muted); font-size: 13.5px; margin-top: 6px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.phero__phones { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
/* Pulled back out to the card's left padding on a phone, to sit with the
   balance and the buttons rather than an avatar's width in from them. The
   offset is the avatar plus the grid gap at this width — the two numbers
   `.is-mobile .phero__av` and `.is-mobile .phero__top` declare — so changing
   either and forgetting this line is visible arithmetic, not a silent drift.
   Measured 09/09/2026 at 390px: four chips over four lines becomes two. */
.is-mobile .phero__phones { margin-left: calc(-1 * (46px + 14px)); }
/* PRIMARY labels the first chip as the first chip, which the order already
   says, and spends 10.5px and ~75px of a 296px line doing it. Dropping it on a
   phone is what lets the number and its WhatsApp chip share a line: measured
   09/09/2026 at 390px, 340px wide with the tag and 265 without. It stays on
   desktop, where the row has room to spare. */
.is-mobile .phone-chip__tag { display: none; }
.phone-chip { display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 999px; border: 1px solid var(--line-strong); background: var(--paper); font-size: 13.5px; font-weight: 500; color: var(--ink-2); box-shadow: var(--sh-1); transition: all .14s; }
.phone-chip .mono { font-size: 13px; white-space: nowrap; }
.phone-chip:hover { border-color: var(--ghost); }
.phone-chip.mobile svg { color: var(--pos); }
.phone-chip.mobile:hover { border-color: var(--pos-line); background: var(--pos-soft); }
.phone-chip.landline svg { color: var(--muted); }
.phone-chip__tag { font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--faint); background: var(--canvas); padding: 2px 6px; border-radius: 999px; }
.phone-chip__wa { font-size: 11px; font-weight: 700; color: var(--pos); background: var(--pos-soft); padding: 2px 7px; border-radius: 999px; }
.phone-chip--edit { color: var(--muted); border-style: dashed; box-shadow: none; background: transparent; }
.phone-chip--edit:hover { color: var(--accent-ink); border-color: var(--accent-line); background: var(--accent-soft); }
.phero__bal { text-align: right; }
.is-mobile .phero__bal { grid-column: 1 / -1; text-align: left; padding-top: 6px; border-top: 1px solid var(--line-soft); }
/* The eyebrow takes the money's own colour rather than the pack's grey, so
   "Owes you" and the figure under it read as one statement instead of a label
   and a number. `currentColor` on a t-pos / t-neg parent would be neater, but
   the eyebrow is a SIBLING of the figure, not its parent — so the colour is
   put on it directly by renderPartyHero. */
.phero__bal-l { font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; margin-bottom: 5px; }
.phero__bal-l.t-muted { color: var(--faint); }
/* 40, not the pack's 34 — see .phero__name above for why. */
.phero__bal-v { font-family: var(--f-serif); font-weight: 500; font-size: 40px; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1; }
.is-mobile .phero__bal-v { font-size: 32px; }
/* ONE left edge on this card. The row is a sibling of the identity column, so
   at the card's own 26px it started an avatar's width to the LEFT of the name,
   the entry count and the phone chips — two left edges on one card, which is
   exactly how it read: "why the add entry is not synced with the numbers?"
   (Karan, 09/09/2026).

   The indent is written as the sum it actually is, from the two numbers
   declared for `.phero__av` and the `.phero__top` gap further down, so that
   changing the avatar and forgetting this line is a visible arithmetic error
   rather than a silent 4px drift. */
.phero__actions { padding: 0 26px 20px; padding-left: calc(26px + 56px + 18px); display: flex; flex-wrap: wrap; gap: 8px; }
/* The phone resolves the same "one edge" the other way — see the note on
   `.is-mobile .phero__phones` below. The avatar and the name are a header; the
   numbers, the balance and these buttons all start at the card's own padding,
   because at 390px the indent leaves 236px and a single number chip is wider
   than that on its own. */
.is-mobile .phero__actions { padding-left: 26px; }
/* Full size, never `--sm`. These are the controls a shopkeeper reaches for
   across a counter; 12.5px was a designer's reading distance, not theirs. */
.phero__actions .btn { font-size: 14px; padding: 10px 16px; }
/* On a phone the row is Add entry, Send PDF and More, and it has to fit one
   line down to a 360px screen — the common width of an Indian Android phone,
   where the desktop padding wraps More onto a line of its own. Only the SIDES
   come in: the height and the 14px words stay the size a shopkeeper reads
   across a counter. More is its three dots alone, and keeps its name for a
   screen reader. */
.is-mobile .phero__actions { gap: 6px; }
.is-mobile .phero__actions .btn { padding-left: 10px; padding-right: 10px; }

/* The date-order switch above a customer's entries (_renderPartyLedger). */
.ledger-order { display: flex; justify-content: flex-end; margin: 14px 0 10px; }

/* Sending a customer their account (openReport). One big button nobody can
   miss across a counter, and a line or two of plain words — Karan's shop 9
   owner is past sixty, and the panel before this one had too much in it. */
.report { gap: 0; }
.report__share { width: 100%; display: flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 60px; padding: 14px; border-radius: 14px; border: 1.5px solid transparent;
  font-weight: 700; font-size: 18px; background: var(--pos); color: var(--on-pos); }
.report__share:active { transform: scale(.98); }
.report__share:disabled { background: var(--canvas-2); color: var(--muted); cursor: progress; transform: none; }
.report__share--quiet { background: var(--paper); color: var(--ink); border-color: var(--line-strong); }
.report__share .spinner { width: 18px; height: 18px; }
.report__to { text-align: center; font-size: 15px; color: var(--ink-2); margin: 8px 0 4px; }
.report__note { text-align: center; font-size: 15px; font-weight: 600; color: var(--ink); margin: 12px 4px 0; }
.report__plain { text-align: center; font-size: 15px; line-height: 1.5; color: var(--ink-2); margin: 18px 4px 2px; }
.hist-sendall { margin: 18px 22px 10px; }
.phero__limit { margin: 0 26px 20px; padding: 14px 16px; background: var(--canvas); border: 1px solid var(--line); border-radius: var(--r); display: flex; align-items: center; gap: 14px; }
.phero__limit-info { font-size: 13.5px; color: var(--muted); }
.phero__limit-info b { color: var(--ink); font-variant-numeric: tabular-nums; }
.phero__limit-bar { flex: 1; height: 7px; background: var(--canvas-2); border-radius: 999px; overflow: hidden; }
.phero__limit-fill { height: 100%; background: var(--accent); border-radius: 999px; transition: width .5s var(--spring); }
.phero__limit-fill.warn { background: var(--amber); }
.phero__limit-fill.over { background: var(--neg); }
/* The pack's hero avatar is a 56px tile. It takes its size from a prop there
   and from a class here, because a colour or a size written into a style
   attribute is a colour this page cannot theme. */
.phero__av { width: 56px; height: 56px; font-size: 19px; }
.is-mobile .phero__av { width: 46px; height: 46px; font-size: 16px; }
/* The name column runs several chips tall on a phone, so the avatar sits at
   the top of it rather than floating halfway down the card. */
.is-mobile .phero__top { align-items: start; }
/* The bar gets its own line. As the flexible middle of a three-part row it
   measured zero pixels wide once the figures beside it wrapped. */
.is-mobile .phero__limit { display: grid; grid-template-columns: auto 1fr; gap: 8px 12px; }
.is-mobile .phero__limit-bar { grid-column: 1 / -1; }

/* ledger table (desktop) */
.ledger { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-1); overflow: hidden; }
/* The card fills the width (so it lines up with the bills above), but the table
   inside it reads at a comfortable width and left-aligns with that content. On a
   normal laptop there is no slack and it just fills; only a very wide screen
   leaves a right-hand margin, which beats a narration stretched a lap wide. */
.ledger table { width: 100%; max-width: 1200px; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.ledger thead th { text-align: left; padding: 12px 18px; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); background: var(--paper-2); border-bottom: 1px solid var(--line); }
.ledger th.r, .ledger td.r { text-align: right; }
.ledger tbody td { padding: 13px 18px; border-bottom: 1px solid var(--line-soft); font-size: 14px; vertical-align: middle; }
.ledger tbody tr:last-child td { border-bottom: 0; }
.ledger tbody tr { transition: background .12s; }
.ledger tbody tr:hover { background: var(--canvas); }
.ledger .l-date { font-size: 12px; color: var(--muted); white-space: nowrap; }
.ledger .l-nar { font-weight: 500; }
.ledger .l-nar small { display: block; color: var(--faint); font-size: 11.5px; font-weight: 400; margin-top: 2px; }
/* WHICH COLOUR EACH MONEY COLUMN TAKES — our rule, not the pack's.
   The pack colours by the direction cash moved. This app colours by the
   effect on THAT party's balance, which is the convention Karan settled for
   the Ledger tab and the one already shipped on this statement: a debit
   raises what the party owes the shop, a credit lowers it. Reversing them
   here to match the mockup would have left one screen disagreeing with
   another about what green means. */
.ledger .l-deb { color: var(--pos); font-weight: 600; }
.ledger .l-cre { color: var(--neg); font-weight: 600; }
/* A row that moves nobody's balance (a Kharcha, money in): one figure across
   both amount columns, in neither side's colour. */
.ledger .l-neu { color: var(--ink); font-weight: 600; text-align: center; white-space: nowrap; }
/* The empty half of a row: every entry sits in one column, so the other one
   prints a dash and must not read as a figure. */
.ledger .l-dim { color: var(--ghost); }
.ledger .l-bal { font-weight: 700; font-family: var(--f-serif); }
.ledger .l-act { opacity: 0; transition: opacity .14s; }
.ledger tr:hover .l-act { opacity: 1; }
.ledger tfoot td { padding: 14px 18px; background: var(--paper-2); border-top: 2px solid var(--line-strong); font-weight: 700; font-size: 13px; }

/* day-grouped entry cards (mobile) */
/* The Ledger tab. Everything below the toolbar is the pack's — the chip,
   the table, the badge, the day card — so these are only the joins: the chip
   row's own spacing (tighter than the generic .row), the widths this table's
   six columns want, and the button that admits the list has another page. */
.ledger-chips, .party-chips { gap: 8px; margin-bottom: 16px; }
/* A type the running search cannot reach. Dimmed, never removed: dropping it
   would rearrange the row under the reader's cursor mid-keystroke. */
.chip--empty { opacity: .5; }
.chip--empty:hover { opacity: 1; }
.ledger-more { display: flex; justify-content: center; margin-top: 18px; }
.ledger .l-clock { color: var(--ghost); }
.ledger .l-th-date { width: 118px; }
.ledger .l-th-type { width: 150px; }
.ledger .l-th-amt { width: 132px; }
.ledger .l-th-by { width: 104px; }
.ledger .l-th-act { width: 44px; }
/* The balance column carries the widest thing on the row — a figure plus the
   " CR" the server appends when the shop is the one who owes — so it gets its
   own width and none of the three money columns is allowed to wrap. A rupee
   figure broken across two lines stops reading as one number. */
.ledger .l-th-bal { width: 154px; }
.ledger .l-deb, .ledger .l-cre, .ledger .l-bal, .ledger .l-dim { white-space: nowrap; }
.ledger tbody tr[data-action] { cursor: pointer; }
/* A carried row (see .frow--archived): faded, and nothing happens on hover. */
.ledger tbody tr.l-archived { opacity: .72; }
.ledger tbody tr.l-archived:hover { background: none; }
.ledger tr.l-archived .l-bal { color: var(--muted); }
.ledger tbody tr:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* A phone has no room for six columns; the tab draws the pack's day cards
   there instead, and the toolbar stops competing with them for width. */
/* Inside an account, Home IS that account. The shop's headline and its two
   summary cards belong to the overview the user has just stepped out of, and
   leaving them above one party's statement reads as though the figures were
   that party's.
   Deliberately NOT `sec12-hidden`: that class has exactly one owner on any
   block carrying `data-home-layouts` — renderHomeLayout — and two of these
   three do. This is a plain rule keyed off a different class, so the owner
   still decides what is on screen the moment the account is closed, and its
   `!important` outranks this rule in the meantime. */
body.in-party-view #home-hdr,
body.in-party-view #home-hero,
body.in-party-view #home-minis,
body.in-party-view #date-question { display: none; }

/* The date question card (Karan, 24/09/2026) — a question put to the shop about
   an imported file, at the top of Home while it is open. The accent edge says
   "this wants an answer" without the red of something having gone wrong. */
.dq {
  background: var(--paper); border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--r-lg); box-shadow: var(--sh-1); padding: 16px 18px; margin-bottom: 18px;
  display: flex; flex-direction: column; gap: 14px;
}
.dq__ex { display: flex; flex-direction: column; gap: 8px; }
.dq__ex + .dq__ex { border-top: 1px solid var(--line-soft); padding-top: 14px; }
.dq__p { margin: 0; color: var(--ink-2); font-size: 14px; line-height: 1.5; }
.dq__entry { font-size: 15px; }
.dq__ask { font-weight: 600; font-size: 14px; }
.dq__opts { display: flex; flex-wrap: wrap; gap: 8px; }
.dq__opt--ro {
  display: inline-flex; align-items: center; padding: 6px 11px; border-radius: var(--r-xs);
  border: 1px dashed var(--line-strong); color: var(--muted); font-size: 12.5px; font-weight: 600;
}
.dq__noted { color: var(--pos); font-weight: 600; font-size: 14px; }
.dq__foot { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border-top: 1px solid var(--line-soft); padding-top: 12px; }
.dq__deadline, .dq__staff { margin: 0; font-size: 13px; color: var(--muted); }
.dq__staff { color: var(--ink-2); font-weight: 600; }
/* A phone's answer is a thumb: every button here is a full 44px target. */
.is-mobile .dq__opt, .is-mobile .dq__more { min-height: 44px; }

/* The list of every entry the answer decides. Four narrow columns, so it fits
   a phone without a sideways scroll: dates are written "5 Feb 2026". */
.sheet--dq { max-width: 720px; }   /* a phone's `.is-mobile .sheet` still wins: full width */
.dq-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.dq-table th {
  text-align: left; padding: 8px 8px; font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--faint); border-bottom: 1px solid var(--line);
}
.dq-table td { padding: 9px 8px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.dq-table .num { text-align: right; white-space: nowrap; }
.dq__tag { margin-left: 4px; }
.is-mobile .dq-table { font-size: 12.5px; }
.is-mobile .dq-table th, .is-mobile .dq-table td { padding-left: 4px; padding-right: 4px; }

/* The Feed's search box sits in the column's own 14px rhythm, not the
   toolbar's extra margin as well. */
.feed__search { margin-bottom: 0; }
.is-mobile .ledger-chips, .is-mobile .party-chips { gap: 6px; margin-bottom: 14px; }
.is-mobile .ledger-chips .chip, .is-mobile .party-chips .chip { padding: 6px 10px; font-size: 12px; }

.dgroup { margin-bottom: 16px; }
.dgroup__h { display: flex; align-items: baseline; justify-content: space-between; padding: 4px 2px 8px; }
.dgroup__d { font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.dgroup__s { font-size: 11.5px; color: var(--faint); font-variant-numeric: tabular-nums; }

/* ═════════ HISTORY ═════════ */
.hist { display: grid; grid-template-columns: 400px 1fr; gap: 18px; align-items: start; }
.is-mobile .hist { grid-template-columns: 1fr; }
.hist__tl { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-1); overflow: hidden; max-height: 72vh; overflow-y: auto; }
.is-mobile .hist__tl { max-height: none; }
.hist__day { position: sticky; top: 0; z-index: 1; padding: 9px 16px; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); background: var(--paper-2); border-bottom: 1px solid var(--line-soft); }
.hist__item { display: grid; grid-template-columns: 30px 1fr auto; gap: 11px; align-items: center; padding: 13px 16px; border-bottom: 1px solid var(--line-soft); cursor: pointer; transition: background .12s; position: relative; }
.hist__item:hover { background: var(--canvas); }
.hist__item[aria-selected="true"] { background: var(--accent-soft); }
.hist__item[aria-selected="true"]::before { content:''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--accent); }
.hist__ic { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; font-weight: 700; font-size: 14px; flex: none; }
.hist__ic.create { background: var(--pos-soft); color: var(--pos); }
.hist__ic.update { background: var(--sky-soft); color: var(--sky); }
.hist__ic.delete { background: var(--neg-soft); color: var(--neg); }
.hist__t { font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hist__m { font-size: 11px; color: var(--faint); margin-top: 2px; }
.hist__time { font-size: 11px; color: var(--faint); white-space: nowrap; font-variant-numeric: tabular-nums; }

.hist__detail { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-1); padding: 22px; min-height: 320px; }
.hist__detail-h { padding-bottom: 16px; margin-bottom: 16px; border-bottom: 1px solid var(--line-soft); }
.hist__detail-t { font-family: var(--f-serif); font-weight: 500; font-size: 21px; letter-spacing: -.01em; margin-bottom: 8px; }
.hist__detail-m { font-size: 12px; color: var(--faint); display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.diff { display: grid; grid-template-columns: 130px 1fr 1fr; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line-soft); font-size: 13px; align-items: center; }
.is-mobile .diff { grid-template-columns: 1fr; gap: 5px; }
.diff__f { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--faint); }
.diff__b, .diff__a { padding: 7px 11px; border-radius: 8px; font-variant-numeric: tabular-nums; font-weight: 500; }
.diff__b { background: var(--neg-soft); color: var(--neg); text-decoration: line-through; text-decoration-color: rgba(var(--neg-rgb),.4); }
.diff__a { background: var(--pos-soft); color: var(--pos); }
.diff__b:empty::after, .diff__a:empty::after { content: '—'; color: var(--ghost); text-decoration: none; }
/* The column captions above the first diff row, and the shape a diff takes
   when there is nothing to compare against — an add has no "before", a delete
   has no "after". */
.diff--head { border-bottom: 1px solid var(--line-strong); }
.diff--head .diff__f { color: var(--muted); }
.diff--single { grid-template-columns: 130px 1fr; }
.is-mobile .diff--single { grid-template-columns: 1fr; }
/* Stacked on a phone, the three column captions become three loose words
   above the first row. The cells say it themselves instead. */
.is-mobile .diff--head { display: none; }
.is-mobile .diff__b::before, .is-mobile .diff__a::before {
  display: block; font-size: 10px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; opacity: .75; margin-bottom: 2px;
}
.is-mobile .diff__b::before { content: 'Before'; }
.is-mobile .diff__a::before { content: 'After'; }
/* An add has no before and a delete has no after; a lone labelled cell in a
   single-column row would be labelling nothing. */
.is-mobile .diff--single .diff__b::before, .is-mobile .diff--single .diff__a::before { content: none; }
/* The change's own verb, sitting at the end of the detail's meta line. */
.hist__action { text-transform: capitalize; }
/* On a phone the two panes are one screen at a time: the timeline, then the
   change you tapped, with this button to get back. It reads the SAME
   `is-mobile` class every other part of the page switches on — this tab used
   to carry two thresholds of its own and they disagreed with each other
   between 860 and 899 pixels wide. */
.hist__back { display: none; }
.is-mobile .hist__back { display: inline-flex; margin-bottom: 14px; }
.is-mobile #history-detail { display: none; }
.is-mobile.h-mobile-detail #history-timeline { display: none; }
.is-mobile.h-mobile-detail #history-detail { display: block; }

/* ═════════ SETTINGS ═════════ */
.set { display: grid; grid-template-columns: 210px 1fr; gap: 26px; align-items: start; }
.is-mobile .set { grid-template-columns: 1fr; gap: 14px; }
.set__nav { display: flex; flex-direction: column; gap: 3px; position: sticky; top: 18px; }
.is-mobile .set__nav { flex-direction: row; overflow-x: auto; position: static; padding-bottom: 4px; }
.set__nav button { text-align: left; padding: 10px 14px; border-radius: var(--r-sm); font-size: 13.5px; font-weight: 500; color: var(--muted); white-space: nowrap; transition: background .14s, color .14s; }
.set__nav button:hover { background: var(--paper); color: var(--ink); }
.set__nav button[aria-selected="true"] { background: var(--accent-soft); color: var(--accent-ink); }
.set__panel { display: flex; flex-direction: column; gap: 16px; }
.scard { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-1); padding: 22px; }
.scard__t { font-family: var(--f-serif); font-weight: 500; font-size: 19px; letter-spacing: -.01em; }
.scard__s { color: var(--muted); font-size: 13px; margin: 4px 0 18px; }
.srow2 { display: grid; grid-template-columns: 200px 1fr auto; gap: 14px; align-items: center; padding: 14px 0; border-top: 1px solid var(--line-soft); }
.srow2:first-of-type { border-top: 0; padding-top: 0; }
.is-mobile .srow2 { grid-template-columns: 1fr auto; }
.is-mobile .srow2 > :first-child { grid-column: 1 / -1; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--faint); font-weight: 700; }
.srow2__l { font-size: 13px; color: var(--muted); }
.srow2__v { font-size: 14px; font-weight: 500; }
.srow2__v small { display: block; color: var(--faint); font-weight: 400; font-size: 12px; margin-top: 2px; }

.mcard { display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: start; background: var(--canvas); border: 1px solid var(--line); border-radius: var(--r); padding: 14px 16px; margin-bottom: 8px; }
.mcard__text { font-size: 13.5px; line-height: 1.5; }
.mcard__meta { font-size: 11px; color: var(--faint); margin-top: 6px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.mcard__imp { height: 4px; width: 44px; border-radius: 999px; background: var(--canvas-2); overflow: hidden; display: inline-block; vertical-align: middle; }
.mcard__imp i { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
/* How hard the bot leans on a remembered fact. Three bands rather than a
   colour written onto the element, so both themes get the right one. */
.mcard__imp--high i { background: var(--pos); }
.mcard__imp--mid i { background: var(--amber); }
.mcard__imp--low i { background: var(--ghost); }
/* A phone has no room for a 210px sidebar beside the panel, so the sections
   become a scrolling row of buttons above it. ONE threshold, the same
   `is-mobile` class every other part of the page switches on — Settings used
   to carry two of its own (899 and 900). */
.is-mobile .set { grid-template-columns: 1fr; gap: 14px; }
.is-mobile .set__nav {
  flex-direction: row; position: static; gap: 6px;
  overflow-x: auto; padding-bottom: 2px; scrollbar-width: none;
}
.is-mobile .set__nav::-webkit-scrollbar { display: none; }
.is-mobile .set__nav button { white-space: nowrap; border: 1px solid var(--line-strong); }
.is-mobile .srow2 { grid-template-columns: 1fr; gap: 4px; }

/* ═════════ ADD ENTRY (fast) ═════════ */
.typegrid { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
.typebtn { display: flex; align-items: center; gap: 11px; padding: 13px; text-align: left; border: 1.5px solid var(--line-strong); background: var(--paper); border-radius: var(--r-sm); transition: all .14s var(--spring); }
.typebtn:hover { border-color: var(--ghost); background: var(--canvas); }
.typebtn:active { transform: scale(.98); }
.typebtn[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); box-shadow: var(--ring); }
.typebtn__ic { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; flex: none; }
.typebtn__l { display: block; font-size: 13.5px; font-weight: 600; }
.typebtn__s { display: block; font-size: 11px; color: var(--faint); margin-top: 1px; }
/* A 375px sheet leaves ~86px beside the icon at the pack's desktop spacing,
   and "Credit Given" is wider than that — every label wrapped and the four
   cards came out ragged. The room comes out of the chrome, not the columns:
   dropping to one column would push the amount box below the fold. */
.is-mobile .typegrid { gap: 8px; }
.is-mobile .typebtn { padding: 10px; gap: 8px; }
.is-mobile .typebtn__ic { width: 32px; height: 32px; }

.amountbox { display: grid; grid-template-columns: 52px 1fr; align-items: stretch; border: 1.5px solid var(--line-strong); border-radius: var(--r); overflow: hidden; background: var(--canvas); transition: border-color .15s, box-shadow .15s, background .15s; }
.amountbox:focus-within { border-color: var(--accent); box-shadow: var(--ring); background: var(--paper); }
.amountbox__cur { display: grid; place-items: center; background: var(--canvas-2); color: var(--muted); font-family: var(--f-serif); font-size: 22px; font-weight: 500; }
.amountbox__in { border: 0; background: transparent; outline: none; padding: 14px 14px; font-family: var(--f-serif); font-weight: 500; font-size: 28px; font-variant-numeric: tabular-nums; }
.quickchips { display: flex; gap: 7px; flex-wrap: wrap; }
.quickchip { padding: 7px 13px; border-radius: 999px; border: 1px solid var(--line-strong); background: var(--paper); font-size: 13px; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; transition: all .13s; }
.quickchip:hover { border-color: var(--accent-line); color: var(--accent-ink); background: var(--accent-soft); }

.pickrow { display: flex; align-items: center; justify-content: space-between; padding: 11px 13px; background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-sm); cursor: pointer; transition: background .12s; }
.pickrow:hover { background: var(--canvas); }
.pickrow + .pickrow { margin-top: 6px; }
/* The pack's pickrow is a <div> holding an avatar and a balance. The same row
   is used here as a menu item, so it needs a left label and a right hint, and
   it is a <button> — width: 100% because a button does not stretch to its
   flex container the way a div does, and text-align because a button centres
   by default. */
.pickrow { width: 100%; text-align: left; }
.pickrow__t { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.pickrow__h { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
.pickrow--danger .pickrow__t { color: var(--neg); }
.pickrow--danger { border-color: var(--neg-line); }
.pickrow + .divline { margin: 10px 0 4px; }

/* tweaks panel */
.tweaks { position: fixed; right: 18px; bottom: 18px; z-index: 9000; width: 264px; background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-pop); overflow: hidden; animation: pop .22s var(--spring); }
.tweaks__h { padding: 13px 16px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line-soft); }
.tweaks__h b { font-size: 12px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.tweaks__b { padding: 14px 16px; display: flex; flex-direction: column; gap: 16px; }
.tweaks__g { display: flex; flex-direction: column; gap: 8px; }
.tweaks__g > span { font-size: 11px; font-weight: 600; color: var(--faint); }

/* ═════════ FIRST SIGN-IN — which arrangement of Home? ═════════
   Three cards, each drawing the arrangement it offers at a small size out of
   the shop's own figures (see _layoutPreview). Opened only when nobody has
   answered yet; one tap answers it for good. */
.chooser {
  position: fixed; inset: 0; z-index: 1200;
  display: grid; place-items: center; padding: 24px;
  background: rgba(var(--scrim-rgb), .55);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  overflow-y: auto;
}
.chooser__panel {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--sh-3); padding: 30px; width: 100%; max-width: 940px;
  animation: rise .34s var(--spring) backwards;
}
.chooser__title { font-family: var(--f-serif); font-weight: 500; font-size: 26px; letter-spacing: -.02em; margin-bottom: 8px; }
.chooser__sub { color: var(--muted); font-size: 14px; line-height: 1.5; max-width: 62ch; margin-bottom: 24px; }
.chooser__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }

.lcard {
  /* flex, not block: a <button> whose content is shorter than the box CENTRES
     it vertically, and the grid stretches all three cards to the tallest. The
     card with the one-line description sat ten pixels lower than its
     neighbours until this was set. */
  display: flex; flex-direction: column; align-items: stretch;
  text-align: left; width: 100%;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r);
  padding: 14px; box-shadow: var(--sh-1);
  transition: transform .14s var(--spring), box-shadow .14s, border-color .14s;
}
.lcard:hover, .lcard:focus-visible { transform: translateY(-3px); box-shadow: var(--sh-2); border-color: var(--accent); }
.lcard:active { transform: translateY(-1px) scale(.99); }
.lcard__prev {
  /* --mini-h is the miniature's own box, and the preview frame is that plus
     its 9px padding and 1px border on each side. Tying the two together is
     what stops the arrangement inside from being cropped mid-row: Pulse needs
     a hero of ~60 plus two 24px rows and the gaps between them, and it used to
     get 98 to draw 118 in. The phone rule below scales this same box, so
     changing the height here changes both. */
  --mini-h: 142px;
  display: block; height: calc(var(--mini-h) + 20px); overflow: hidden;
  background: var(--canvas); border: 1px solid var(--line-soft); border-radius: var(--r-sm);
  padding: 9px; margin-bottom: 12px;
}
.lcard__name { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 600; margin-bottom: 3px; }
.lcard__desc { display: block; font-size: 12.5px; color: var(--muted); line-height: 1.45; }

/* The miniatures. Everything is a <span>, because each card is a <button>. */
.mini { display: flex; flex-direction: column; gap: 5px; height: 100%; }
.mini--streams, .mini--feed { flex-direction: row; gap: 7px; }
.mini--feed .mini-main { flex: 1.6; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
/* Wider than the pack's rail because ours carries a real rupee figure
   rather than a grey bar: the shop's own net position, server-formatted
   and printed whole. At the pack's width it lost its last digit. */
.mini--feed .mini-side { flex: 1.25; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.mini-col { flex: 1; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.mini-colhead { display: block; height: 16px; border-radius: 4px; padding: 5px 5px 0; }
.mini-colhead.pos { background: var(--pos-soft); }
.mini-colhead.neg { background: var(--neg-soft); }
.mini-hero {
  display: block; background: var(--paper); border: 1px solid var(--line-soft);
  border-radius: 6px; padding: 8px 9px; margin-bottom: 2px;
}
.mini-card { display: block; flex: 1; background: var(--paper); border: 1px solid var(--line-soft); border-radius: 6px; padding: 7px 8px; }
.mini-card--short { flex: .6; }
.mini-num { display: block; font-family: var(--f-serif); font-weight: 500; font-size: 15px; letter-spacing: -.02em; line-height: 1; white-space: nowrap; overflow: hidden; }
.mini-num .rs { font-size: .6em; color: var(--faint); margin-inline-end: .1em; }
.mini-spark { display: block; margin-top: 5px; }
.mini-spark svg { display: block; width: 100%; height: auto; }
/* The side rail's figure has a third of the width the hero's does, so it is
   set smaller rather than clipped mid-number. */
.mini--feed .mini-num { font-size: 11.5px; }
.mini-day { display: block; font-size: 8px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); }
.mini-row {
  display: flex; align-items: center; gap: 5px;
  background: var(--paper); border: 1px solid var(--line-soft); border-radius: 5px; padding: 5px 6px;
}
.mini-av { width: 12px; height: 12px; border-radius: 4px; flex: none; background: var(--canvas-2); }
.mini-line { flex: 1; height: 4px; border-radius: 999px; background: var(--line); min-width: 0; }
.mini-line--wide { display: block; height: 4px; border-radius: 999px; background: rgba(var(--ink-rgb), .16); width: 60%; }
.mini-amt { width: 22px; height: 4px; border-radius: 999px; flex: none; background: currentColor; opacity: .55; }

/* On a phone the three cards lie DOWN — thumbnail on the left, name and
   blurb on the right. Stacked full-width cards made each miniature twice as
   wide as it is on a laptop while staying the same height, so it read as a
   zoomed-in crop rather than a small screen; worse, the panel came out taller
   than the phone and the third option sat below the fold, which is a bad way
   to ask somebody to choose between three things. Laid down, all three fit on
   one screen at their right proportions. The overlay still scrolls, for a
   short window or very large text. */
.is-mobile .chooser { padding: 14px; align-items: start; }
.is-mobile .chooser__panel { padding: 20px 16px; }
.is-mobile .chooser__title { font-size: 21px; }
.is-mobile .chooser__sub { font-size: 13.5px; margin-bottom: 18px; }
.is-mobile .chooser__grid { grid-template-columns: 1fr; gap: 10px; }
.is-mobile .lcard {
  display: grid; grid-template-columns: 126px 1fr;
  grid-template-areas: "prev name" "prev desc";
  align-items: center; column-gap: 13px; padding: 12px;
}
/* The thumbnail is the LAPTOP miniature, drawn at its own size and then
   scaled down as one piece — not a narrower miniature. Re-flowing it into a
   126px box cut the shop's net figure to "₹34,750." and the feed rail's to
   "₹3", because a rupee figure is as long as it is and does not reflow.
   Scaling keeps every proportion and every digit; 160 x 122 at 0.7 lands
   exactly on the 112 x 85 the padding leaves. */
.is-mobile .lcard {
  grid-template-columns: 152px 1fr;
}
/* The phone thumbnail is the LAPTOP miniature drawn at its own size and then
   scaled down as one piece — not a narrower miniature. Re-flowing it into a
   126px box cut the shop's net figure to "₹34,750." and the feed rail's to
   "₹3", because a rupee figure is as long as it is and does not reflow.
   --mini-w is the width the miniature has on a laptop at the panel's full
   width; the frame's own height is derived from --mini-h so the two rules
   cannot drift apart. */
.is-mobile .lcard__prev {
  --mini-w: 235px; --mini-scale: .58;
  grid-area: prev; margin-bottom: 0; padding: 7px;
  height: calc(var(--mini-h) * var(--mini-scale) + 16px);
}
.is-mobile .lcard__prev > .mini {
  width: var(--mini-w); height: var(--mini-h);
  transform: scale(var(--mini-scale)); transform-origin: top left;
}
.is-mobile .lcard__name { grid-area: name; align-self: end; margin-bottom: 2px; }
.is-mobile .lcard__desc { grid-area: desc; align-self: start; }
/* Lying down, the card is not lifted on tap — there is nothing above it to
   lift away from, and the row would shove its neighbours. */
.is-mobile .lcard:active { transform: scale(.995); }

/* ── Selecting several accounts to delete them together (Karan, 12/09/2026) ──
   Every colour here is a token: a raw hex would render wrong in one theme, and
   `tests/test_design_tokens_ssot.py` refuses one anyway. */
.toolbar__pick {
  flex: none; height: 44px; padding: 0 16px;
  background: var(--paper); color: var(--ink); border: 1px solid var(--line);
}
.is-mobile .toolbar__pick { padding: 0 12px; }
/* Its OWN hiding class, never `sec12-hidden`: that one is already owned by the
   admin-only pass at sign-in, and one attribute with two owners means whichever
   ran last wins. Same idiom as `.attn__rail.is-off`. */
.toolbar__pick.is-off { display: none; }
.pickbar {
  display: flex; align-items: center; gap: 10px; margin: 0 0 12px;
  padding: 10px 12px; border-radius: var(--r);
  background: rgba(var(--neg-rgb), .07); border: 1px solid var(--neg-line);
}
.pickbar__n { font-weight: 700; color: var(--ink); margin-right: auto; }
.pickbar__cancel { background: var(--paper); color: var(--ink); border: 1px solid var(--line); }
.pickbar__del { background: var(--neg); color: var(--on-accent); border: 1px solid var(--neg); }
.pickbar__del[disabled] { opacity: .5; cursor: not-allowed; }

/* The tick sits where the avatar does, so a picked row reads as picked from the
   left edge rather than from a mark hiding at the far end of a wide row. */
.prow--pick { cursor: pointer; }
.prow--pick.is-picked { background: rgba(var(--accent-rgb), .08); border-color: var(--accent); }
/* `.prow` is a GRID, not a flex row — `flex: 0 0 18px` here was inert, and a new
   first child shifted auto-placement by one cell. On the phone's three-track
   grid that pushed `.prow__info` onto a second row in a 36px column, wrapping
   the account name one character wide: the only thing on the row that says
   whose books are about to be destroyed. So the tick TAKES the avatar's cell
   rather than sitting before it, which is what the note above always said. */
.prow__tick {
  width: 18px; height: 18px; border-radius: var(--r-xs);
  border: 1.5px solid var(--line-strong); background: var(--paper);
  grid-column: 1; grid-row: 1; align-self: center; justify-self: center;
}
.prow--pick .prow__av { display: none; }
.prow__tick.is-on { background: var(--accent); border-color: var(--accent); position: relative; }
.prow__tick.is-on::after {
  content: ''; position: absolute; left: 5px; top: 1px; width: 5px; height: 10px;
  border: solid var(--on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg);
}

.bulkdel__lead { font-size: 15px; line-height: 1.6; color: var(--ink); }
.bulkdel__names { margin-top: 6px; font-size: 13px; color: var(--muted); }
/* The warning panel and the footer are `.dangerpanel` and `.sheet__foot`, the
   page's own primitives — this sheet carried private copies of both until
   16/09/2026. All that is left is the spacing between them, which no existing
   rule covers. */
.bulkdel__lead + .dangerpanel { margin-top: 14px; }
.dangerpanel + .bulkdel__ack { margin-top: 14px; }
/* A button, not <input type="checkbox">: every interactive element on this page
   routes through the data-action dispatcher under the SEC-12 CSP. */
.bulkdel__ack {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: 0; padding: 6px 0; cursor: pointer;
  color: var(--ink); font-size: 14px; text-align: left;
}
.bulkdel__box {
  flex: 0 0 20px; width: 20px; height: 20px; border-radius: var(--r-xs);
  border: 1.5px solid var(--line-strong); background: var(--paper); position: relative;
}
.bulkdel__ack.is-on .bulkdel__box { background: var(--neg); border-color: var(--neg); }
.bulkdel__ack.is-on .bulkdel__box::after {
  content: ''; position: absolute; left: 6px; top: 2px; width: 5px; height: 11px;
  border: solid var(--on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.bulkdel__prog { margin-top: 12px; font-size: 13px; color: var(--muted); }
