/* ============================================================================
   Collection Dashboard — Bharat Fincare
   ----------------------------------------------------------------------------
   A recovery desk, not a report. Somebody sits in this eight hours a day making
   phone calls, so the screen is built to carry rows, not to frame them.

   The system, in one paragraph. Two faces from one family: IBM Plex Sans for
   language, IBM Plex Mono for every quantity, identifier and timestamp — face
   separates KIND, size separates RANK, which is the work eleven near-identical
   font sizes were failing to do. Seven sizes. Four weights, none fractional.
   Six neutrals on one hue. Four state colours, each with a text tier that
   passes AA and a separate mark tier for rails and bars. One 4px spacing scale.
   Rows are 30px and ruled by a hairline; nothing is a card unless it genuinely
   floats. Every left edge on a page falls on the same vertical line.

   What this replaces: 19 font sizes, 11 weights (seven of them fractional and
   therefore invisible off macOS), 62 loose hex values, 31 spacing values, and
   112 inline style attributes.
   ========================================================================== */

/* ── Faces ───────────────────────────────────────────────────────────────
   Self-hosted. The CSP is default-src 'self', so /fonts/*.woff2 is allowed
   with no server change; a CDN would be blocked.

   latin-ext is NOT optional. U+20B9 — the rupee sign — is not in the latin
   subset (which carries U+20AC, the euro, and stops). Ship latin alone and
   every ₹ in the application falls back to a system face at a different width,
   which is exactly why the money columns never lined up. */
@font-face {
  font-family: Plex; src: url(/fonts/plex-latin.woff2) format('woff2');
  font-weight: 100 700; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: Plex; src: url(/fonts/plex-latin-ext.woff2) format('woff2');
  font-weight: 100 700; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+1E00-1E9F,U+1EF2-1EFF,
    U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: PlexMono; src: url(/fonts/mono-latin-400.woff2) format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF,U+2000-206F,U+20AC,U+2122,U+2212,U+FEFF,U+FFFD;
}
@font-face {
  font-family: PlexMono; src: url(/fonts/mono-latin-ext-400.woff2) format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA,U+20A0-20C0,U+2113;
}
/* Mono is static cuts, not a variable file like Plex: a weight with no face of
   its own is drawn with the nearest one. Without these, every .money, .led__v and
   legend figure asking for 500 rendered at exactly 400. */
@font-face {
  font-family: PlexMono; src: url(/fonts/mono-latin-500.woff2) format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF,U+2000-206F,U+20AC,U+2122,U+2212,U+FEFF,U+FFFD;
}
@font-face {
  font-family: PlexMono; src: url(/fonts/mono-latin-ext-500.woff2) format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA,U+20A0-20C0,U+2113;
}
@font-face {
  font-family: PlexMono; src: url(/fonts/mono-latin-600.woff2) format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF,U+2000-206F,U+20AC,U+2122,U+2212,U+FEFF,U+FFFD;
}
@font-face {
  font-family: PlexMono; src: url(/fonts/mono-latin-ext-600.woff2) format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA,U+20A0-20C0,U+2113;
}

:root {
  --font: Plex, 'IBM Plex Sans', 'Segoe UI', system-ui, -apple-system, Arial, sans-serif;
  --mono: PlexMono, 'IBM Plex Mono', Consolas, ui-monospace, Menlo, monospace;
  --figs: 'tnum' 1, 'lnum' 1, 'zero' 1;      /* tabular · lining · slashed zero */

  /* ── Type. Seven sizes. Every step is a visible jump.
        The 11/12/13 cluster is legitimate: each is a different role in a
        different case or face — 11 is uppercase, 12 is muted lowercase,
        13 is full-ink lowercase. Uppercase 11 beside lowercase 13 is a
        bigger visual step than 13 beside 17. */
  --t-cap:   11px;   /* column heads, nav, tabs, eyebrows — UPPERCASE       */
  --t-meta:  12px;   /* sub-lines, timestamps, units, microcopy             */
  --t-body:  13px;   /* row text, names, prose, inputs                      */
  --t-fig:   15px;   /* a money figure inside a row                         */
  --t-lead:  18px;   /* page title, section title                           */
  --t-kpi:   24px;   /* the figures in a summary strip                      */
  --t-hero:  38px;   /* one per page, at most                               */

  /* ── Weight. Four. Never a fractional value: the old file asked for
        460/540/560/620/640/650/660, which only resolve against SF Pro's
        variable axis on macOS and collapse to 400/700 on the Windows
        machines the agents actually use — so the hierarchy existed only
        on the developer's screen. */
  --w-reg: 400; --w-med: 500; --w-semi: 600; --w-bold: 700;

  --tr-cap: .08em;    /* uppercase only     */
  --tr-0:   0;        /* anything ≤ 18px    */
  --tr-big: -.015em;  /* figures ≥ 24px     */

  /* ── Neutrals. Six, one hue, warm rather than blue-white.
        Contrast against --sheet is stated and held. */
  --ink:    #14161A;  /* 18.1:1  names, figures, headings                   */
  --ink-2:  #4A4E57;  /*  8.3:1  secondary prose, units                     */
  --ink-3:  #6C707A;  /*  4.96:1 on sheet, 4.62 on band — the AA floor      */
  --ink-4:  #A2A6AE;  /*  2.6:1  NON-TEXT ONLY: ticks, disabled glyphs      */
  --rule:   #E4E4DF;  /* the hairline that does all the separating          */
  --rule-2: #C6C6BF;  /* the heavier rule: under column heads, above totals */
  --paper:  #FBFBF9;  /* page ground                                        */
  --sheet:  #FFFFFF;  /* the sheet the data is ruled onto                   */
  --band:   #F7F7F3;  /* row hover and counting band                        */

  /* ── State. Four hues, and this is the whole chromatic budget.
        Each carries four tiers, which is what the old palette lacked: one
        hex was doing both text and background duty and could not pass
        contrast in both jobs.
          (no suffix) text — passes AA on white
          -mark        rails, bars, dots — non-text, needs only 3:1
          -w           wash / background
          -r           border */
  --bad:    #B0221C;  --bad-mark:  #D4453B;  --bad-w:  #F6E6E4;  --bad-r:  #E3B5B1;
  --warn:   #8A5A00;  --warn-mark: #C38619;  --warn-w: #F7EFDC;  --warn-r: #E0CB9C;
  --good:   #0B6B45;  --good-mark: #12A06B;  --good-w: #E2F0E9;  --good-r: #A8CFBE;
  --info:   #1B4FA0;  --info-mark: #3F72C4;  --info-w: #E6ECF7;  --info-r: #B4C4E0;
  /* Categorical marks for the Overview charts — identity, not state, so they are
     kept apart from the four status hues above. Six slots in this fixed order,
     validated for colour-blind separation on the white sheet (worst adjacent
     CVD ΔE 9.1, normal-vision 19.6); slots 3–5 are under 3:1 on white, so every
     slice carries a visible label beside it. MARKS ONLY — text never wears them. */
  --series-1: #2A78D6; --series-2: #EB6834; --series-3: #1BAF7A;
  --series-4: #EDA100; --series-5: #E87BA4; --series-6: #4A3AA7;

  /* ── Chrome. The slab the sidebar, the primary button, the sign-in panel and
        the avatars are painted on.
        This exists because --ink was doing two jobs that move in opposite
        directions: it was the darkest TEXT colour and also that slab's
        BACKGROUND. In a dark theme --ink has to become light so text can be
        read, which would have turned the sidebar white. The slab is dark in
        both themes, so everything sitting on it — all eleven hardcoded #fff and
        every rgba(255,255,255,a) overlay — keeps working untouched. */
  --chrome:       #14161A;   /* was --ink used as a background */
  --chrome-2:     #22252B;   /* was --navy-2 */
  --on-chrome:    #FFFFFF;
  --on-chrome-2:  rgba(255,255,255,.62);
  --on-chrome-3:  rgba(255,255,255,.50);
  --chrome-fill:  rgba(255,255,255,.13);
  --chrome-hover: rgba(255,255,255,.06);
  --chrome-on:    rgba(255,255,255,.12);
  --chrome-rule:  rgba(255,255,255,.12);
  --chrome-btn:   rgba(255,255,255,.08);
  --chrome-btn-b: rgba(255,255,255,.14);
  --scrim:        rgba(10,12,16,.38);

  /* The only blue used for focus, so it can never be mistaken for anything. */
  --focus:  #1D5FD6;
  --focus-glow: rgba(29,95,214,.16);

  /* Legacy aliases — the ten pages were written against these names, and
     renaming 2,000 lines of markup to land a stylesheet is how the last
     three attempts ended up applying to one page only. */
  --accent: var(--info); --accent-ink: #143C7A; --accent-bg: var(--info-w);
  --bg: var(--paper); --surface: var(--sheet); --surface-2: var(--band);
  --border: var(--rule); --border-2: var(--rule-2);
  --good-bg: var(--good-w); --warn-bg: var(--warn-w); --bad-bg: var(--bad-w);
  --navy: #14161A; --navy-2: #22252B;

  /* ── Space. 4px grid, six steps, nothing else.
        The horizontal inset is --s4 in every component, so every left edge
        on a page falls on one vertical line. */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;

  /* ── Geometry. Fixed row heights are what make a console a console. */
  --row: 30px;        /* every data row in the application                  */
  --row-head: 26px;   /* column head                                        */
  --ctl: 30px;        /* select, input, secondary button                    */
  --ctl-lg: 34px;     /* the one primary action on a screen                 */
  --side-w: 196px;

  --r: 2px;           /* controls, chips, drawer                            */
  --radius: 2px; --radius-lg: 3px;         /* legacy aliases                */

  /* One shadow, and only things that genuinely overlay may use it. */
  --shadow: none;
  --shadow-lg: -1px 0 0 var(--rule-2), -28px 0 56px -20px rgba(10,12,16,.24);
  --ease: 90ms cubic-bezier(.2,0,0,1);
  /* Two more, for the things that actually travel across the screen. 90ms is
     right for a hover and far too quick to read as movement, which is why the
     drawer used to look like it had teleported. The curve decelerates hard: the
     panel arrives fast and settles, rather than easing in at both ends. */
  --ease-layer: 260ms cubic-bezier(.22, 1, .36, 1);
  --ease-scrim: 170ms ease-out;
}

/* ══ Base ═══════════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  font: var(--w-reg) var(--t-body)/1.45 var(--font);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}

/* Face separates kind. Everything that is a quantity, an identifier or a
   timestamp is mono; everything that is language is sans. The eye separates
   two faces far faster than it separates 12.5px from 13px. */
.money, .num, .mono, .figure, .cell-sub, .t-mail, .id,
td.num, input[type="number"], time {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums lining-nums slashed-zero;
  font-feature-settings: var(--figs);
  letter-spacing: 0;
}
/* A column head is language even over a column of figures: .num right-aligns it
   but must not hand it the mono face, or one row of heads mixes two faces and
   loses its caps tracking half-way along. Tracked caps run wider than untracked
   mono did, so a long head may take two lines: two at 1.15 still fit --row-head,
   where one line ran into the next column at 1366. The 1.15 is on every th (the
   Tables block), or a one-line figure head sits a pixel below its neighbours. */
th.num {
  font-family: var(--font); letter-spacing: var(--tr-cap);
  font-feature-settings: normal; font-variant-numeric: normal;
  white-space: normal;
}

a { color: var(--info); text-underline-offset: 2px; }
/* Zero-weight on the element, so any class that paints an anchor — a.btn, the
   sidebar brand, a desk panel — keeps its own colour on hover instead of every
   link-shaped control turning navy. */
:where(a):hover { color: var(--accent-ink); }
/* Inside a data table the row is the object, not the link — so a link there
   takes the row's colour and earns its underline on hover. */
td a, .led__row, .qrow a.btn { color: inherit; text-decoration: none; }
td a:hover { text-decoration: underline; }

/* The browser's own [hidden] rule is display:none at the lowest possible
   weight, so any class that sets display beats it — .btn does. This app
   shipped three permanently-visible buttons that way. State it with weight. */
[hidden] { display: none !important; }

/* Nothing in this application had a focus style before. Two treatments, one per
   kind of control. Something you press gets a ring clear of its edge. A box you
   type into lights its own edge instead: a ring 2px outside a bordered field
   reads as two borders. Inside anything that clips (a row, the nav list) the
   ring moves inward with outline-offset:-2px, or it is cut off. */
:where(a, button, summary, [tabindex], input[type="checkbox"], input[type="radio"]):focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r);
}
:where(input:not([type="checkbox"], [type="radio"]), select, textarea):focus {
  outline: none; border-color: var(--focus); box-shadow: 0 0 0 2px var(--focus-glow);
}
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

/* ══ Shell ══════════════════════════════════════════════════════════════ */
.app { display: grid; grid-template-columns: var(--side-w) 1fr; min-height: 100vh; }

/* The sidebar is exactly one screen tall and sticky, so nothing in it scrolls
   with the page: whatever does not fit is not further down, it is gone. The
   overflow here is the backstop for the case the rule on .side__nav cannot
   cover — a screen so short that the brand and the footer alone exceed it. */
.side {
  background: var(--chrome); color: var(--on-chrome); padding: var(--s4) var(--s3);
  display: flex; flex-direction: column; gap: var(--s5); position: sticky; top: 0; height: 100vh;
  overflow-y: auto; overscroll-behavior: contain;
}
.side__brand { display: flex; align-items: center; gap: var(--s2); text-decoration: none; color: var(--on-chrome); padding: 0 var(--s1); }
.side__glyph {
  width: 26px; height: 26px; border-radius: var(--r); background: var(--chrome-fill);
  display: grid; place-items: center; font-size: var(--t-cap); font-weight: var(--w-bold); letter-spacing: .04em;
}
.side__name { font-size: var(--t-meta); font-weight: var(--w-semi); line-height: 1.25; }
.side__name em { font-style: normal; font-weight: var(--w-reg); color: var(--on-chrome-3); }

/* `flex: 1` alone is not enough. A flex item's default min-height is `auto`,
   which refuses to shrink below its own content, so the twelve links floored
   this list at their min-content height of 371px and pushed .side__foot — the
   profile link, the theme picker and Sign out — off the bottom of a sidebar
   that cannot be scrolled to reach them. Measured: at a 613px viewport Sign out
   sits on screen, at 573px it is 6px past the edge and unreachable, which is an
   ordinary 1366x768 laptop once the taskbar, the browser chrome and a bookmarks
   bar are taken off. min-height:0 lets the list shrink; overflow-y gives it
   somewhere to put what no longer fits. */
.side__nav { display: flex; flex-direction: column; gap: 1px; flex: 1; min-height: 0; overflow-y: auto; }
/* flex:none — with the default shrink, a short window squashed all twelve links to
   19px before the list above would scroll at all. */
.side__link {
  display: flex; align-items: center; gap: var(--s2); padding: 0 var(--s2); flex: none;
  height: var(--ctl); border-radius: var(--r); text-decoration: none;
  color: var(--on-chrome-2); font-size: var(--t-body); font-weight: var(--w-med);
  transition: background var(--ease), color var(--ease);
}
.side__link:hover { background: var(--chrome-hover); color: var(--on-chrome); }
/* overflow-y:auto on .side__nav clips sideways too, and a link is the list's full width. */
.side__link:focus-visible { outline-offset: -2px; }
.side__link.is-on { background: var(--chrome-on); color: var(--on-chrome); }
.side__icon { display: grid; place-items: center; width: 16px; height: 16px; flex: none; }
.side__icon svg { width: 16px; height: 16px; }

/* ── Sections ───────────────────────────────────────────────────────────────
   Thirteen links flat gave a manager no hierarchy to scan: "My work", opened
   all day, sat at exactly the weight of "Activity log", opened twice a year.
   The headings are the quietest thing that can still be seen — the same
   uppercase micro-label the stat tiles use, at .50 alpha, so they read as
   dividers rather than as more links to consider. The space above each one does
   more work than the text does. */
.side__ghead {
  flex: none; padding: var(--s3) var(--s2) var(--s1);
  font-size: var(--t-meta); font-weight: var(--w-semi); letter-spacing: .06em;
  text-transform: uppercase; color: var(--on-chrome-3);
}
/* The first heading follows Overview and My work directly, so it needs less
   room above it than the ones separating two full sections. */
.side__nav > .side__ghead:first-child { padding-top: var(--s1); }

/* The Admin disclosure wears .side__link's metrics so it sits in the rhythm of
   the list, but it is a <button>: no underline, no inherited form styling, and
   the full width of the rail. */
.side__fold {
  display: flex; align-items: center; gap: var(--s2); width: 100%;
  padding: 0 var(--s2); margin-top: var(--s3); flex: none;
  height: var(--ctl); border: 0; border-radius: var(--r); background: none;
  color: var(--on-chrome-3); font: inherit; font-size: var(--t-meta);
  font-weight: var(--w-semi); letter-spacing: .06em; text-transform: uppercase;
  cursor: pointer; text-align: left;
  transition: background var(--ease), color var(--ease);
}
.side__fold:hover { background: var(--chrome-hover); color: var(--on-chrome); }
.side__fold:focus-visible { outline-offset: -2px; }
.side__caret { display: grid; place-items: center; width: 16px; height: 16px; flex: none; }
/* Points right when shut, down when open — rotated rather than swapped, so the
   movement itself says which way it went. */
.side__caret svg { width: 14px; height: 14px; transition: transform var(--ease); }
.side__fold[aria-expanded="true"] .side__caret svg { transform: rotate(90deg); }
/* The count is the whole reason a fold is acceptable here: it says what is
   inside without opening it, so nothing is hidden, only stowed. */
.side__foldn {
  margin-left: auto; font-size: var(--t-meta); font-weight: var(--w-med);
  letter-spacing: 0; color: var(--on-chrome-3);
  background: var(--chrome-fill); border-radius: var(--r);
  min-width: 18px; padding: 0 5px; text-align: center; line-height: 17px;
}
.side__fold:hover .side__foldn { color: var(--on-chrome); }
/* Indented under the fold so the nesting is visible without a connecting line,
   and the rule marks where the stowed group starts. */
.side__sub {
  display: flex; flex-direction: column; gap: 1px; flex: none;
  margin-left: var(--s3); padding-left: var(--s2);
  border-left: 1px solid var(--chrome-rule);
}

.side__foot { display: flex; flex-direction: column; gap: var(--s2); border-top: 1px solid var(--chrome-rule); padding-top: var(--s3); }
.side__user { display: flex; align-items: center; gap: var(--s2); min-width: 0; text-decoration: none; padding: var(--s1); margin: calc(var(--s1) * -1); border-radius: var(--r); }
a.side__user:hover, a.side__user.is-on { background: var(--chrome-btn); }
.side__user .avatar { background: var(--chrome-fill); color: var(--on-chrome); flex: none; }
.side__ident { min-width: 0; }
.side__uname { font-size: var(--t-body); font-weight: var(--w-med); color: var(--on-chrome); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side__urole { font-size: var(--t-meta); color: var(--on-chrome-3); }
/* Scoped under .side: the button also wears .btn/.btn--ghost/.btn--sm, which come
   later at the same weight, so a bare .side__out lost every declaration and Sign
   out was a white 24px button — and on hover white text on the --band fill. */
.side .side__out {
  background: var(--chrome-btn); border-color: var(--chrome-btn-b); color: var(--on-chrome);
  height: var(--ctl);
}
.side .side__out:hover:not(:disabled) { background: var(--chrome-on); border-color: var(--chrome-btn-b); color: var(--on-chrome); }

.avatar {
  width: 26px; height: 26px; border-radius: var(--r); background: var(--band); color: var(--ink-2);
  display: grid; place-items: center; font-size: var(--t-cap); font-weight: var(--w-semi); flex: none;
}

/* Content is a sheet, the chrome is paper. */
.content { min-width: 0; padding: var(--s4) var(--s5) var(--s6); }
.content--wide { max-width: none; }

/* ══ Page head — one line, not five ═════════════════════════════════════
   The old head spent 86-231px before any data: an h1, then two or three
   paragraphs of grey prose, then a tile row. The prose moves into a single
   muted line beside the title, and the tiles become a strip. */
.page-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); margin-bottom: var(--s3); flex-wrap: wrap; }
.page-head h1 { margin: 0; font-size: var(--t-lead); font-weight: var(--w-semi); letter-spacing: var(--tr-0); }
/* 120ch, not 92: My work's one-sentence scope note took two lines at every width
   and pushed the first case 16px further down on a 768px laptop. */
.page-head p { margin: 0; color: var(--ink-3); font-size: var(--t-meta); max-width: 120ch; }
/* A basis rather than max-content: Allocation's title block (two lines of prose)
   was wider than any row as max-content, so it took a line of its own and dropped
   Preview / Run allocation under it at 1366. 60ch still wraps on a phone. */
.page-head > div:first-child { display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap; min-width: 0; flex: 1 1 60ch; }
.page-head .field__hint { margin: 0; }

/* ══ Summary strip — replaces the tile row ═══════════════════════════════
   Five bordered boxes, each 90px tall and mostly empty, become one ruled
   strip 56px tall carrying the same numbers. */
/* The hairlines are the 1px gap showing the rule colour through, not borders on
   the tiles, so a wrapped strip is ruled between its rows as well as its tiles.
   Every row is always full: the strip is the container, and the rules below pick
   how many share a row from its own width and its tile count, rather than
   letting flex-wrap strand the last tile alone across the whole width.
   A grid, and each tile a subgrid of three rows — label, figure, sub-line — so
   the tiles of one row share those rows. As a flex row, a label that took two
   lines at 1280 dropped only its own figure 16px below its neighbours. Each tile
   names its own row (1, 4, 7…) because the split rules are container queries,
   which can style the tiles but never the strip that is the container. */
.stats {
  display: grid; grid-auto-columns: minmax(0, 1fr); gap: 1px; container-type: inline-size;
  background: var(--rule); border: 1px solid var(--rule); border-radius: var(--r);
  margin-bottom: var(--s3); overflow: hidden;
}
.stat {
  grid-row: 1 / span 3; display: grid; grid-template-rows: subgrid; row-gap: 0;
  min-width: 0; padding: var(--s2) var(--s3);
  background: var(--sheet); position: relative;
}
/* One row until a tile would drop under 144px — a nine-character figure
   ("₹13.67 Cr") plus its padding. Then: seven go 4 + 3, then 3 + 2 + 2; six go
   3 + 3; five 3 + 2; four 2 + 2; and on a phone two to a row, where an odd count
   gives the first figure the whole row instead of leaving the last one alone. */
@container (580px <= width < 1015px) {
  .stats:has(> .stat:nth-child(7):last-child) > .stat { grid-column: span 3; }
  .stats:has(> .stat:nth-child(7):last-child) > .stat:nth-child(n+5) { grid-column: span 4; grid-row: 4 / span 3; }
}
@container (435px <= width < 580px) {
  .stats:has(> .stat:nth-child(7):last-child) > .stat { grid-column: span 3; }
  .stats:has(> .stat:nth-child(7):last-child) > .stat:nth-child(-n+3) { grid-column: span 2; }
  .stats:has(> .stat:nth-child(7):last-child) > .stat:nth-child(n+4) { grid-row: 4 / span 3; }
  .stats:has(> .stat:nth-child(7):last-child) > .stat:nth-child(n+6) { grid-row: 7 / span 3; }
  .stats:has(> .stat:nth-child(4):last-child) > .stat:nth-child(n+3) { grid-row: 4 / span 3; }
}
@container (435px <= width < 870px) {
  .stats:has(> .stat:nth-child(6):last-child) > .stat:nth-child(n+4) { grid-row: 4 / span 3; }
}
@container (435px <= width < 725px) {
  .stats:has(> .stat:nth-child(5):last-child) > .stat { grid-column: span 3; }
  .stats:has(> .stat:nth-child(5):last-child) > .stat:nth-child(-n+3) { grid-column: span 2; }
  .stats:has(> .stat:nth-child(5):last-child) > .stat:nth-child(n+4) { grid-row: 4 / span 3; }
}
@container (width < 435px) {
  .stats > .stat:nth-child(n+3) { grid-row: 4 / span 3; }
  .stats > .stat:nth-child(n+5) { grid-row: 7 / span 3; }
  .stats > .stat:nth-child(n+7) { grid-row: 10 / span 3; }
  .stats > .stat:first-child:nth-last-child(odd) { grid-column: span 2; }
  .stats:has(> .stat:first-child:nth-last-child(odd)) > .stat:nth-child(n+2) { grid-row: 4 / span 3; }
  .stats:has(> .stat:first-child:nth-last-child(odd)) > .stat:nth-child(n+4) { grid-row: 7 / span 3; }
  .stats:has(> .stat:first-child:nth-last-child(odd)) > .stat:nth-child(n+6) { grid-row: 10 / span 3; }
}
/* allocation.js puts its error notice where the tiles would be. */
.stats > .notice { grid-row: 1 / span 3; margin: 0; border: 0; border-radius: 0; }
.stat__n {
  font-family: var(--mono); font-size: var(--t-kpi); font-weight: var(--w-semi);
  letter-spacing: var(--tr-big); line-height: 1.15; color: var(--ink);
  font-variant-numeric: tabular-nums lining-nums slashed-zero;
}
.stat__l {
  font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: var(--tr-cap);
  text-transform: uppercase; color: var(--ink-3); margin-bottom: var(--s1);
}
.stat__s { font-size: var(--t-meta); color: var(--ink-2); margin-top: 1px; }
.stat::before { display: none; }
.stat--npa  .stat__n { color: var(--warn); }
.stat--rrwd .stat__n { color: var(--bad); }
.stat--good .stat__n { color: var(--good); }
/* Nothing to report is not an alarm: a red 0 under Behind reads as trouble. The
   renderer adds is-zero when the figure is 0 or ₹0; it outranks every tone above. */
.stats .stat__n.is-zero, .is-zero { color: var(--ink-2); }
/* The headline figure of the row — Today's entries on /logs, Cases held on
 * /profile, the first tile on /allocation. All three markups asked for it and
 * there was no rule, so the figure meant to lead the row read exactly like the
 * ones beside it. Found by npm run check:css, which is why that gate exists:
 * a class with no rule behind it changes nothing and says nothing. */
.stat--accent .stat__n { color: var(--accent-ink); }

/* ══ Controls ═══════════════════════════════════════════════════════════ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  height: var(--ctl); padding: 0 var(--s3); font: var(--w-med) var(--t-body)/1 var(--font);
  cursor: pointer; border-radius: var(--r); border: 1px solid var(--rule-2);
  background: var(--sheet); color: var(--ink); white-space: nowrap; text-decoration: none;
  transition: background var(--ease), border-color var(--ease), color var(--ease);
}
.btn:hover:not(:disabled) { background: var(--band); border-color: var(--ink-4); }
.btn--primary { background: var(--chrome); border-color: var(--chrome); color: var(--on-chrome); width: auto; }
.btn--primary:hover:not(:disabled) { background: var(--chrome-2); border-color: var(--chrome-2); }
.btn--ghost { background: var(--sheet); }
.btn--sm { height: 24px; padding: 0 var(--s2); font-size: var(--t-meta); }
.btn--danger { color: var(--bad); border-color: var(--bad-r); background: var(--sheet); }
.btn--danger:hover { background: var(--bad-w); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
/* An <a> cannot be :disabled, and an <a> with no href is still perfectly
   clickable-looking — it just silently does nothing. The Download button on
   Collection ships without an href on purpose (its query string is only known
   after the first load), so it needs to LOOK unavailable for that moment
   instead of looking ready and ignoring the click. */
.btn.is-disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }

.field { margin-bottom: var(--s3); }
.field label { display: block; font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--ink-3); margin-bottom: var(--s1); }
.field input, .field select, .field textarea {
  width: 100%; height: var(--ctl); padding: 0 var(--s2); font: var(--w-reg) var(--t-body)/1 var(--font);
  color: var(--ink); background: var(--sheet); border: 1px solid var(--rule-2); border-radius: var(--r);
}
.field input[type="file"] { height: auto; padding: var(--s2); font-size: var(--t-meta); }
/* A typed amount, date or identifier is a quantity like any other. The shorthand
   above is (0,1,1) and later than the face rule's input[type="number"], so it won. */
.field input:is([type="number"], [type="date"], .mono) {
  font-family: var(--mono); font-variant-numeric: tabular-nums lining-nums slashed-zero;
  font-feature-settings: var(--figs);
}
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--focus); outline: none; box-shadow: 0 0 0 2px var(--focus-glow); }
/* Every placeholder, one colour: form fields were --ink-4 (2.4:1) while the
   filter bars fell back to the browser's grey, two tones on one screen. */
::placeholder { color: var(--ink-3); opacity: 1; }
.field__hint { font-size: var(--t-meta); color: var(--ink-3); margin-top: var(--s1); }

/* One row, always. The old filter bar wrapped to two lines and cost 100px. */
.filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3); margin-bottom: var(--s3);
  background: var(--sheet); border: 1px solid var(--rule); border-radius: var(--r);
}
.filters select, .filters input {
  height: var(--ctl); padding: 0 var(--s2); font: var(--w-reg) var(--t-meta)/1 var(--font);
  color: var(--ink); background: var(--sheet); border: 1px solid var(--rule-2); border-radius: var(--r);
}
/* The container wraps, so an item goes to a second line before it ever shrinks:
   118–190px selects put Search and Clear under Dispositions' bar at 1366. Nine
   controls (Cases) still take two lines below 1440. */
.filters select { min-width: 0; max-width: 160px; flex: 0 1 auto; }
/* The desk filter on Cases is the one whose option carries the figure it is
   chosen by — "NPA · 90–364 days (4,886)". A closed <select> cannot ellipsise or
   carry a title, so at the shared cap the count was simply not there. 230px is
   the .act-row select's cap; only this control gets it, because widening all nine
   is what put Search and Clear on a second line.

   IT WAS ON THE WRONG CONTROL until 22-09-2026: the cap was written for #fBucket,
   whose options are "Bucket 1 (754)" and have always fitted, while #fTeam — the
   one the comment quotes — kept the 160px cap and rendered "NPA · 90–364 days (4"
   with the count cut off mid-number. */
.filters select#fTeam { max-width: 230px; }
.filters input[type=search] { min-width: 150px; flex: 1 1 200px; max-width: 300px; }
.filters select:focus, .filters input:focus { border-color: var(--focus); outline: none; box-shadow: 0 0 0 2px var(--focus-glow); }
.filters .spacer { flex: 1; }
/* A toolbar is one height. A 24px button beside a 30px select read as a second
   kind of control and, top-aligned, stopped 6px short of it. A table row's own
   buttons (the roster's Edit/Reset) keep the compact size so the row does not grow. */
.filters .btn, :not(td) > .act-row .btn { height: var(--ctl); }
/* The month stepper on Performance and Incentive: square buttons either side of
   the month select. The ‹ › are sized to match the select's own arrow; at body
   size they were a 5px mark in a 30px box. */
.mstep { display: flex; gap: var(--s1); align-items: center; }
.mstep .btn { width: var(--ctl); height: var(--ctl); padding: 0; font-size: var(--t-lead); }

/* ══ Sheet — what used to be a card ═════════════════════════════════════ */
.card, .panel {
  background: var(--sheet); border: 1px solid var(--rule); border-radius: var(--r);
  overflow: hidden; box-shadow: none;
}
.card__head, .panel__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  padding: var(--s2) var(--s3); border-bottom: 1px solid var(--rule-2); background: var(--sheet);
  flex-wrap: wrap; row-gap: var(--s1);
}
/* When the sub-line does not fit beside the title it moves under it. Without the
   wrap, flex squeezed the title instead: "CASES BY / STAGE" at 820. */
.card__head h2, .panel__head h2 {
  flex: none; white-space: nowrap;
  margin: 0; font-size: var(--t-cap); font-weight: var(--w-semi);
  letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--ink-2);
}
.card__head .sub, .panel__head .sub { font-size: var(--t-meta); color: var(--ink-3); }
.panel__body { padding: var(--s3); }
/* A list is ruled onto its card edge to edge, as the Cases table is. The head's
   rule is the list's top edge and the pager's its bottom, so the queue and its
   last row must not draw a second line beside either; a filter bar becomes a
   ruled band under the head rather than a box inside the box. */
.card > .queue { border-top: 0; }
.card > .queue > :last-child { border-bottom: 0; }
.card > .filters { margin: 0; border-width: 0 0 1px; border-radius: 0; }
/* Inside a card the note is the card's last band, ruled off by its own
   border-top — the base margin left a blank strip between the last row and that
   rule, so the rule read as floating rather than as the table's bottom edge. */
.card > .note { padding: var(--s2) var(--s3); margin-top: 0; }

/* Panels must never be stretched to the height of their tallest sibling —
   that is what put four rows in a 340px box and read as "empty". */
.grid-2, .ov__cols { align-items: start; }
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: var(--s3); margin-bottom: var(--s3); }
/* Side by side only while each card holds its 480px table (Allocation). At 340px
   the Workload and Recent runs figures were cut mid-number at 820. A modifier,
   because .grid-2 also lays out the 460px EMI modal, Profile and Logs. */
.grid-2--tables { grid-template-columns: repeat(auto-fit, minmax(min(100%, 500px), 1fr)); }

/* ══ Tables — 30px rows, ruled, aligned ═════════════════════════════════ */
table { width: 100%; border-collapse: collapse; }
th {
  height: var(--row-head); padding: 0 var(--s3); text-align: left;
  font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: var(--tr-cap);
  text-transform: uppercase; color: var(--ink-3);
  border-bottom: 1px solid var(--rule-2); background: var(--sheet);
  position: sticky; top: 0; z-index: 1; white-space: nowrap; line-height: 1.15;
}
td { height: var(--row); padding: 0 var(--s3); border-bottom: 1px solid var(--rule); vertical-align: middle; font-size: var(--t-body); }
tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--band); }
.num { text-align: right; white-space: nowrap; }
/* The figure stays on one line; the note under it may not. "12-09-2026 · Bank
   transfer" and "Rs 10.62 L auto-debit" inherited nowrap from .num and, with no
   clip on the cell, were painted across the next column. */
td.num .cell-note { white-space: normal; }

/* Column groups. Eight ruled rows and no vertical structure left the eye with
   nothing to track a column by, so adjacent right-aligned figures read as one
   block of numbers. A hairline at each group boundary — identity | money |
   status — is enough; a full grid would be louder than the data. */
th.grp, td.grp { border-left: 1px solid var(--rule); }
th.grp { border-left-color: var(--rule-2); }
/* The serial is a position, not a quantity — it should be findable but never
   compete with the money for attention. */
.sno { color: var(--ink-3); font-size: var(--t-meta); }
.money { white-space: nowrap; font-weight: var(--w-med); }
.money--good { color: var(--good); }
.money--nil  { color: var(--ink-3); }
.t-name { font-weight: var(--w-med); }
.t-mail { font-size: var(--t-meta); color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* An address offers no break opportunity, so unclipped it ran under the Result
   pill on Sign-ins and into the next column on the roster. The full address is
   in the title attribute. */
.t-actions { text-align: right; white-space: nowrap; }
.t-actions .btn { margin-right: var(--s1); }
.t-actions .btn:last-child { margin-right: 0; }
.act-row { display: flex; align-items: center; gap: var(--s1); justify-content: flex-end; }
/* My work's head: picker, Moved to RRWD and the profile link ran 471px on a phone. */
.act-row--wrap { flex-wrap: wrap; }
.row-off td { opacity: .5; }
.muted { color: var(--ink-3); }
.clip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* text-overflow only ever applies to the box that overflows. Where the cell clips
   but the words sit in a block child — the log's "EMI — instalment arrangement"
   under its chip — the child overflowed the cell and the glyphs were sheared at the
   cell edge with no ellipsis to say so. The child inherits the nowrap. */
.clip > div { overflow: hidden; text-overflow: ellipsis; }
.empty { padding: var(--s6) var(--s3); text-align: center; color: var(--ink-3); font-size: var(--t-meta); }
.scroll-x { overflow-x: auto; }
.pager {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3); border-top: 1px solid var(--rule); font-size: var(--t-meta);
}

/* A two-line cell inside a 30px row: the name carries the row, the
   identifier sits beside it in mono rather than beneath it. */
.cell-stack { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--s2); row-gap: 0; min-width: 0; }
/* Wraps rather than squeezes. The loan number is nowrap and never gives ground,
   so on one line it took whatever the cell had and the customer name was crushed
   to its first letter — "V.", "S.", "P" down the whole Dispositions table at
   1366x768, with no tooltip to recover it. Wrapping puts the loan number under
   the name the moment both do not fit, and the name gets the full width. */
.cell-lead  { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; }
.cell-title { font-weight: var(--w-med); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-sub   { font-size: var(--t-meta); color: var(--ink-3); white-space: nowrap; }
.cell-note  { font-size: var(--t-meta); color: var(--ink-3); }
.ageing     { display: flex; align-items: baseline; gap: var(--s2); }
.ageing__n  { font-family: var(--mono); font-weight: var(--w-semi); text-align: right; }
.due--hot   { color: var(--bad); }
.due--warm  { color: var(--warn); }
.case-row   { cursor: pointer; }
.disp-none  { color: var(--ink-3); font-size: var(--t-meta); }

/* ══ Marks — pills, chips, flags ════════════════════════════════════════
   One shape, one size, four state tiers. There were nine badge components. */
.pill, .flag, .ptp-tag, .prod, .chip-prod, .person, .file__kind,
.pillr, .dchip, .srcpill {
  display: inline-flex; align-items: center; height: 18px; padding: 0 var(--s2);
  border-radius: var(--r); border: 1px solid var(--rule-2); background: var(--sheet);
  font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: .02em;
  color: var(--ink-2); white-space: nowrap;
}
.chip-prod, .prod { font-family: var(--mono); letter-spacing: 0; }
/* An arrangement's status on /emi. The word arrives lowercase from the API. */
.pillr { letter-spacing: var(--tr-cap); text-transform: uppercase; }
.pillr--active    { background: var(--info-w); color: var(--info);  border-color: var(--info-r); }
.pillr--completed { background: var(--good-w); color: var(--good);  border-color: var(--good-r); }
.pillr--broken    { background: var(--bad-w);  color: var(--bad);   border-color: var(--bad-r); }
.pillr--cancelled { background: var(--band);   color: var(--ink-3); }
.pill--role-super_admin { background: var(--info-w); color: var(--info); border-color: var(--info-r); }
.pill--role-manager     { background: var(--info-w); color: var(--info); border-color: var(--info-r); }
.pill--role-agent       { background: var(--band); color: var(--ink-2); border-color: var(--rule-2); }
/* This cycle's work. Deliberately neutral: the coloured flags on a row mean
   what has happened TO the case, and "it arrived this month" is not that. */
.flag--new              { background: var(--band); color: var(--ink-2); border-color: var(--rule-2); }
.pill--NPA,  .ptp-tag--due, .flag--cb       { background: var(--warn-w); color: var(--warn); border-color: var(--warn-r); }
.pill--RRWD, .flag--due, .flag--legal, .ptp-tag--broken  { background: var(--bad-w);  color: var(--bad);  border-color: var(--bad-r); }
.pill--BOTH, .flag--done, .flag--closed, .ptp-tag--paid  { background: var(--good-w); color: var(--good); border-color: var(--good-r); }
.flag--working, .flag--ptp, .flag--emi, .flag--agent, .ptp-tag--pending { background: var(--info-w); color: var(--info); border-color: var(--info-r); }
.pill--off { background: var(--bad-w); color: var(--bad); border-color: var(--bad-r); }
.person--none { background: var(--band); color: var(--ink-3); font-style: italic; }
.prods, .people { display: flex; gap: var(--s1); flex-wrap: wrap; }
.locked-tag, .hist__locked { font-size: var(--t-meta); color: var(--ink-3); }

/* ══ Notices ════════════════════════════════════════════════════════════ */
.alert, .notice, .ov__note {
  display: flex; gap: var(--s2); padding: var(--s2) var(--s3); border-radius: var(--r);
  font-size: var(--t-meta); line-height: 1.5; margin-bottom: var(--s3);
  border: 1px solid var(--rule); background: var(--sheet); color: var(--ink-2);
}
.alert--bad  { background: var(--bad-w);  color: var(--bad);  border-color: var(--bad-r); }
.alert--good { background: var(--good-w); color: var(--good); border-color: var(--good-r); }
/* A note inside an alert takes the alert's own colour. --ink-3 is tuned for the
   paper behind a table cell, and on a tinted alert wash it fell to 4.22:1 — the
   sentence under "Closed." in the case drawer. Size and the bold lead-in carry the
   hierarchy instead of a lighter ink. */
.alert :is(.cell-note, .muted, .disp-none) { color: inherit; }
/* The arithmetic under the attribution sentence on /collection. Stacked under it
   rather than beside it — .alert is a flex ROW, so a second sibling <div> renders
   as a second COLUMN at wide widths, which is how "gaps around today" happened. */
.alert__bridge { margin-top: var(--s2); }

/* ══ A receipt whose money is not the holder's ══════════════════════════
   On /collection every row is a case somebody holds, but not every row is that
   person's money: a case handed over mid-month brings the previous holder's
   collections with it. Those rows are the reason the page was misread, so they
   are marked — a tint on the row, and the name of whoever it actually counts
   toward set apart from the ordinary ones.
   Tinted rather than badged: there can be a screenful of them, and a screenful
   of badges is noise nobody reads by the third row. */
.pay--moved > td { background: var(--warn-w); }
.pay-moved { color: var(--warn); font-weight: 600; }
.notice--warn, .ov__note--warn { background: var(--warn-w); color: var(--warn); border-color: var(--warn-r); }
.ov__dot { width: 6px; height: 6px; border-radius: 99px; background: var(--good-mark); margin-top: 6px; flex: none; }
.ov__note--warn .ov__dot { background: var(--warn-mark); }
.ov__notes { display: grid; gap: var(--s2); }

/* ══ Bars — one denominator per page ════════════════════════════════════
   Every ledger block used to normalise to its own maximum, so ₹151.84 Cr,
   ₹105.04 Cr and ₹198.05 Cr rendered as three identical full-width bars
   side by side. Comparable bars share a denominator. */
/* max-content: a fixed 96px value column cut '1,728 · ₹5.04 Cr' to '₹5.04 C' on
   /profile. The bar is the part that can give. .barrow--wide keeps its own. */
.barrow { display: grid; grid-template-columns: 108px minmax(40px, 1fr) max-content; gap: var(--s3); align-items: center; height: var(--row); }
.barrow--wide { grid-template-columns: 152px 1fr 64px; }
.barrow__k { font-size: var(--t-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.barrow__track { height: 4px; background: var(--band); box-shadow: inset 0 0 0 1px var(--rule); }
.barrow__fill { height: 100%; background: var(--info-mark); }
.barrow__fill--warn { background: var(--warn-mark); }
.barrow__fill--bad  { background: var(--bad-mark); }
.barrow__fill--good { background: var(--good-mark); }
.barrow__v { font-family: var(--mono); font-size: var(--t-meta); text-align: right; color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* 56px because logs.js and profile.js scale the peak to 56: at 44 the busiest day
   rose through the panel head. The bars were the wash colour (1.19:1), so only
   today could be seen; a day with nothing logged is a rule-grey stub, so an empty
   fortnight does not read as a steady blue line. */
.spark { display: flex; align-items: flex-end; gap: 2px; height: 56px; }
.spark__b { flex: 1; background: var(--info-mark); min-height: 2px; }
.spark__b.is-today { background: var(--info); }
.spark__b.is-zero { background: var(--rule-2); }
.spark__x { display: flex; gap: 2px; margin-top: var(--s1); }
.spark__x span { flex: 1; text-align: center; font-size: var(--t-cap); color: var(--ink-3); font-family: var(--mono); }

/* ══ Queue — a table, not a stack of cards ══════════════════════════════
   The card was an 87px pitch carrying ten data atoms, so eight of 1,772
   cases fitted on screen — and because the facts were a flex-wrap run, no
   two rows shared a column and the eye had nothing to run down. */
.queue { display: flex; flex-direction: column; border-top: 1px solid var(--rule); }
/* My work's list measures itself, not the window: the sidebar comes and goes at
   1100px, so the list is narrower at a 1101px window than at a 1000px one. */
.queue--work { container: work-queue / inline-size; }
.qrow {
  display: grid;
  /* The spare width goes to what happened last, not to the name: at 1920 the name
     track was 597px round a 323px name while the Legal date was cut at 1366. The
     figures are sized to the widest in the book (₹3,50,000 70px, 2138d 39px, ten
     mono digits 72px). The actions are max-content: as `auto` under min-width:0
     they collapsed to 0px and Open was clipped off the row below ~947px. */
  grid-template-columns:
    38px                    /* product            */
    minmax(140px, 300px)    /* customer           */
    152px                   /* loan number — 19 mono characters; 132px cut every one to "ELI70112141555395…" */
    84px                    /* loan amount        */
    48px                    /* days               */
    80px                    /* mobile             */
    minmax(140px, 1fr)      /* what happened last */
    max-content;            /* actions            */
  gap: var(--s3); align-items: center;
  /* 3px rail + 8px: the product chip starts on the card head's 12px inset. */
  height: var(--row); padding: 0 var(--s3) 0 var(--s2);
  border-bottom: 1px solid var(--rule); border-left: 3px solid transparent;
  background: var(--sheet); color: inherit; text-decoration: none;
}
.qrow:hover { background: var(--band); }
.qrow--due     { border-left-color: var(--bad-mark); }
.qrow--working { border-left-color: var(--info-mark); }
.qrow--done    { border-left-color: var(--good-mark); }
/* Legal is not done: the rail agrees with the red Legal flag on the row. */
.qrow--legal   { border-left-color: var(--bad-mark); }
.qrow > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The flags trail the name, so every name starts on the column edge; a long name
   gives way before a flag does. */
.qrow__name { font-weight: var(--w-med); display: flex; align-items: center; gap: var(--s1); }
.qrow__nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.qrow__name .flag { flex: none; }
.qrow__loan { font-family: var(--mono); font-size: var(--t-meta); color: var(--ink-3); }
.qrow__mob  { font-family: var(--mono); font-size: var(--t-meta); color: var(--ink-2); }
.qrow__last { font-size: var(--t-meta); color: var(--ink-2); }
.qrow__last em { font-style: normal; color: var(--ink-3); }
.qrow__acts { display: flex; gap: var(--s1); justify-content: flex-end; overflow: visible; }
.qrow__acts .btn { height: 24px; padding: 0 var(--s2); font-size: var(--t-meta); }
/* The primary action on a row repeats once per row. Filling 26 of them black
   spends the page's whole contrast budget on the most redundant object on it,
   so the row action is quiet and earns its weight only on hover. */
.qrow__acts .btn--go { border-color: var(--ink-4); color: var(--ink); font-weight: var(--w-semi); }
.qrow:hover .btn--go { background: var(--chrome); border-color: var(--chrome); color: var(--on-chrome); }
/* The number is the field the job runs on: make it dialable and obvious. */
.qrow__mob a { color: var(--info); text-decoration: none; }
.qrow__mob a:hover { text-decoration: underline; }
/* Every .qrow cell clips (overflow:hidden above), which cut the dial link's focus
   ring off on three sides. Ten mono digits need ~72px of this track, so there is
   nothing here to clip; an inset ring instead would sit on the first and last digit. */
.qrow > .qrow__mob { overflow: visible; }
/* A 24px target without growing the 30px row. */
.qrow__mob a { display: inline-block; padding-block: var(--s1); }
.qrow .num { font-family: var(--mono); font-size: var(--t-body); }
/* Narrower than one line holds, the row reflows; it never scrolls sideways and
   never hides a field. Two lines, what happened last under the name, until the
   name would drop under ~120px; then three: name and actions, the figures, what
   happened last beside the number to dial. 920 and 720 are the one-line and
   two-line minimums with Put back beside Open. */
@container work-queue (width < 920px) {
  .qrow {
    grid-template-columns: 38px minmax(0, 1fr) 144px 84px 48px 80px max-content;
    height: auto; min-height: var(--row); padding-block: 2px; row-gap: 0;
  }
  .qrow > :first-child { grid-area: 1 / 1; }
  .qrow__last { grid-area: 2 / 2 / 3 / 7; }
  .qrow__acts { grid-area: 1 / 7 / 3 / 8; }
}
@container work-queue (width < 720px) {
  .qrow { grid-template-columns: 38px minmax(0, 1fr) 76px 44px; column-gap: var(--s2); row-gap: 2px; padding-block: var(--s1); }
  /* The tags wrap under the name rather than squeezing it. With two of them
     beside it (Closed and EMI on a settled case) the name was left about 100px
     to live in and broke mid-word. */
  .qrow__name { grid-area: 1 / 2; flex-wrap: wrap; }
  /* A phone has no hover to read a clipped name by, so a long one takes two lines.
     break-word, NOT anywhere: `anywhere` also lets the browser break to the
     narrowest possible box, and with two tags beside the name (Closed and EMI on a
     settled case) it split Deshmukh across lines as "Deshmuk / h". A name is the
     one string on the row that must not be cut in the middle. */
  .qrow__nm { white-space: normal; overflow-wrap: break-word; min-width: 0; }
  .qrow__acts { grid-area: 1 / 3 / 2 / 5; }
  .qrow__loan { grid-area: 2 / 2; }
  .qrow > .money { grid-area: 2 / 3; }
  .qrow > .num:not(.money) { grid-area: 2 / 4; }
  .qrow__last { grid-area: 3 / 2; }
  .qrow__mob { grid-area: 3 / 3 / 4 / 5; text-align: right; }
  .qrow__acts .btn { height: var(--ctl); }
}

/* ── Moved to RRWD, under the queue ─────────────────────────────────────────
   Cases dealt to this person that have aged past their desk and are held by
   somebody else or nobody (src/moved.js). Read-only and not the day's work, so
   it folds away under the queue with its count on the fold, and never pushes
   the first case down. The fold mark sits at the right so the title stays on
   the same edge as the queue's. */
.card__head--fold { cursor: pointer; list-style: none; }
.card__head--fold::-webkit-details-marker { display: none; }
.card__head--fold:hover { background: var(--band); }
/* The card clips, so the ring is drawn inside the head. That is true of every
   fold in a card, not only this one: the case drawer's "Everything on this case"
   summary fills its card's padding box exactly, so at the global +2px the ring
   was painted entirely outside the clip and a keyboard user saw nothing. */
:is(.card__head--fold, details.card > summary):focus-visible { outline-offset: -2px; }
.card__head--fold .sub::after {
  content: ''; display: inline-block; width: 6px; height: 6px; margin-left: var(--s3);
  border: solid var(--ink-2); border-width: 0 2px 2px 0; transform: translateY(-3px) rotate(45deg);
}
details[open] > .card__head--fold .sub::after { transform: translateY(0) rotate(-135deg); }
/* Folded, the head's rule would lie on the card's own bottom edge. */
details:not([open]) > .card__head--fold { border-bottom: 0; }
/* Allocation's own per-person table is .table--moved; this is one person's cases. */
.table--workmoved { table-layout: fixed; min-width: 860px; }
.table--workmoved th, .table--workmoved td { padding-left: var(--s2); padding-right: var(--s2); }
.table--workmoved :is(th, td):first-child { padding-left: var(--s3); }
.table--workmoved :is(th, td):last-child { padding-right: var(--s3); }
/* The spare width goes to Customer, which is running text. It used to go to the
   last column, and when every case came from one sheet the How-it-left column was
   dropped, so the last column became a right-aligned "₹50,000" holding 290px at
   1366 and 430px at 1536 — the row split visibly into two halves. Money is capped
   whatever the column count; only Customer and How it left may take up slack. */
.table--workmoved th:nth-child(1) { width: 164px; }
.table--workmoved th:nth-child(2) { width: auto; }
.table--workmoved th:nth-child(3) { width: 112px; }
.table--workmoved th:nth-child(4) { width: 132px; }
.table--workmoved th:nth-child(5) { width: 120px; }

/* ══ Tabs ═══════════════════════════════════════════════════════════════ */
/* Wraps rather than scrolls: My work has seven tabs since 14-09-2026, and an
   overflow-x strip would clip the underline each tab draws over this border. */
.tabs { display: flex; flex-wrap: wrap; gap: 0 var(--s5); border-bottom: 1px solid var(--rule-2); margin-bottom: var(--s3); }
/* The allocation-cycle strip.
   It was the same control as the work-state tabs — underlined words, same size,
   directly above them — so two strips read as one strip that had wrapped, and
   which of the seven was selected was a guess. Pills under their own heading: a
   different shape for a different question. */
.tabs--band {
  border-bottom: none; margin-bottom: var(--s3); gap: var(--s2);
  flex-wrap: wrap; align-items: center;
}
/* The pills' own 26px: at .tab's 34px the label made the whole band 8px taller. */
.tabs__label {
  display: flex; align-items: center; height: 26px; padding-right: var(--s2);
  font: var(--w-med) var(--t-cap)/1 var(--font); letter-spacing: var(--tr-cap);
  text-transform: uppercase; color: var(--ink-3);
}
.tab {
  appearance: none; background: none; border: none; border-bottom: 2px solid transparent;
  padding: var(--s2) 0; margin-bottom: -1px; cursor: pointer;
  font: var(--w-med) var(--t-cap)/1 var(--font); letter-spacing: var(--tr-cap); text-transform: uppercase;
  color: var(--ink-3); display: flex; align-items: center; gap: var(--s2); height: 34px;
}
.tab:hover { color: var(--ink); }
.tab.is-on { color: var(--ink); border-bottom-color: var(--ink); }
.tab__n { font-family: var(--mono); font-size: var(--t-meta); font-weight: var(--w-semi); color: var(--ink-3); letter-spacing: 0; }
.tab.is-on .tab__n { color: var(--ink); }
.tab__n--alert { color: var(--bad); }
/* My work's seven work-state tabs. Wrapped, LEGAL CASES went to a line of its own
   and the selected tab's underline floated 33px above the rule. Closer gaps keep
   one row down to a tablet; narrower still, the strip scrolls, and the rule moves
   to an inset shadow so the underline still lands on it. */
@media (max-width: 900px) { .tabs--states { column-gap: var(--s4); } }
@media (max-width: 800px) {
  .tabs--states {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
    border-bottom: 0; box-shadow: inset 0 -1px var(--rule-2);
  }
  .tabs--states .tab { flex: none; margin-bottom: 0; }
  .tabs--states .tab:focus-visible { outline-offset: -2px; }
}

/* The cycle pills, AFTER .tab and scoped one level deeper than it.
 *
 * They were written directly above .tab as a bare `.btab`, which is the same
 * specificity — so .tab won every property the two share and the pills were
 * never drawn: no border, no radius, 34px tall, and .tab.is-on's dark `color`
 * landing on .btab.is-on's dark `background`, which is a selected tab whose
 * label you cannot read. The page looked exactly as it had before the change.
 *
 * Both halves of the fix are deliberate. Coming after .tab settles it today;
 * `.tabs--band` scoping settles it whatever order the file ends up in, which is
 * what stops the next edit re-breaking it silently. check:contrast passed
 * throughout — it reads declared token pairs, not what the cascade resolves to. */
.tabs--band .btab {
  height: 26px; padding: 0 var(--s3); margin-bottom: 0;
  border: 1px solid var(--border); border-radius: 999px;
  color: var(--ink-2);
}
.tabs--band .btab:hover { border-color: var(--ink-4); color: var(--ink); }
.tabs--band .btab.is-on {
  background: var(--chrome); border-color: var(--chrome); color: var(--on-chrome);
  border-bottom-color: var(--chrome);
}
.tabs--band .btab.is-on .tab__n { color: var(--on-chrome); }

/* ══ Drawer and modal — the only things allowed to float ════════════════ */
/* Motion lives with the layer, state lives in one class. `is-open` is put on by
   openLayer() in public/js/shell.js one frame after `hidden` comes off, because
   a browser that is handed both in the same frame coalesces them and there is
   nothing left to animate. closeLayer() takes it off and waits for the panel's
   transitionend before hiding, so the exit is seen and not cut.

   The prefers-reduced-motion rule near the top of this file switches every
   transition and animation off, and closeLayer checks the same media query so it
   stops waiting for a transitionend that will never arrive. Everything below
   therefore has to be correct with no animation at all: only the transform and
   the scrim's opacity are animated away from a usable resting state, and the
   entry keyframes animate FROM a hidden state rather than TO one, so an element
   that never animates is simply visible.*/
.drawer, .modal { position: fixed; inset: 0; z-index: 50; }
.modal { display: grid; place-items: center; padding: var(--s5); z-index: 60; overflow-y: auto; overscroll-behavior: contain; }
/* Same defect as the sidebar: a fixed layer cannot be moved by scrolling the page,
   so a box taller than the window (Add user with its error banner, in a short
   browser window) left Save and Cancel below the edge. The layer scrolls itself;
   margin:auto on the box keeps it centred while it fits and top-aligned, rather
   than cut off at both ends, once it does not. */
.drawer__scrim, .modal__scrim {
  position: absolute; inset: 0; background: var(--scrim);
  opacity: 0; transition: opacity var(--ease-scrim);
}
.drawer.is-open > .drawer__scrim,
.modal.is-open  > .modal__scrim { opacity: 1; }

.drawer__panel {
  position: absolute; top: 0; right: 0; bottom: 0; width: min(600px, 92vw);
  background: var(--sheet); box-shadow: var(--shadow-lg); overflow-y: auto;
  transform: translateX(100%);
  transition: transform var(--ease-layer);
}
.drawer.is-open .drawer__panel { transform: none; }

/* The case arrives as one object, then its contents settle — six steps of 22ms,
   which is under a tenth of a second in total and reads as the panel having
   depth rather than as six things animating. Capped at six: a stagger long
   enough to notice is a stagger that has to be waited for. */
.drawer.is-open .drawer__body > * { animation: layer-rise 240ms cubic-bezier(.22,1,.36,1) both; }
.drawer.is-open .drawer__body > *:nth-child(1) { animation-delay: 40ms; }
.drawer.is-open .drawer__body > *:nth-child(2) { animation-delay: 62ms; }
.drawer.is-open .drawer__body > *:nth-child(3) { animation-delay: 84ms; }
.drawer.is-open .drawer__body > *:nth-child(4) { animation-delay: 106ms; }
.drawer.is-open .drawer__body > *:nth-child(5) { animation-delay: 128ms; }
.drawer.is-open .drawer__body > *:nth-child(n+6) { animation-delay: 150ms; }
@keyframes layer-rise { from { opacity: 0; transform: translateY(6px); } }
.drawer__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3);
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule-2);
  position: sticky; top: 0; background: var(--sheet); z-index: 2;
}
.drawer__head h2 { margin: 0 0 2px; font-size: var(--t-lead); font-weight: var(--w-semi); }
.drawer__body { padding: var(--s3) var(--s4) var(--s6); display: grid; gap: var(--s3); }
/* The grid gap already spaces the sections; the alert's own margin made 24px of 12. */
.drawer__body > .alert { margin-bottom: 0; }
.modal__box {
  margin: auto; position: relative; background: var(--sheet); border: 1px solid var(--rule-2); border-radius: var(--r);
  padding: var(--s5); width: 100%; max-width: 460px; box-shadow: var(--shadow-lg);
  opacity: 0; transform: translateY(10px) scale(.985);
  transition: opacity var(--ease-scrim), transform var(--ease-layer);
}
.modal.is-open .modal__box { opacity: 1; transform: none; }
.modal__box h3 { margin: 0 0 var(--s1); font-size: var(--t-lead); font-weight: var(--w-semi); }
.modal__box > p { margin: 0 0 var(--s4); color: var(--ink-2); font-size: var(--t-meta); }
.modal__actions { display: flex; gap: var(--s2); justify-content: flex-end; margin-top: var(--s4); }

.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--s3) var(--s4); margin: 0; }
.kv > div { min-width: 0; }
.kv dt { font-size: var(--t-cap); color: var(--ink-3); text-transform: uppercase; letter-spacing: var(--tr-cap); font-weight: var(--w-semi); }
.kv dd { margin: 2px 0 0; font-size: var(--t-body); font-weight: var(--w-med); word-break: break-word; }

.hist { display: grid; gap: var(--s2); }
.hist__item { border: 1px solid var(--rule); border-radius: var(--r); padding: var(--s2) var(--s3); }
.hist__top { display: flex; justify-content: space-between; align-items: center; gap: var(--s2); margin-bottom: var(--s1); }
.hist__meta { font-size: var(--t-meta); color: var(--ink-3); }
.hist__text { margin-bottom: var(--s1); }
/* Inside its card the history is ruled rows, like every other list: boxes inside
   a box read as a second set of cards. */
.card .hist { gap: 0; }
.card .hist__item { border: 0; border-top: 1px solid var(--rule); border-radius: 0; padding: var(--s2) 0; }
.card .hist__item:last-child { padding-bottom: 0; }

.files { display: grid; gap: var(--s2); }
.file { display: flex; align-items: center; gap: var(--s3); padding: var(--s2) var(--s3); border: 1px solid var(--rule); border-radius: var(--r); }
.file--gone { opacity: .5; }
.file__thumb { width: 34px; height: 34px; flex: none; border-radius: var(--r); overflow: hidden; background: var(--band); border: 1px solid var(--rule); display: grid; place-items: center; font-size: var(--t-cap); font-weight: var(--w-semi); color: var(--ink-3); font-family: var(--mono); }
.file__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.file__body { min-width: 0; flex: 1; }
.file__name { font-size: var(--t-body); font-weight: var(--w-med); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file__meta { font-size: var(--t-meta); color: var(--ink-3); margin-top: 1px; }
.file__kind--proof { background: var(--good-w); color: var(--good); border-color: var(--good-r); }

.proof { border: 1px solid var(--rule-2); border-radius: var(--r); padding: var(--s3); background: var(--band); margin-bottom: var(--s3); }
.proof--required { border-color: var(--warn-r); background: var(--warn-w); }
.proof--ready { border-color: var(--good-r); background: var(--good-w); }
.proof__title { font-size: var(--t-body); font-weight: var(--w-semi); }
.proof__hint { font-size: var(--t-meta); color: var(--ink-2); margin-top: 2px; }
.proof__pick { margin-top: var(--s2); }
.proof__preview { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s2); }
.proof__preview img { max-height: 52px; border: 1px solid var(--rule); }

/* The old markup used display:none, which removes the control from the
   accessibility tree entirely — unreachable by keyboard. */
.checks { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--s1); }
.check { display: flex; align-items: center; justify-content: center; height: var(--ctl); border: 1px solid var(--rule-2); border-radius: var(--r); font-size: var(--t-meta); cursor: pointer; position: relative; }
.check input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.check--on { background: var(--info-w); border-color: var(--info-r); color: var(--info); font-weight: var(--w-semi); }
/* The toggles sit inside a .field, whose `.field label` (0,1,1) outranks .check: they
   came out as uppercase block labels with the code in the top-left corner, and a
   picked product kept the grey instead of turning blue. Product codes are
   identifiers, so they are set in mono as they are on every roster row. */
.checks .check {
  display: flex; margin-bottom: 0; color: var(--ink); font-family: var(--mono);
  font-size: var(--t-meta); font-weight: var(--w-reg); letter-spacing: 0; text-transform: none;
}
.checks .check--on { color: var(--info); font-weight: var(--w-semi); }
.check:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ══ Profile ════════════════════════════════════════════════════════════ */
.pro-head { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s3); }
.pro-head .avatar { width: 40px; height: 40px; font-size: var(--t-body); background: var(--chrome); color: var(--on-chrome); }
.pro-head h1 { margin: 0; font-size: var(--t-lead); font-weight: var(--w-semi); }
.pro-meta { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; font-size: var(--t-meta); color: var(--ink-2); }
/* Columns, not a grid: six panels from 91 to 357px tall made grid rows as tall as
   their tallest panel, leaving 206-266px of paper under the short ones. They now
   pack down each column — 3 at 1280-1536, 4 at 1920 (an agent's panels measured
   636 → 464px tall at 1366, 580 → 390px at 1920). 330px, not 300: a refused
   sign-in row (stamp, address, "bad password") needs 333px and broke over two
   lines in the 314px columns 300 gave at 1536. The <dl> lists kept the browser's
   1em margin, so Details and Their record started 13px below their neighbours. */
.pro-grid { columns: 330px 5; column-gap: var(--s3); margin-bottom: var(--s3); }
.pro-grid > .panel { break-inside: avoid; margin-bottom: var(--s3); }
.pro-list { display: grid; margin: 0; }
.pro-row { display: flex; justify-content: space-between; gap: var(--s3); font-size: var(--t-body); min-height: var(--row); padding-block: 3px; align-items: center; border-bottom: 1px solid var(--rule); }
/* min-height, not height: "4 — 0 kept, 0 broken, 4 still to fall due" wraps, and a
   fixed 30px row printed its second line on top of the next row's first. */
.pro-row:last-child { border-bottom: none; }
.pro-row dt { color: var(--ink-3); }
.pro-row dd { margin: 0; font-weight: var(--w-med); text-align: right; }

/* ══ Sign in ════════════════════════════════════════════════════════════ */
.auth { display: grid; grid-template-columns: 1.05fr 1fr; min-height: 100vh; }
.auth__brand { background: var(--chrome); color: var(--on-chrome); padding: var(--s6); display: flex; flex-direction: column; justify-content: space-between; position: relative; }
.auth__brand::after { display: none; }
.brand-mark { display: flex; align-items: center; gap: var(--s3); }
.brand-mark__glyph { width: 34px; height: 34px; border-radius: var(--r); background: var(--chrome-fill); display: grid; place-items: center; font-weight: var(--w-bold); font-size: var(--t-body); }
.brand-mark__name { font-weight: var(--w-semi); font-size: var(--t-body); }
.brand-mark__sub { font-size: var(--t-meta); color: var(--on-chrome-3); margin-top: 1px; }
.auth__pitch { max-width: 460px; }
.auth__pitch h1 { font-size: var(--t-hero); line-height: 1.08; margin: 0 0 var(--s3); font-weight: var(--w-semi); letter-spacing: var(--tr-big); }
.auth__pitch p { margin: 0; color: var(--on-chrome-2); font-size: var(--t-fig); line-height: 1.6; }
.auth__facts { display: flex; gap: var(--s6); }
.fact__n { font-family: var(--mono); font-size: var(--t-kpi); font-weight: var(--w-semi); }
.fact__l { font-size: var(--t-cap); text-transform: uppercase; letter-spacing: var(--tr-cap); color: var(--on-chrome-3); margin-top: 2px; }
.auth__panel { display: grid; place-items: center; padding: var(--s6) var(--s5); }
.auth__card { width: 100%; max-width: 340px; }
.auth__card h2 { margin: 0 0 var(--s1); font-size: var(--t-lead); font-weight: var(--w-semi); }
.auth__card > p { margin: 0 0 var(--s5); color: var(--ink-3); font-size: var(--t-meta); }
.auth__card .btn--primary { width: 100%; height: var(--ctl-lg); }

/* ── The theme picker, on the chrome slab in both themes ─────────────────── */
.themepick {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px;
  padding: 2px; border-radius: var(--r);
  background: var(--chrome-btn); border: 1px solid var(--chrome-btn-b);
}
.themepick button {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--s1) 0; border: none; border-radius: 1px; cursor: pointer;
  background: transparent; color: var(--on-chrome-2);
  font-family: inherit; font-size: var(--t-cap); font-weight: var(--w-med);
  letter-spacing: var(--tr-cap); text-transform: uppercase;
}
.themepick button svg {
  width: 14px; height: 14px; fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.themepick button:hover { background: var(--chrome-hover); color: var(--on-chrome); }
.themepick button.is-on { background: var(--chrome-on); color: var(--on-chrome); }
.themepick button:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

/* ══ Dark ═══════════════════════════════════════════════════════════════════
   One block, not two. public/js/theme.js resolves "follow the machine" and
   always stamps data-theme, so there is no prefers-color-scheme copy of these
   forty tokens to drift out of step with this one.

   Not an inversion. Dark grounds change how colour behaves: saturated hues
   vibrate, pure black makes 13px text bloom over an eight-hour shift, and a
   light theme's shadows stop separating anything. So the surfaces are a real
   ladder — the sheet is LIGHTER than the page, the way light mode has the sheet
   lighter than the paper — and every state hue is re-stepped rather than
   flipped. Ratios are asserted by scripts/check-contrast.js, not by eye.

   The chrome tokens are deliberately absent: the sidebar and the primary button
   are a dark slab in BOTH themes, so they and their white-alpha overlays carry
   over untouched. Only --chrome itself drops, so the sidebar sits below the
   page rather than looming out of it. */
:root[data-theme="dark"] {
  --ink:    #EAECEF;   /* names, figures, headings                            */
  --ink-2:  #B4B9C2;   /* secondary prose, units                              */
  --ink-3:  #949AA4;   /* the AA floor — column heads, timestamps, hints      */
  --ink-4:  #666C77;   /* NON-TEXT ONLY: ticks, disabled glyphs               */
  --rule:   #2C3037;   /* the hairline that does all the separating           */
  --rule-2: #454B55;   /* under column heads, above totals                    */
  --paper:  #16181C;   /* page ground                                         */
  --sheet:  #1D2026;   /* the sheet the data is ruled onto — lighter, raised  */
  --band:   #23272E;   /* row hover and counting band                         */

  --bad:    #FF9A90;  --bad-mark:  #E0554A;  --bad-w:  #3A2320;  --bad-r:  #5E3A34;
  --warn:   #F0BE5C;  --warn-mark: #C88B23;  --warn-w: #382C16;  --warn-r: #5A4622;
  --good:   #5FD3A0;  --good-mark: #16A171;  --good-w: #14302A;  --good-r: #245047;
  --info:   #93B8F5;  --info-mark: #4479CC;  --info-w: #1C2740;  --info-r: #33456B;
  /* The same six hues stepped for the dark sheet, validated against #1D2026 as a
     set (worst adjacent CVD ΔE 8.4, normal-vision 19.3, all >= 3:1). */
  --series-1: #3987E5; --series-2: #D95926; --series-3: #199E70;
  --series-4: #C98500; --series-5: #D55181; --series-6: #9085E9;

  --focus:  #7FA9FF;
  --focus-glow: rgba(127,169,255,.22);

  /* The sidebar recedes below the page instead of looming out of it. */
  --chrome:   #101216;
  --chrome-2: #191C21;

  --accent-ink: #B9CDF3;
  --navy: #101216; --navy-2: #191C21;

  --shadow-lg: -1px 0 0 var(--rule-2), -28px 0 56px -20px rgba(0,0,0,.6);
  --scrim: rgba(6,8,11,.62);
}
/* A slab that recedes is right for the sidebar and wrong for anything meant to
   stand out of the page: --chrome #101216 on --paper is 1.05:1, so the primary
   button and the selected cycle pill read as bare words. In dark they invert to
   the ink slab (15:1 for the label). Light is untouched: there --ink and
   --chrome are the same colour. */
:root[data-theme="dark"] .btn--primary,
:root[data-theme="dark"] .tabs--band .btab.is-on,
:root[data-theme="dark"] .qrow:hover .btn--go { background: var(--ink); border-color: var(--ink); color: var(--paper); }
:root[data-theme="dark"] .btn--primary:hover:not(:disabled) { background: var(--ink-2); border-color: var(--ink-2); }
:root[data-theme="dark"] .tabs--band .btab.is-on .tab__n { color: var(--paper); }

/* ══ Table variants — one geometry, per-page column widths ══════════════ */
.table--cases, .table--disp, .table--logs, .table--logins,
.table--roster, .table--branch, .table--rules, .table--load, .table--runs,
.table--stage, .table--people, .table--log, .table--months, .table--whose,
.table--receipts {
  table-layout: fixed; width: 100%;
}
.table--cases { min-width: 1000px; }
/* 1320px left Assigned half-cut and Disposition off the right edge at 1366x768;
   1120px still scrolled 17px behind a Windows scrollbar (1103) and hid Disposition
   at 1280. 1000px fits 1280 even with that scrollbar (1017). The columns whose
   content is a fixed size — the serial, the product chip, a 19-character loan
   number, "2138d RRWD" — get exactly that in px, so a wider screen gives its extra
   room to Customer name, the one auto column, instead of to them. */
.table--cases th, .table--cases td { padding-left: var(--s2); padding-right: var(--s2); }
.table--cases .col-sno { width: 56px; } .table--cases .col-ent { width: 64px; }
.table--cases .col-loan { width: 158px; } .table--cases .col-ageing { width: 118px; }
.table--cases .col-name { width: auto; } .table--cases .col-branch { width: 8%; }
.table--cases .col-money { width: 9.5%; } .table--cases .col-owner { width: 9.5%; }
.table--cases .col-disp { width: 11.5%; }
.table--disp  { min-width: 1030px; }
/* Edit is the only way to correct an entry. Below ~1280px the table is wider than
   its box, and the column was off the right edge with the sideways scrollbar a
   screen-height further down, so it is pinned to the right of the scroller. */
.table--disp th.t-actions, .table--disp td.t-actions {
  position: sticky; right: 0; z-index: 1; background: var(--sheet); box-shadow: inset 1px 0 0 var(--rule);
}
.table--disp tbody tr:hover td.t-actions { background: var(--band); }
/* The PTP date and its tag on one line. Stacked they made every row 43px; a mono
   date wraps at its hyphens below 16%. Remarks already clip with a title. */
.table--disp .col-ptp { width: 16%; } .table--disp .col-remarks { width: 13%; }
.table--disp .ptp-when { white-space: nowrap; }
/* Where the table is at its 1030px floor (1280 and below) "due today" cannot sit
   beside the date; the tag drops under it rather than past the cell edge. */
.table--disp .col-ptp .cell-lead { flex-wrap: wrap; row-gap: 2px; }
.table--disp th, .table--disp td { padding-left: var(--s2); padding-right: var(--s2); }
.table--disp th:first-child, .table--disp td:first-child { padding-left: var(--s3); }
.table--disp th:last-child, .table--disp td:last-child { padding-right: var(--s3); }

/* Column heads that stay in view down a long list: Cases, Dispositions and the
   Collection receipts only. A sticky head sticks inside its nearest scroll
   container, and .scroll-x (overflow-x:auto) is one on both axes that never scrolls
   vertically, so th{position:sticky} did nothing while the page scrolled. Capping
   the wrapper's height makes it the thing that scrolls; its sideways scrollbar is
   then a screen away at most, not fifty rows down. No overscroll containment: at
   the end of the list the wheel carries on to the page. */
.scroll-x--capped { max-height: calc(100vh - 2 * var(--s6)); overflow: auto; }
/* A collapsed border belongs to the table, not the cell, so it stays behind when
   the head sticks; the rule under the head is drawn by the cell instead. */
.scroll-x--capped th { z-index: 2; box-shadow: inset 0 -1px 0 var(--rule-2); }
/* The pinned Edit column's body cells come later in the document at the same
   z-index, so they painted over its head and left a blank corner. */
.scroll-x--capped .table--disp th.t-actions { z-index: 3; box-shadow: inset 1px 0 0 var(--rule), inset 0 -1px 0 var(--rule-2); }
/* 1160px was 40px wider than the box at 1366 and cut Where from; and a 12% When
   column broke every older stamp over two lines, so 38 of 60 rows were 39px. */
.table--logs  { min-width: 1020px; }
.table--logs th, .table--logs td { padding-left: var(--s2); padding-right: var(--s2); }
/* The four tables under Imports & runs and By person scroll inside their cards on a
   phone anyway; at a 480px floor with fixed shares a stamp broke over three lines,
   so below 820 they size to their content and every row stays one line. */
@media (max-width: 820px) {
  .table--branch { table-layout: auto; }
  .table--branch td { white-space: nowrap; }
}
.table--logins { min-width: 860px; }
.table--roster { min-width: 940px; }
.table--rules { min-width: 720px; }
.table--load, .table--runs, .table--branch { min-width: 480px; }
/* Allocation · Moved to RRWD (src/moved.js): one row per person, and in the drawer
   one row per case. The case cell carries the name over its loan number, and the
   holder cell says how the case came to that person, so rows are two lines. */
.table--moved, .table--movedcases { table-layout: fixed; width: 100%; }
/* Recent runs sits beside Workload in a half-width card: 8px inner padding keeps
   "11-09-2026 11:24" on one line and CONSIDERED inside its cell down to 1280. */
.table--runs th, .table--runs td { padding-left: var(--s2); padding-right: var(--s2); }
.table--runs th:first-child, .table--runs td:first-child { padding-left: var(--s3); }
.table--runs th:last-child, .table--runs td:last-child { padding-right: var(--s3); }
.table--moved { min-width: 340px; }
.table--moved th, .table--moved td { padding-left: var(--s2); padding-right: var(--s2); }
.table--moved th:first-child, .table--moved td:first-child { padding-left: var(--s3); }
.table--moved th:last-child, .table--moved td:last-child { padding-right: var(--s3); }
/* The name takes what is left after the figures and a "See cases" button. */
.table--moved th:nth-child(1) { width: auto; } .table--moved th:nth-child(2) { width: 17%; }
.table--moved th:nth-child(3) { width: 26%; } .table--moved th:nth-child(4) { width: 96px; }
.table--moved .empty { padding: var(--s4) var(--s3); }
.table--movedcases { min-width: 560px; }
.table--movedcases th:nth-child(1) { width: 39%; } .table--movedcases th:nth-child(2) { width: 21%; }
.table--movedcases th:nth-child(3) { width: 22%; } .table--movedcases th:nth-child(4) { width: 18%; }
.table--movedcases th, .table--movedcases td { padding-left: var(--s2); padding-right: var(--s2); }
.table--movedcases th:first-child, .table--movedcases td:first-child { padding-left: var(--s3); }
.table--movedcases th:last-child, .table--movedcases td:last-child { padding-right: var(--s3); }
.table--movedcases td { padding-block: var(--s1); }
/* The first line of an empty state says what is empty; the rest says why. */
.empty__t { font-size: var(--t-body); font-weight: var(--w-semi); color: var(--ink-2); margin-bottom: var(--s1); }
.table--stage { min-width: 500px; }
/* By person on Performance: fourteen columns at 12px a side ran the Lifetime
   figure off the right of the box below 1440px. 8px a side is 112px back. */
table.perf th, table.perf td { padding-left: var(--s2); padding-right: var(--s2); }
/* Five equal fifths put the whole-book outstanding (Rs 2,14,94,65,462) in the same
   width as the decorative bar, and below 1440px it was painted into Collected. */
/* The stage words stay on one line: wrapped, four of five rows grew to 40px at
   1366, and the chip's title still carries the full label when they are cut. */
.table--stage th:nth-child(1) { width: 35%; } .table--stage th:nth-child(2) { width: 12%; }
.table--stage th:nth-child(3) { width: 22%; } .table--stage th:nth-child(4) { width: 23%; }
.table--stage th:nth-child(5) { width: 8%; }
.table--stage th, .table--stage td { padding-left: var(--s2); padding-right: var(--s2); }
.table--stage td:first-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.table--stage td small { font-size: var(--t-meta); color: var(--ink-3); }
.table--people { min-width: 680px; }
/* Fixed layout with no widths split the table into equal sixths: the bar got as
   much room as the money, and at 820 the heads were cut. Person carries the desk
   chip beside the name, so it is the widest share. */
.table--people th:nth-child(1) { width: 25%; } .table--people th:nth-child(2) { width: 9%; }
.table--people th:nth-child(3) { width: 16%; } .table--people th:nth-child(4) { width: 17%; }
.table--people th:nth-child(5) { width: 16%; } .table--people th:nth-child(6) { width: 17%; }
/* The collection tab. `.tbl` used to sit on these and on My work's log and had
   NO rule anywhere — the bare `table` element rule was doing all the work. A
   class that styles nothing is a hook that reads like a style, so these use the
   same .table--X convention as every other table in the app. */
.table--log { min-width: 940px; }
.table--months { min-width: 420px; }
/* Five columns in a half-width card since Logged joined Received (14-09-2026).
   The month name is the widest thing in the table and got the same fifth as a
   four-digit count, so "September 2026" was cut. */
.table--months th:nth-child(1) { width: 28%; } .table--months th:nth-child(2) { width: 14%; }
.table--months th:nth-child(3) { width: 12%; } .table--months th:nth-child(4) { width: 25%; }
.table--months th:nth-child(5) { width: 21%; }
.table--months th, .table--months td { padding-left: var(--s2); padding-right: var(--s2); }
/* The outer edges keep the card's 12px inset, so Month lines up under "By month". */
.table--months th:first-child, .table--months td:first-child { padding-left: var(--s3); }
.table--months th:last-child, .table--months td:last-child { padding-right: var(--s3); }
/* Seventy months at 30px ran the By month card ~2,700px down the page while By
   person beside it ended after twelve rows — ~2,160px of bare paper in the right
   column at 1366, and the receipts table pushed below y=2,900. The list scrolls
   inside its own card instead, capped to about the height of its neighbour.
   NOT .scroll-x--capped: D14 keeps sticky heads to Cases, Dispositions, the
   receipts table and the scorecard, so this head scrolls with its rows. */
.scroll-y--month { max-height: max(300px, 48vh); overflow: auto; }
.scroll-y--month th { position: static; }
.table--whose { min-width: 380px; }
/* Person carries a name and its desk chip; Loans is a three-digit count. Equal
   thirds gave the count as much room as the name. */
.table--whose th:nth-child(1) { width: 42%; } .table--whose th:nth-child(2) { width: 16%; }
.table--whose th:nth-child(3) { width: 42%; }
.table--receipts { min-width: 960px; }
/* Fixed layout takes its widths from the head row, and these heads had none, so
   the table split into six equal columns and cut 34 of 100 names and dates.
   960, not 900: at the floor the mono date needs 11% and the 19-character loan
   number 17% of at least that. */
.table--receipts th:nth-child(1) { width: 11%; } .table--receipts th:nth-child(2) { width: 17%; }
.table--receipts th:nth-child(3) { width: 30%; } .table--receipts th:nth-child(4) { width: 12%; }
.table--receipts th:nth-child(5) { width: 12%; } .table--receipts th:nth-child(6) { width: 18%; }
.table--months tbody tr[data-month],
.table--whose tbody tr[data-who] { cursor: pointer; }
.table--months tbody tr.is-on,
.table--whose tbody tr.is-on { background: var(--accent-bg); box-shadow: inset 2px 0 0 var(--accent); }
/* The loan number is the thing you match against a bank statement, so it is set
   in the same face as every other identifier in the app. */
.cell-mono { font-family: var(--mono); font-size: var(--t-meta); color: var(--ink-2); }

/* The explanatory line under a table.
   `.note` was used on the new Collection page and on My work's log card, and the
   only rule for it lives in the inline <style> of incentive.html and
   performance.html — neither of which those pages load. So the sentence that says
   what a table means rendered at full body size in full-strength ink, reading as
   another row of content rather than as a note about one. Same defect as .linkish
   and .tbl, found the same way. */
.note {
  margin-top: var(--s3); padding-top: var(--s2); border-top: 1px solid var(--rule);
  font-size: var(--t-meta); line-height: 1.5; color: var(--ink-3);
}
.note a { color: var(--info); }

/* ── Per person, on the Overview ──────────────────────────────────────────
   Every row points the four figures above at that person, so it has to look
   like something you can press. `is-on` marks the one currently showing — a
   page that changes what it is about without saying which row did it reads as
   a glitch. */
.table--people tbody tr[data-who] { cursor: pointer; }
.table--people tbody tr[data-who]:hover { background: var(--band); }
.table--people tbody tr.is-on { background: var(--accent-bg); box-shadow: inset 2px 0 0 var(--accent); }

/* The person picker sits in the page head, styled like the filter-bar selects
   so one control does not read as a different kind of control per page. */
.act-row select, .act-row input {
  height: var(--ctl); padding: 0 var(--s2); font: var(--w-reg) var(--t-meta)/1 var(--font);
  color: var(--ink); background: var(--sheet); border: 1px solid var(--rule-2); border-radius: var(--r);
}
.act-row select { min-width: 150px; max-width: 230px; }
.act-row select:focus, .act-row input:focus { border-color: var(--focus); outline: none; box-shadow: 0 0 0 2px var(--focus-glow); }

/* Zero-width until selection is switched on, so the table an agent reads all
   day is exactly the table they signed off on. */
.col-sel { width: 0; }
.is-selecting .col-sel { width: 40px; }
.col-sel { padding: 0; overflow: hidden; }
.is-selecting .col-sel { padding: 0 var(--s2); overflow: visible; }
.col-sel input { margin: 0; vertical-align: middle; width: 18px; height: 18px; }
/* A zero-width cell clips the paint and nothing else: with selection off, fifty
   checkboxes stayed in the tab order and were still announced ("Select <name>")
   on a page that offers no selection. visibility takes them out of both. */
.table--cases:not(.is-selecting) .col-sel input { visibility: hidden; }
/* The box is 18px because a 24px tick in a 30px row reads as a control the row is
   hung on; the target is the label around it, which is the full height of the row. */
.pick-hit {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 28px; cursor: pointer;
}
tr.is-picked td { background: var(--info-w); }
/* The muted tier is 4.18:1 on that wash, under AA; the next tier up holds it. */
tr.is-picked :is(.sno, .money--nil, .disp-none, .cell-sub) { color: var(--ink-2); }
tr.is-picked td.grp { border-left-color: var(--info-r); }

.movebar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  flex-wrap: wrap; padding: var(--s2) var(--s4);
  background: var(--info-w); border-top: 1px solid var(--info-r); border-bottom: 1px solid var(--info-r);
  font-size: var(--t-body);
}
.movebar__l { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; }
/* Wraps. The four controls need about 580px of card interior and a 390px phone
   gives 334, so on one line Move and Cancel sat outside the card — which clips —
   and a manager who had ticked rows and typed the reason could not press Move at
   all. The select keeps its 190px (the longest agent name); the reason field is
   what gives ground. */
.movebar__r { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; row-gap: var(--s1); }
.movebar__r select { min-width: 190px; }
.movebar__r input { min-width: 180px; flex: 1 1 180px; }
/* The Move to / Reason controls were the browser's own, beside 24px buttons: one
   toolbar height and one field style, as the filter bar. */
.movebar__r select, .movebar__r input {
  height: var(--ctl); padding: 0 var(--s2); font: var(--w-reg) var(--t-meta)/1 var(--font);
  color: var(--ink); background: var(--sheet); border: 1px solid var(--rule-2); border-radius: var(--r);
}
.movebar__r .btn { height: var(--ctl); }
.movebar__err { flex: 1 0 100%; font-size: var(--t-meta); }
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* S. No. is 5%, not 4.5%: its tracked-caps head needs 53px at the 1120px floor or
   it breaks over two lines. The half point comes from the loan number, which
   still holds 19 mono characters there. */
.col-sno { width: 5%; } .col-ent { width: 5.5%; } .col-loan { width: 14.5%; }
.col-name { width: 16%; } .col-case { width: 30%; }
.col-branch { width: 8%; } .col-ageing { width: 10%; }
.col-money { width: 10%; } .col-owner { width: 9%; } .col-disp { width: 12%; }
.col-when { width: 10%; } .col-case2 { width: 20%; } .col-code { width: 13%; }
.col-ptp { width: 11%; } .col-amt { width: 11%; } .col-remarks { width: 18%; }

/* The clickable case name inside a table cell.
   `linkish` was used on My work's log and defined NOWHERE — the customer's name
   was a bare <button>, which is how the one column you navigate by ended up the
   hardest thing on the row to see. */
.linkish {
  /* padding-block, not padding: the name is the one thing on the row you press,
     and at the bare line box it was a 20px target. Four pixels either side make it
     28px inside a 30px row, so the row height does not move. */
  appearance: none; background: none; border: none; padding: var(--s1) 0; margin: 0;
  /* font-family and font-size, NOT the `font` shorthand: `font: inherit` also
     resets font-weight, and this class is worn together with .cell-title, whose
     whole job is the weight. The shorthand is declared later, so it silently won
     and the customer's name came out at normal weight — a rule cancelling a rule
     it was meant to sit beside. */
  font-family: inherit; font-size: inherit; line-height: inherit;
  color: inherit; text-align: left; cursor: pointer;
  /* An underline, not a bottom border: with the padding above, a border would
     draw a line 4px under the word, against the row's own rule. */
  text-decoration: underline; text-decoration-color: var(--rule-2); text-underline-offset: 2px;
}
.linkish:hover { color: var(--info); text-decoration-color: var(--info); }
.linkish:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* An entry logged today can still be corrected; after midnight it is a record.
   Said on the row, not only in a footnote under the table. */
.table--log tbody tr.is-today > td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
.col-by { width: 9%; } .col-act2 { width: 8%; }
.col-cwhen { width: 11%; } .col-ccase { width: 20%; } .col-cchan { width: 15%; }
.col-cout { width: 13%; } .col-cbody { width: 25%; } .col-cdur { width: 7%; } .col-cby { width: 9%; }
/* When: 15% of the 1020px floor less padding is 137px, for a 125px mono stamp. */
.col-lwhen { width: 15%; } .col-lwho { width: 11%; } .col-lact { width: 14%; }
.col-lwhat { width: 26%; } .col-ldet { width: 22%; } .col-lwhere { width: 12%; }
/* Sign-ins had borrowed the Activity shares and cut the address at 21 characters
   while Reason, one or two words, had the room. Daily uploads had no colgroup, so
   the file name got the same sixth as each of three counts. */
.col-gwhen { width: 16%; } .col-gacct { width: 30%; } .col-gres { width: 10%; }
.col-greason { width: 22%; } .col-gwhere { width: 22%; }
.col-uwhen { width: 17%; } .col-uprod { width: 9%; } .col-ufile { width: 44%; } .col-ucount { width: 10%; }
.col-person { width: 26%; } .col-role { width: 11%; } .col-desk { width: 11%; }
.col-prods { width: 15%; } .col-mgr { width: 12%; } .col-seen { width: 11%; } .col-act { width: 14%; }

/* ══ Responsive ═════════════════════════════════════════════════════════ */
@media (max-width: 1100px) {
  /* auto 1fr: a min-height grid with two auto rows shared the spare height out, so
     a short page opened with up to 269px of blank paper under the bar. */
  .app { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .side { position: static; height: auto; flex-direction: row; align-items: center; gap: var(--s3); }
  .side__nav { flex-direction: row; flex-wrap: wrap; }
  .side__foot { border-top: none; padding-top: 0; flex-direction: row; align-items: center; }
  .content { padding: var(--s3); }
}
/* Tablet and phone: one bar and one strip. The row above never wrapped, so brand,
   foot and a theme picker that would not shrink left the nav an 80px column and
   pushed every page 140-190px wider than a 390 screen. Now the brand, the person,
   the picker (icons only; the words stay for screen readers) and Sign out share the
   bar, and the links run in one line under it that scrolls sideways on its own —
   shell.js brings the current page into view. The scrollbar stays: in an 820px
   desktop window it is a mouse's only way to the last links. */
@media (max-width: 820px) {
  .side {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center;
    gap: var(--s2) var(--s3); padding: var(--s2) var(--s3) 0; overflow: visible;
  }
  .side__brand { grid-column: 1; grid-row: 1; }
  .side__foot { display: contents; }
  .side__user { grid-column: 2; grid-row: 1; justify-self: end; margin: 0; max-width: 100%; }
  .side__urole { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .themepick { grid-column: 3; grid-row: 1; grid-template-columns: repeat(3, 28px); }
  .themepick button { height: 26px; padding: 0; justify-content: center; }
  .themepick button span {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
  .side .side__out { grid-column: 4; grid-row: 1; }
  .side__nav {
    grid-column: 1 / -1; grid-row: 2; flex-direction: row; flex-wrap: nowrap; gap: 2px;
    overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; scrollbar-color: var(--on-chrome-3) transparent;
    margin: 0 calc(-1 * var(--s3)); padding: var(--s2) var(--s3); border-top: 1px solid var(--chrome-rule);
  }
  .side__link { white-space: nowrap; }
  /* The sign-in screens keep their mark as a strip rather than losing every trace
     of whose system this is. auto 1fr, or the strip stretches to 271px of black. */
  .auth { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .auth__brand { flex-direction: row; align-items: center; padding: var(--s4) var(--s5); }
  .auth__pitch, .auth__facts { display: none; }
  .checks { grid-template-columns: repeat(3, 1fr); }
}
/* A tablet held upright has the height for the pitch: a band over the form instead
   of a 69px strip above 800px of paper. Never on a phone or a landscape screen,
   where it would push the form under the keyboard. */
@media (min-width: 621px) and (max-width: 820px) and (min-height: 720px) {
  .auth__brand { flex-direction: column; align-items: stretch; gap: var(--s5); padding: var(--s5); }
  .auth__pitch { display: block; }
  .auth__facts { display: flex; }
}
@media (max-width: 620px) {
  .side { column-gap: var(--s2); }
  .side__name { display: none; }
  .side__uname { font-size: var(--t-meta); }
  /* On a phone the keyboard takes the lower half: the form starts under the strip
     instead of floating mid-screen above 200px of nothing. */
  .auth__panel { align-items: start; }
}

/* ══ Hero — the one dominant figure on the overview ═════════════════════ */
.hero {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--s5);
  padding: var(--s3) var(--s4); background: var(--sheet);
  border: 1px solid var(--rule); border-radius: var(--r); margin-bottom: var(--s3);
}
.hero__l { font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--ink-3); }
.hero__n {
  font-family: var(--mono); font-size: var(--t-hero); font-weight: var(--w-semi);
  letter-spacing: var(--tr-big); line-height: 1.02; margin-top: var(--s1);
  font-variant-numeric: tabular-nums lining-nums slashed-zero;
}
.hero__s { font-size: var(--t-meta); color: var(--ink-2); margin-top: var(--s1); }
.hero__side { display: flex; flex-wrap: wrap; row-gap: var(--s2); column-gap: var(--s5); text-align: right; }
/* Once the figures wrap under the headline they start from the left edge, and a
   right-aligned label over a narrower figure read as centred. */
@media (max-width: 760px) { .hero__side { text-align: left; } }
.hero__fig b { display: block; font-family: var(--mono); font-size: var(--t-kpi); font-weight: var(--w-semi); letter-spacing: var(--tr-big); }
.hero__fig span { display: block; font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--ink-3); }
.hero__fig em { display: block; font-style: normal; font-size: var(--t-meta); color: var(--ink-2); margin-top: 1px; }
.hero__fig--bad b  { color: var(--bad); }
.hero__fig--warn b { color: var(--warn); }
.hero__fig--good b { color: var(--good); }
.hero__fig b.is-zero { color: var(--ink-2); }

/* A ledger line: label, count, money, and a share bar drawn against ONE
   denominator for the whole page. Every block used to normalise to its own
   maximum, which put ₹151.84 Cr, ₹105.04 Cr and ₹198.05 Cr on screen as
   three identical full-width bars. */
/* One grid for the block and each row a subgrid of it, so the money lines up down
   the rows and sits right after the longest month rather than 566px from it at
   1920; the count track is empty on the month ledger and collapses to nothing, and
   the bar takes what is left. Fixed tracks left a dead 56px column and cut
   "Sep 2026" at 820. */
.led { display: grid; grid-template-columns: minmax(0, max-content) auto auto minmax(48px, 1fr); column-gap: var(--s3); }
.led__row {
  display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; gap: var(--s3);
  align-items: center; height: var(--row); padding: 0 var(--s3);
  border-bottom: 1px solid var(--rule); text-decoration: none; color: inherit;
}
.led__row:last-child { border-bottom: none; }
a.led__row:hover { background: var(--band); }
.led__k { font-size: var(--t-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.led__k small { font-size: var(--t-meta); color: var(--ink-3); margin-left: var(--s2); }
.led__n { font-family: var(--mono); font-size: var(--t-meta); color: var(--ink-3); text-align: right; }
.led__v { font-family: var(--mono); font-size: var(--t-fig); font-weight: var(--w-med); text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums lining-nums slashed-zero; }
/* display:block because the stage and person tables put the bar in a <td> as a
   span: left inline, it ignored its height and drew a 30px slab. */
.led__bar { display: block; height: 4px; background: var(--band); box-shadow: inset 0 0 0 1px var(--rule); }
.led__bar i { display: block; height: 100%; background: var(--info-mark); }
.led__bar i.is-warn { background: var(--warn-mark); }
.led__bar i.is-bad  { background: var(--bad-mark); }
.led__bar i.is-good { background: var(--good-mark); }
.led__bar i.is-mute { background: var(--ink-4); }

/* Two cards side by side only while each still gets 500px: at 320px a table card
   squeezed its columns into a sideways scroll long before the page ran out of room.
   min() lets a single card shrink below 500 on a phone instead of overflowing. */
.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 500px), 1fr)); gap: var(--s3); align-items: start; margin-bottom: var(--s3); }

/* ══ Primitives ═════════════════════════════════════════════════════════
   Each of these exists because the same declaration was being written inline
   in the markup a dozen times. An inline style is the stylesheet losing an
   argument with the page; there were 112 of them. */
.card--pad { padding: var(--s3) var(--s4); }
.mb   { margin-bottom: var(--s3); }
.mb-0 { margin-bottom: 0; }
.mt   { margin-top: var(--s3); }
.mt-1 { margin-top: var(--s1); }
.h3   { margin: 0 0 var(--s3); font-size: var(--t-cap); font-weight: var(--w-semi);
        letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--ink-2); }
.is-warn { color: var(--warn); }
.is-bad  { color: var(--bad); }
.is-good { color: var(--good); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
/* .h3's own 12px margin, inside a centred row, set the title 5px above its count. */
.row-between > .h3 { margin: 0; }
.stack { display: grid; gap: var(--s3); align-content: start; }

/* ══ Idle sign-out ══════════════════════════════════════════════════════
   The session cookie is rolling with an hour's maxAge, so the server signs
   somebody out an hour after their last request. This is the warning the
   shell raises shortly before that, and the blanking it applies on the way
   out — a dashboard left open shows a customer's name, loan and mobile, and
   that should not still be on screen when somebody walks past it at seven. */
.idle {
  position: fixed; z-index: 60; top: var(--s3); left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3); border-radius: var(--r);
  background: var(--warn-w); color: var(--warn); border: 1px solid var(--warn-r);
  font-size: var(--t-meta); box-shadow: 0 6px 24px rgb(0 0 0 / 18%);
}
.idle__dot { width: 6px; height: 6px; border-radius: 99px; background: var(--warn-mark); flex: none; }
.idle__t b { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.idle .btn { margin-left: var(--s2); }

/* Everything goes, including the sidebar: the screen should be blank between
   the timeout firing and the login page arriving, however slow that is. */
body.is-signedout > * { visibility: hidden; }
body.is-signedout { background: var(--paper); }

/* ══ Dispositions, coloured ═════════════════════════════════════════════
   Every disposition used to be the same grey pill, so a column of forty rows
   said nothing until you read each one. The tone comes from
   disposition_codes.tone in the database, not a map in the page, so Cases and
   Dispositions cannot disagree about what NC looks like. Text tokens on the
   matching wash: check:contrast holds all four pairs above 4.5:1. */
.dchip { background: var(--band); font-family: var(--mono); letter-spacing: 0; }
.dchip--good { background: var(--good-w); color: var(--good); border-color: var(--good-r); }
.dchip--warn { background: var(--warn-w); color: var(--warn); border-color: var(--warn-r); }
.dchip--bad  { background: var(--bad-w);  color: var(--bad);  border-color: var(--bad-r); }
.dchip--info { background: var(--info-w); color: var(--info); border-color: var(--info-r); }

/* ══ Money on a disposition ═════════════════════════════════════════════
   An amount with no date could not answer "when did it come in", and one
   customer paying in three instalments had to be flattened into one number.
   One row per payment, each keeping its own date. The block only appears for a
   disposition that involves money. */
.pays {
  border: 1px solid var(--good-r); background: var(--good-w);
  border-radius: var(--r); padding: var(--s2) var(--s3); margin-bottom: var(--s3);
}
.pays__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s2); }
.pays__title { font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: var(--tr-cap);
  text-transform: uppercase; color: var(--good); }
.pays__total { font-family: var(--mono); font-size: var(--t-fig); color: var(--ink-2);
  font-variant-numeric: tabular-nums; }
.pays__total--on { color: var(--good); font-weight: var(--w-semi); }
.pays__hint { font-size: var(--t-meta); color: var(--ink-2); margin: 2px 0 var(--s2); }
.pays__rows { display: grid; gap: 6px; margin-bottom: var(--s2); }
/* Three cells: the date it came in, the amount, and the button that removes the
   row. It was five — a mode select and a reference box sat between them until
   11-09-2026. Their columns stayed behind, so every payment row carried a 116px
   gap and a second 1fr of nothing between the amount and the x. */
.pays__row { display: grid; grid-template-columns: 150px 1fr var(--ctl); gap: 6px; align-items: center; }
.pays__row input, .pays__row select { min-width: 0; }
/* Outside any .field, so these were the browser's own boxes: 21 and 24px tall,
   2px inset bevels and a generic monospace, under a form of 30px token fields. */
.pays__row input {
  height: var(--ctl); padding: 0 var(--s2); font: var(--w-reg) var(--t-body)/1 var(--mono);
  font-variant-numeric: tabular-nums lining-nums slashed-zero; font-feature-settings: var(--figs);
  color: var(--ink); background: var(--sheet); border: 1px solid var(--rule-2); border-radius: var(--r);
}
.pays__row input:focus { border-color: var(--focus); outline: none; box-shadow: 0 0 0 2px var(--focus-glow); }
.pays__x {
  border: 1px solid var(--rule); background: var(--paper); color: var(--ink-3);
  border-radius: var(--r); height: var(--ctl); cursor: pointer; font: var(--w-reg) var(--t-fig)/1 var(--font);
}
.pays__x:hover { color: var(--bad); border-color: var(--bad-r); background: var(--bad-w); }
@media (max-width: 620px) {
  /* A mono date and the picker's own icon need 120px, or the icon sits on the year. */
  .pays__row { grid-template-columns: 136px 1fr var(--ctl); }
}

/* The dated breakdown, wherever a total alone would hide it. */
.paysplit { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.paysplit__i {
  display: inline-flex; gap: 5px; align-items: baseline;
  border: 1px solid var(--rule); border-radius: 3px; padding: 0 6px;
  background: var(--sheet); font-size: var(--t-cap);
}
.paysplit__d { color: var(--ink-3); font-family: var(--mono); }
.paysplit__a { font-family: var(--mono); color: var(--good); font-variant-numeric: tabular-nums; }
.paysplit__m { color: var(--ink-3); }

.amtsplit { display: block; margin-top: 3px; }
.amtsplit__i {
  display: block; font-size: var(--t-cap); color: var(--ink-2);
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.amtsplit__d { color: var(--ink-3); margin-right: 5px; }

/* Which source a payment came from. The recovery upload is money the lender
   has; an agent row is what somebody logged on a call. Never added together. */
.srcpill--upload { background: var(--info-w); color: var(--info); border-color: var(--info-r); }
.srcpill--agent  { background: var(--band);   color: var(--ink-2); }

/* Whether a payment an agent logged ever turned up at the lender (step 33).
   Deliberately quiet: "not yet received" on something logged this morning is
   the normal state of a payment that has not settled, not an accusation. */
.matchpill {
  display: inline-block; padding: 0 6px; border-radius: 9px;
  font-size: var(--t-cap); line-height: 16px; border: 1px solid transparent;
  white-space: nowrap; vertical-align: 1px;
}
.matchpill--ok   { background: var(--good-w); color: var(--good); border-color: var(--good-r); }
.matchpill--wait { background: var(--band);   color: var(--ink-2); border-color: var(--line); }
.matchpill--no   { background: var(--warn-w); color: var(--warn); border-color: var(--warn-r); }
#dPayTable tbody tr.is-agent td { background: var(--sheet); }
/* Payment history in the case drawer: 534px inside the 600px panel. Auto-width, the
   nowrap reference set the table at 666-705px and the card cut off Where it came
   from. Fixed columns sized to what each carries — "Logged by agents" in the foot
   (101px) is the date column's widest text, "Recovery upload" (106px) the
   source's — and the reference takes the rest, wrapping the rare long one (up to
   75 characters in the file) rather than cutting it. Narrower than 534px, the
   table scrolls inside its card. Edge cells sit on the card's title edge. */
#dPayTable { table-layout: fixed; min-width: 534px; }
#dPayTable th, #dPayTable td { padding-left: var(--s2); padding-right: var(--s2); }
#dPayTable :is(th, td):first-child { padding-left: 0; }
#dPayTable :is(th, td):last-child { padding-right: 0; }
#dPayTable th { white-space: normal; }
#dPayTable th:nth-child(1) { width: 110px; }
#dPayTable th:nth-child(2) { width: 86px; }
#dPayTable th:nth-child(3) { width: 82px; }
#dPayTable th:nth-child(5) { width: 116px; }
#dPayHist td:nth-child(4) .cell-sub { white-space: normal; overflow-wrap: anywhere; }
#dPayFoot td:first-child, #dPayHist td.col-date { white-space: nowrap; }

/* ══ The incentive approval ═════════════════════════════════════════════
   Its own screen now, /incentive-consent, not a dismissible modal — so the
   ladder sits in the sign-in card rather than a dialog. Wider than the
   password card because it carries a table somebody has to read before
   agreeing to it, and a ladder squeezed into 340px is not something anyone
   reads. */
.auth__card--wide { max-width: 470px; }
.modal__box--wide { max-width: 620px; }
.cns__tbl { width: 100%; border-collapse: collapse; margin-bottom: var(--s2); }
.cns__tbl th { text-align: left; font-size: var(--t-cap); letter-spacing: var(--tr-cap);
  text-transform: uppercase; color: var(--ink-3); font-weight: var(--w-semi);
  padding: 0 var(--s2) var(--s1); border-bottom: 1px solid var(--rule); }
.cns__tbl td { height: var(--row); padding: 0 var(--s2); border-bottom: 1px solid var(--rule);
  font-size: var(--t-body); }
.cns__tbl tr:last-child td { border-bottom: none; }
/* Scoped to the table: .cns__tbl th's text-align:left outranked a bare .cns__n,
   so the Incentive head sat left of its right-aligned figures. */
.cns__tbl .cns__n { text-align: right; }
td.cns__n { font-family: var(--mono); font-variant-numeric: tabular-nums lining-nums slashed-zero; }
.cns__award { display: block; font-family: var(--font); font-size: var(--t-meta); color: var(--good); }
.cns__note, .cns__more { font-size: var(--t-meta); color: var(--ink-3); margin: 0 0 var(--s3); }
/* The four terms on the approval screen. Numbered, because somebody is agreeing
   to them one by one and an unnumbered list of four reads as a paragraph. */
.cns__terms {
  margin: 0 0 var(--s3); padding-left: 1.35em; list-style: decimal;
  font-size: var(--t-meta); color: var(--ink-2); line-height: 1.6;
}
.cns__terms li { margin-bottom: var(--s2); }
.cns__terms li::marker { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.cns__terms b { color: var(--ink); font-weight: var(--w-semi); }
/* The name of one ladder among several on the approval screen. Two ladders
   stacked without their names read as one long one, which is a different
   promise from the one being made. */
/* ══ Two measures, one payout ═══════════════════════════════════════════
   A manager measured on two desks against two ladders. Shown as a list rather
   than two figures side by side, because side by side is an invitation to add
   them — and only the better single rung is paid. */
/* It sits inside the card, so only a top rule: side borders doubled the card's. */
.meas__wrap { display: grid; gap: 1px; background: var(--rule); border: solid var(--rule);
  border-width: 1px 0 0; overflow: hidden; margin: 0; }
.meas { display: grid; grid-template-columns: 1fr auto auto; gap: var(--s3);
  align-items: baseline; padding: var(--s2) var(--s3); background: var(--sheet); }
.meas--best { background: var(--good-w); }
.meas__l { font-size: var(--t-meta); color: var(--ink-2); }
.meas__k { display: block; font-size: var(--t-cap); color: var(--ink-3); }
.meas__n { font-family: var(--mono); font-variant-numeric: tabular-nums lining-nums slashed-zero;
  font-weight: var(--w-semi); }
.meas__r { font-size: var(--t-meta); color: var(--ink-3); text-align: right; min-width: 11ch; }
.meas__note { padding: var(--s2) var(--s3); background: var(--sheet); font-size: var(--t-meta);
  color: var(--ink-3); }

.cns__grp td { background: var(--band); font-size: var(--t-cap); letter-spacing: var(--tr-cap);
  text-transform: uppercase; color: var(--ink-2); font-weight: var(--w-semi);
  height: var(--row); }

/* ══ Overview charts — four donuts ════════════════════════════════════════
   "Overview 4 chart or PIE Chart basis of the entries" (14-09-2026). Donuts at
   the user's request; the rules that keep a pie honest are applied rather than
   argued with: at most seven slices (the rest fold into Other), slices in a fixed
   order so a colour always means the same thing, a 2px gap in the sheet colour
   between slices, and a legend with the count and share on every row, so
   identity and value never rest on colour alone. */
.charts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s3); margin-bottom: var(--s3); }
@media (max-width: 1280px) { .charts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .charts { grid-template-columns: 1fr; } }
/* The rings line up because the title block above them is the same height in every
   card — two lines of sub reserved — NOT because the cards are stretched to one
   another. A two-row subgrid did align the rings, but it also made every card as
   tall as "Where the cases are", whose legend has three more rows: 93px, 72px and
   113px of empty paper under the other three at 1366. Each card is now its own
   height and starts at the top of the row. */
.charts { grid-auto-rows: auto; align-items: start; }
.chart { background: var(--sheet); border: 1px solid var(--rule); border-radius: var(--r); padding: var(--s3); display: grid; grid-auto-rows: min-content; row-gap: var(--s2); min-width: 0; }
.chart__body { align-self: start; }
.chart__h { font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--ink-2); margin: 0; }
/* Two lines reserved at the body's 1.45: a one-line sub keeps the second, so every
   ring in the row starts on the same y without the cards being stretched to match. */
.chart__sub { font-size: var(--t-meta); color: var(--ink-3); margin: 0; min-height: calc(2 * 1.45 * var(--t-meta)); }
.chart__body { display: flex; align-items: center; gap: var(--s3); min-width: 0; }
/* 120px for a 120-unit viewBox, so the type sizes below are the sizes drawn. */
.chart__ring { flex: none; width: 120px; height: 120px; }
.chart__ring circle { fill: none; }
.chart__ring .ring-track { stroke: var(--band); }
/* With no slices the track is the only mark, and --band on the sheet is 1.07:1. */
.chart__ring .ring-track.is-empty { stroke: var(--rule); }
.chart__total { font-family: var(--mono); font-size: var(--t-lead); font-weight: var(--w-semi); fill: var(--ink); }
/* An eight-character money centre ("₹12.34 L") at 18px is wider than the hole. */
.chart__total--long { font-size: var(--t-fig); }
.chart__unit { font-size: var(--t-cap); fill: var(--ink-3); letter-spacing: var(--tr-cap); text-transform: uppercase; }
.chart__legend { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; min-width: 0; flex: 1; }
.chart__legend li { display: grid; grid-template-columns: 9px minmax(0, 1fr) auto auto; align-items: baseline; column-gap: 6px; font-size: var(--t-meta); color: var(--ink-2); }
.chart__legend li > i { width: 9px; height: 9px; border-radius: 2px; align-self: center; }
.chart__legend li > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chart__legend li > b { font-family: var(--mono); font-weight: var(--w-med); color: var(--ink); font-variant-numeric: tabular-nums; }
.chart__legend li > em { font-style: normal; font-family: var(--mono); color: var(--ink-3); font-variant-numeric: tabular-nums; min-width: 3.2em; text-align: right; }
/* A zero row kept for the partition (Kept 0, Broken 0): its figure drops from ink
   to the row's ink-2, the one colour every zero in the app takes. */
.chart__legend li.is-zero > b { color: inherit; }
.chart__empty { font-size: var(--t-meta); color: var(--ink-3); }
/* Four across leaves each card ~270px at 1366 and ~330px at 1600: too narrow for
   the ring AND a legend beside it, so the legend goes underneath until there is room. */
@media (max-width: 1720px) and (min-width: 1281px) { .chart__body { flex-direction: column; align-items: stretch; } .chart__ring { align-self: center; } }

/* ══ EMI, month by month (js/emi-months.js) ═════════════════════════════════
   One component in the case panel and on the EMI page. Built to fit the 600px
   drawer: the paid amount sits under the instalment rather than in a column of
   its own, and a row's buttons stack. */
.emim { display: grid; gap: var(--s3); }
.emim__title { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s1) var(--s3); flex-wrap: wrap; margin-bottom: var(--s2); }
.emim__head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s1) var(--s4); flex-wrap: wrap; font-size: var(--t-body); }
.emim__terms b { font-family: var(--mono); font-weight: var(--w-semi); }
.emim__prog { color: var(--ink-2); }
.emim__table { width: 100%; border-collapse: collapse; }
.emim__table th {
  text-align: left; padding: var(--s2); border-bottom: 1px solid var(--rule-2); white-space: nowrap;
  font: var(--w-med) var(--t-cap)/1 var(--font); letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--ink-3);
}
.emim__table th.num, .emim__table td.num { text-align: right; }
.emim__table td { padding: var(--s2); border-bottom: 1px solid var(--rule); vertical-align: top; font-size: var(--t-body); }
.emim__table tr.is-missed td, .emim__table tr.is-short td { background: var(--bad-w); }
.emim__seq { font-family: var(--mono); color: var(--ink-3); width: 28px; }
.emim__date, .emim__amt { display: block; font-family: var(--mono); font-size: var(--t-meta); color: var(--ink); white-space: nowrap; }
.emim__note { display: block; font-size: var(--t-meta); color: var(--ink-3); white-space: nowrap; }
.emim__st { display: block; font-size: var(--t-cap); font-weight: var(--w-semi); letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--warn); }
.emim__st--paid { color: var(--good); }
.emim__st--part { color: var(--info); }
.emim__st--bad  { color: var(--bad); }
.emim__st--later { color: var(--ink-3); }
.emim__by { display: block; font-size: var(--t-meta); color: var(--info); }
.emim__by--file { color: var(--ink-3); }
.emim__prog .emim__by { display: inline; }
.emim__rem { display: block; font-size: var(--t-meta); color: var(--ink-2); white-space: nowrap; }
.emim__rem--auto { color: var(--ink-3); }
.emim__acts { width: 1%; }
.emim__btns { display: flex; flex-direction: column; align-items: stretch; gap: var(--s1); }
.emim__btns .btn { white-space: nowrap; justify-content: center; }
.emim__form { border: 1px solid var(--rule-2); border-radius: var(--r); background: var(--band); padding: var(--s3); }
.emim__formhead { font-size: var(--t-body); font-weight: var(--w-semi); margin-bottom: var(--s2); }
.emim__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0 var(--s3); }
.emim__form .modal__actions { margin-top: var(--s2); }
.emim__sub {
  margin-top: var(--s2);
  font: var(--w-med) var(--t-cap)/1 var(--font); letter-spacing: var(--tr-cap); text-transform: uppercase; color: var(--ink-3);
}
.emim__foot, .emim__empty { font-size: var(--t-meta); color: var(--ink-3); }
/* What the amount will pay, under the amount as it is typed. Warn-coloured when
   part of it will not count — the file already has it, or it is beyond the plan. */
.emim__land { font-size: var(--t-meta); color: var(--ink-2); margin: calc(-1 * var(--s1)) 0 var(--s2); min-height: 1.4em; }
.emim__land--warn { color: var(--warn); }
.emim__error { margin: 0; }

