/* ============================================================================
   KISHAN SUCHIKARAN PRANALI — ERP design system
   Ported from the Himalaya ERP shell (Bootstrap 5 + custom shell), rebranded
   to an agriculture-green palette. Self-contained: layout skeleton + the .ds
   component library. Loaded after Bootstrap 5.3; same selectors win.
   ========================================================================== */

/* ── Mukta (Ek Type, OFL — see fonts/mukta/OFL.txt) ───────────────────────────
   One typeface for BOTH scripts. Mukta ships Devanagari and Latin in a single
   family, so Nepali and English in the same label render in the same design
   instead of seaming between two fonts mid-sentence — which is why it replaces
   the old Inter + Noto Sans Devanagari pair (Inter has no Devanagari coverage
   at all, and was never vendored, so Latin text silently fell back to whatever
   the OS had).

   Self-hosted, not Google Fonts: the app's own CSP is `default-src 'self'` with
   no font-src override (apps.common.middleware.SecurityHeadersMiddleware), so
   any external font request is blocked outright.

   Mukta has no variable cut, so each weight is its own file — 400/500/600/700
   is exactly what this stylesheet and the templates use. The `unicode-range`
   split is what keeps that cheap: a page pulls only the subsets it actually
   renders (~150 KB for a Devanagari page at 400+600), never all 12 files. Add
   a weight here only alongside a matching pair of woff2 files. */
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-400-devanagari.b786401020a1.woff2") format('woff2');
  unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-400-latin.afc380f56ddb.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-400-latin-ext.47f8ed665f2a.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-500-devanagari.4a0e20612757.woff2") format('woff2');
  unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-500-latin.0742895157f6.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-500-latin-ext.df51ae23685c.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-600-devanagari.b021445bfb7a.woff2") format('woff2');
  unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-600-latin.af92558794b3.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-600-latin-ext.b9fed2814804.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-700-devanagari.693d9a1aabe1.woff2") format('woff2');
  unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-700-latin.9b7e3ea2eb7d.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Mukta';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/mukta/Mukta-700-latin-ext.80b8a31a5887.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── brand tokens (gov blue) ───────────────────────────────────────────────
   Blue, not the agriculture green this started as: the legacy system staff have
   used for years is blue, and matching it costs nothing while making the rebuild
   read as the same product rather than a different one (2026-08-20 client).

   Only the BRAND changes. The status colours below are untouched — green still
   means स्वीकृत, orange चेतावनी, red रोक्ने. Recolouring those would leave the
   त्रुटि engine's severities indistinguishable from ordinary chrome, which is the
   one thing on these pages that must never be ambiguous.

   Soft/line values are hardcoded (not color-mix) so old Android WebViews and
   government-office browsers render the brand correctly. */
:root {
  /* Lightened from the original navy ramp (#1d4f91) on 2026-08-26 — the darker
     blue read as heavy across a screen that is mostly chrome. Every value below
     is derived from --brand-primary so the ramp stays one colour rather than
     drifting into several blues.

     The chosen swatch was #4176a8; this is one step darker, and here is why.
     --brand-primary is not only a button fill — it is the LINK colour
     (.content-area a), and links sit on --ds-bg (#f6f7f9) as often as on white.
     #4176a8 scores 4.79:1 on white but only 4.47:1 on that tint, just under the
     4.5:1 AA floor for normal text. #4074a5 clears both (4.93 / 4.60) and is a
     1-3 value shift per channel — no visible difference, so nothing is traded
     away for it. Treat this as the light end of the range: anything lighter
     stops passing on the tinted background. */
  --brand-primary: #4074a5;
  --brand-primary-dark: #345f86;    /* hover / pressed — white on it is 6.5:1 */
  --brand-primary-light: #e8eff5;
  --brand-gradient-end: #6b98c1;
  --brand-accent: #2f5b83;
  --on-primary: #ffffff;
  --on-accent: #ffffff;
  --primary-soft: #e9f0f6;
  --primary-softer: #f4f8fb;
  --primary-line: #d3e2ee;
  --accent-soft: #e9eef3;

  --ds-bg: #f6f7f9;
  --ds-surface: #ffffff;
  --ds-border: #e6e8ee;
  --ds-border-strong: #d3d7e0;
  --ds-ink: #0b1524;
  --ds-muted: #59616f;
  --ds-subtle: #8990a0;

  --ok: #1e9e5a;   --ok-bg: #e7f6ee;   --ok-ink: #0f6b3b;
  --warn: #c77a0a; --warn-bg: #fbf1e0; --warn-ink: #8a5406;
  --bad: #d6453f;  --bad-bg: #fcebea;  --bad-ink: #9c2a25;
  --inf: #2563c9;  --inf-bg: #e9f0fc;  --inf-ink: #1b4a9c;

  --r-sm: 8px;  --r-md: 12px;  --r-lg: 16px;
  --sh-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
  --sh-md: 0 4px 14px rgba(16, 24, 40, 0.06);

  --sidebar-width: 240px;
  --topbar-height: 60px;
}

* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: 'Mukta', 'Noto Sans Devanagari', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  -webkit-font-smoothing: antialiased;
  font-size: 14px;
  line-height: 1.45;
  color: var(--ds-ink);
  background: var(--ds-bg);
}
/* Kept as a no-op alias: Mukta already covers Devanagari, so `.dev` no longer
   needs to swap in a second family. It stays because ~100 template call sites
   mark Devanagari runs with it, and it remains the hook for anything script-
   specific later (a Devanagari-only size or leading tweak). */
.dev { font-family: 'Mukta', 'Noto Sans Devanagari', sans-serif; }
.tnum { font-variant-numeric: tabular-nums; }

/* ── layout skeleton ──────────────────────────────────────────────────────── */
.app-wrapper { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-width);
  flex: none;
  background: var(--ds-surface);
  border-right: 1px solid var(--ds-border);
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0; left: 0; bottom: 0;
  z-index: 1040;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.sidebar::-webkit-scrollbar { width: 8px; }
.sidebar::-webkit-scrollbar-thumb { background: var(--ds-border); border-radius: 8px; }
.sidebar::-webkit-scrollbar-thumb:hover { background: var(--ds-border-strong); }
.sidebar svg { stroke-width: 1.8; }

.sidebar-header {
  height: var(--topbar-height);
  flex: none;
  padding: 0 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border-bottom: 1px solid var(--ds-border);
}
.sidebar-brand { display: flex; align-items: center; gap: 11px; color: var(--ds-ink); min-width: 0; text-decoration: none; }
.brand-mark {
  width: 34px; height: 34px; border-radius: 9px; flex: none;
  background: var(--brand-primary); color: var(--on-primary);
  display: grid; place-items: center; font-weight: 700; font-size: 15px; overflow: hidden;
}
.brand-mark img { width: 100%; height: 100%; object-fit: cover; }
.brand-txt { min-width: 0; line-height: 1.2; }
.brand-txt b { font-size: 15px; font-weight: 600; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.brand-txt span { font-size: 11px; color: var(--ds-subtle); font-weight: 500; }
/* The system name is fixed text, not a variable-length user name, and it does not
   fit one 240px-sidebar line in Devanagari at 15px — wrap it instead of clipping
   "किसान सूचीकरण प्रणाली" to an ellipsis. It is the header's only content on
   desktop (the close button is d-lg-none), so the link takes the full width and
   the name centres in it, wrapped lines included. It was shrunk to 13.5px back
   when an office line shared the 60px header; alone it can hold the base 15px,
   whose two wrapped lines come to 37.5px. */
.sidebar-header .brand-txt b { white-space: normal; line-height: 1.25; }
.sidebar-header .sidebar-brand { flex: 1; justify-content: center; }
.sidebar-header .brand-txt { text-align: center; }
.sidebar-toggle { background: none; border: 0; color: var(--ds-muted); cursor: pointer; padding: 4px; display: inline-flex; }

.sidebar-nav { padding: 8px 0 24px; flex: 1; }
/* One type scale for chrome and content. Devanagari has no lowercase: its base
   characters are 0.630em in Mukta — the same as Latin CAP height — while Latin
   lowercase is 0.477em. Nepali content therefore looks ~30% larger than English
   chrome at equal px, and the chrome used to be 1.5-2.5px smaller on top of that,
   which read as two different scales. Equal px removes the half of the mismatch
   that is ours; the rest is the scripts themselves. */
.sidebar-nav .nav-link {
  display: flex; align-items: center; gap: 11px;
  color: var(--ds-muted); font-size: 15px; font-weight: 500;
  border-radius: var(--r-sm); padding: 9px 10px; margin: 2px 10px;
  text-decoration: none; position: relative;
}
.sidebar-nav .nav-link svg { width: 18px; height: 18px; flex: none; }
.sidebar-nav .nav-link:hover { background: var(--ds-bg); color: var(--ds-ink); }
.sidebar-nav .nav-link.active { background: var(--primary-soft); color: var(--brand-primary); font-weight: 600; }
.sidebar-nav .nav-link .badge-count { margin-left: auto; font-size: 11px; font-weight: 700; background: var(--brand-primary); color: #fff; border-radius: 20px; padding: 1px 7px; }
/* watch-only pipeline stages: visibility, not work — count reads quiet */
.sidebar-nav .nav-link.nav-watch { color: var(--ds-muted); font-weight: 400; }
.sidebar-nav .nav-link .badge-count.badge-watch { background: var(--ds-bg); color: var(--ds-muted); border: 1px solid var(--ds-border-strong); font-weight: 600; }

.nav-group-toggle {
  display: flex; align-items: center; gap: 11px; width: calc(100% - 20px);
  color: var(--ds-muted); font-size: 15px; font-weight: 500;
  padding: 9px 10px; margin: 2px 10px; border: 0; background: none;
  border-radius: var(--r-sm); cursor: pointer; text-align: left;
}
.nav-group-toggle:hover { background: var(--ds-bg); color: var(--ds-ink); }
.nav-group-toggle[aria-expanded="true"] { color: var(--ds-ink); font-weight: 600; }
.nav-group-toggle .group-icon { width: 18px; height: 18px; flex: none; }
.nav-group-toggle .chevron { width: 14px; height: 14px; opacity: 0.55; margin-left: auto; transition: transform 0.25s; }
.nav-group-toggle[aria-expanded="true"] .chevron { transform: rotate(180deg); }
.nav-group-items { padding: 0 10px 4px; }
.nav-group-items .nav-link { padding: 7px 10px 7px 39px; margin: 1px 0; font-size: 14px; }
.nav-group-items .nav-link svg { width: 15px; height: 15px; }

.nav-divider { height: 1px; background: var(--ds-border); margin: 8px 16px; }
.nav-section-title { color: var(--ds-subtle); font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 600; padding: 14px 20px 6px; }

.sidebar-backdrop { display: none; position: fixed; inset: 0; background: rgba(11,21,36,0.4); z-index: 1035; }

/* ── main column ─────────────────────────────────────────────────────────── */
.main-content { flex: 1; min-width: 0; margin-left: var(--sidebar-width); display: flex; flex-direction: column; }

.topbar {
  height: var(--topbar-height); flex: none;
  position: sticky; top: 0; z-index: 1030;
  display: flex; align-items: center; gap: 16px; padding: 0 22px;
  background: var(--ds-surface); border-bottom: 1px solid var(--ds-border);
}
.topbar svg { stroke-width: 1.8; }
/* 600, not the 650 this used to be: Mukta is a static family, so an off-step
   weight rounds up to the next vendored cut (700) rather than interpolating
   the way the old variable font did. Pinned to the real cut it will render. */
.topbar-title { font-size: 17px; font-weight: 600; margin: 0; flex: none; }
.topbar-date { display: flex; flex-direction: column; line-height: 1.25; padding-left: 16px; border-left: 1px solid var(--ds-border); }
.topbar-date b { font-size: 12.5px; font-weight: 600; color: var(--ds-ink); }
.topbar-date span { font-size: 11px; color: var(--ds-subtle); }
.topbar-spacer { flex: 1; }
.topbar-right { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* Nothing in this bar yields on its own: .topbar-title is flex:none and both
   the role chip and the account button are white-space:nowrap. Whenever the sum
   of the three exceeds the bar, the overflow has nowhere to go and the PAGE
   scrolls sideways — a long office chip ("स्थानीय तह · भरतपुर महानगरपालिका,
   चितवन, बागमती प्रदेश") did it by 113px at 680px, which is above the 560px
   rule further down that strips the chip and the name back to their icons.
   Shrinkable and truncating instead, so the chip keeps its icon and as much of
   the office name as there is room for. */
.role-chip { min-width: 0; }
.role-chip > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.user-menu > span:not(.avatar) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Which role this session is acting as — किसान / गणक / the office it serves.
   Same background+ink token pairing as `.ds .pill` so the two read as one
   family, but the topbar sits outside `.ds`, so that class cannot be reused. */
.role-chip {
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; padding: 5px 11px 5px 9px;
  font-size: 12.5px; font-weight: 600; line-height: 1.2; white-space: nowrap;
  background: var(--ds-bg); color: var(--ds-muted);
}
.role-chip svg { width: 14px; height: 14px; flex: none; }
.role-chip--farmer { background: var(--inf-bg); color: var(--inf-ink); }
.role-chip--enumerator { background: var(--primary-soft); color: var(--brand-primary); }
.role-chip--office { background: var(--ds-bg); color: var(--ds-muted); }

.clinic-badge {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--primary-soft); color: var(--brand-primary);
  border: 1px solid var(--primary-line); border-radius: var(--r-sm);
  padding: 5px 10px; font-size: 12.5px; font-weight: 600;
}
.clinic-badge svg { width: 15px; height: 15px; }

.user-menu {
  display: inline-flex; align-items: center; gap: 9px;
  border: 1px solid var(--ds-border); border-radius: var(--r-sm);
  background: none; padding: 5px 10px; cursor: pointer; font-size: 13px; font-weight: 500; color: var(--ds-ink);
}
.user-menu:hover { background: var(--ds-bg); }
.avatar { border-radius: 50%; background: var(--primary-soft); color: var(--brand-primary); display: inline-grid; place-items: center; font-weight: 600; flex: none; }
/* The avatar is a <span> with an initial, or an <img> of the uploaded profile
   photo — the img needs the crop, since place-items does nothing for it. */
img.avatar { object-fit: cover; }
.av-lg { width: 30px; height: 30px; font-size: 12px; }
.av-md { width: 30px; height: 30px; font-size: 12px; }

.content-messages { padding: 16px 22px 0; }
/* every message on the page was a toast — don't leave the banner strip's padding behind */
.content-messages:empty { padding: 0; }

/* Floating confirmation for extra_tags="toast" messages (base/shell.html). Fixed
   to the viewport so it is seen wherever the page happens to be scrolled — the
   inline banner at the top of .content-area was being missed after a section
   save on a long form. erp.js fades each one out; the × dismisses early. */
.toast-stack { position: fixed; top: 18px; right: 18px; z-index: 1080; display: flex; flex-direction: column; gap: 10px; pointer-events: none; max-width: min(380px, calc(100vw - 36px)); }
.toastmsg { pointer-events: auto; display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px; border-radius: var(--r-md); background: var(--ds-surface); border: 1px solid #b7e0c7; border-left: 4px solid var(--ok); box-shadow: var(--sh-md); font-size: 13.5px; line-height: 1.5; color: var(--ds-ink); transition: opacity .25s ease, transform .25s ease; }
.toastmsg--bad { border-color: #f0c9c7; border-left-color: var(--bad); }
.toastmsg__ic { flex: 0 0 auto; width: 20px; height: 20px; color: var(--ok); display: grid; place-items: center; }
.toastmsg--bad .toastmsg__ic { color: var(--bad); }
.toastmsg__ic i { width: 18px; height: 18px; }
.toastmsg__x { flex: 0 0 auto; margin-left: auto; border: 0; background: none; cursor: pointer; font-size: 19px; line-height: 1; padding: 0 2px; color: var(--ds-subtle); }
.toastmsg.is-gone { opacity: 0; transform: translateX(12px); }
@media (max-width: 680px) { .toast-stack { top: 12px; right: 12px; left: 12px; max-width: none; } }
@media (prefers-reduced-motion: reduce) { .toastmsg { transition: none; } }

/* Confirmation dialog for standalone pages (the offline-entry PWA), which don't
   extend base/shell.html and so ship neither Bootstrap nor #confirmModal. erp.js
   builds this <dialog> on first use; it carries .ds itself so the .dbtn rules
   below apply to its buttons. Shell pages keep using the Bootstrap modal. */
.confirmbox { border: 1px solid var(--ds-border); border-radius: var(--r-md); box-shadow: var(--sh-md); background: var(--ds-surface); color: var(--ds-ink); padding: 20px 22px 16px; max-width: min(440px, calc(100vw - 32px)); font-size: 14px; line-height: 1.6; }
.confirmbox::backdrop { background: rgba(16, 24, 40, 0.45); }
.confirmbox__msg { margin: 0 0 18px; }
.confirmbox__acts { display: flex; gap: 9px; justify-content: flex-end; flex-wrap: wrap; }
.confirmbox[open] { animation: confirmbox-in .16s ease-out; }
@keyframes confirmbox-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .confirmbox[open] { animation: none; } }
@media (max-width: 480px) { .confirmbox__acts .dbtn { flex: 1 1 100%; justify-content: center; } }

/* अनुदान लेजर. One grid template shared by the header row, every entry row and the
   जम्मा line, so the money column stays aligned down the whole ledger. */
.rows--ledger .trowh,
.rows--ledger .trow { grid-template-columns: 0.9fr 2.4fr 1.3fr 0.9fr 1fr 1.1fr; align-items: start; }
.rows--ledger .trow--total { border-top: 2px solid var(--ds-border-strong); font-size: 14px; }
.ledger__audit { margin-top: 6px; }
.ledger__audit > summary { cursor: pointer; font-size: 12px; }
.ledger__audit > div { margin-top: 4px; padding-left: 10px; font-size: 12px; }
.ledger__acts { margin-top: 8px; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.ledger__acts form { display: flex; gap: 6px; align-items: center; }
.ledger__acts .form-control { width: 190px; }
.ledger__add { margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px dashed var(--ds-border); }
/* the stacked card the .trow becomes on a phone has no header row to align to,
   so the total reads as a labelled line like every other cell */
@media (max-width: 680px) {
  .rows--ledger .trow--total span:empty { display: none; }
  .ledger__acts .form-control { width: 100%; }
}

/* फोटो खिच्नुहोस् — injected by erp.js beside image file inputs on touch devices
   only, so there is no desktop state to style. Block on the narrowest phones: at
   that width it sits under the input rather than beside it, and a 48px full-width
   target is the point. Not scoped under .ds — the offline shell's file inputs live
   outside a .ds container. */
.camera-btn { margin-top: 6px; display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-weight: 600; font-size: 12.5px; padding: 9px 13px; cursor: pointer;
  border-radius: var(--r-sm); border: 1px solid var(--ds-border-strong);
  background: var(--ds-surface); color: var(--ds-ink); }
.camera-btn svg { width: 16px; height: 16px; }
.camera-btn:active { background: var(--ds-bg); }
@media (max-width: 520px) {
  .camera-btn { display: flex; width: 100%; justify-content: center; min-height: 48px; }
}
/* flex-grow so a short page still pushes .gov-appfoot to the bottom of the
   viewport instead of leaving it stranded mid-screen over empty background. */
.content-area { padding: 22px 22px 46px; max-width: 1400px; width: 100%; margin: 0 auto; flex: 1 1 auto; }

/* dropdown menus (Bootstrap) — design language */
.dropdown-menu { font-size: 13px; border: 1px solid var(--ds-border); border-radius: var(--r-md); box-shadow: var(--sh-md); padding: 6px; min-width: 200px; }
.dropdown-menu .dropdown-item { display: flex; align-items: center; gap: 9px; border-radius: var(--r-sm); padding: 8px 10px; font-size: 13px; font-weight: 500; color: var(--ds-ink); }
.dropdown-menu .dropdown-item:hover, .dropdown-menu .dropdown-item:focus { background: var(--ds-bg); color: var(--ds-ink); }
.dropdown-menu .dropdown-item svg { width: 15px; height: 15px; }
.dropdown-menu .dropdown-item-text { font-size: 12px; padding: 6px 10px; color: var(--ds-subtle); }
.dropdown-menu .dropdown-divider { border-color: var(--ds-border); margin: 5px 4px; }
/* A menu entry that ends something (लग आउट). Same danger ink as .dbtn--danger,
   and restated on :hover/:focus because the two rules above reset every item's
   colour there — without that the red would flip back to black on hover, which
   is the moment it matters. Declared after them so equal specificity resolves
   in its favour. #9c2a25 holds ~7.6:1 on white and ~6.9:1 on the hover tint. */
.dropdown-menu .dropdown-item--danger,
.dropdown-menu .dropdown-item--danger:hover,
.dropdown-menu .dropdown-item--danger:focus { color: var(--bad-ink); }
.dropdown-menu .dropdown-item--danger:hover,
.dropdown-menu .dropdown-item--danger:focus { background: var(--bad-bg); }

/* ── responsive: off-canvas sidebar ───────────────────────────────────────── */
@media (max-width: 991.98px) {
  .sidebar { transform: translateX(-100%); transition: transform 0.25s ease; }
  body.sidebar-open .sidebar { transform: translateX(0); }
  body.sidebar-open .sidebar-backdrop { display: block; }
  .main-content { margin-left: 0; }
}

/* ── desktop: collapsed rail ──────────────────────────────────────────────── */
/* The 240px sidebar is a permanent tax on a data-entry app whose forms want the
   width. body.sidebar-rail (set by the header toggle, remembered in
   localStorage, re-applied before first paint by the inline script in
   base/shell.html) shrinks it to an icon rail. Same nav, same links, same
   order — only the labels go, and every one of them survives as a title=
   tooltip. Below 992px the rail is irrelevant: the sidebar is already
   off-canvas there, so the class is scoped to the desktop breakpoint and the
   toggle switches meaning with the viewport (see toggleSidebar in erp.js). */
@media (min-width: 992px) {
  body.sidebar-rail { --sidebar-width: 64px; }
  body.sidebar-rail .brand-txt,
  body.sidebar-rail .nav-link > span:not(.badge-count),
  body.sidebar-rail .nav-group-toggle > span,
  body.sidebar-rail .nav-group-toggle .chevron,
  body.sidebar-rail .nav-section-title { display: none; }
  /* Icons centre in the rail; the 44px hit area is kept, not shrunk with it. */
  body.sidebar-rail .sidebar-nav .nav-link,
  body.sidebar-rail .nav-group-toggle { justify-content: center; gap: 0; padding: 11px 0; margin: 2px 8px; width: calc(100% - 16px); }
  body.sidebar-rail .sidebar-header { justify-content: center; padding: 0 8px; }
  /* A group's children have no room to indent under a hidden parent label —
     clicking the group un-rails the sidebar first (erp.js), which is what
     brings this list back. */
  body.sidebar-rail .nav-group-items { display: none; }
  /* The badge would otherwise be pushed out of the 64px rail by margin-left:auto. */
  body.sidebar-rail .nav-link .badge-count { position: absolute; top: 3px; right: 3px; margin: 0; padding: 0 5px; font-size: 10px; }
  body.sidebar-rail .nav-divider { margin: 8px 12px; }
  .sidebar, .main-content { transition: width 0.18s ease, margin-left 0.18s ease; }
}
@media (min-width: 992px) { .d-lg-none { display: none !important; } }
@media (max-width: 1120px) { .topbar-date { display: none; } }
/* Below the desktop sidebar breakpoint the bar is tight enough that the chip
   has to be capped outright — truncation alone still let it take the width the
   page title and the account button needed. */
@media (max-width: 991.98px) {
  .topbar-title { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .role-chip { max-width: 38vw; }
}
@media (max-width: 680px) { .content-area { padding: 16px 14px 40px; } .content-messages { padding: 14px 14px 0; } }

/* ============================================================================
   Bootstrap bridge — not-yet-migrated Bootstrap pages inherit the design
   language. Scoped to .content-area so shell/modals are untouched.
   ========================================================================== */
/* The exemption list is every component that is ITSELF a link but paints its own
   colours — .kpi/.trow originally, plus .step (the wizard chips' three states)
   and .brkrow/.entrycard (dashboard rows and record cards). This blanket rule is
   several :not()s deep, so it outweighs any plain class selector: without the
   exemption it repaints the whole component brand-green, children included, and
   underlines it on hover. Add a component here when you make a card a link. */
/* .yrpill excluded like every other self-coloured component above it: this rule
   carries nine :not() classes, so it outranks .ds .yrpill--active and painted the
   ACTIVE year's text --brand-primary on its --brand-primary fill — blue on blue,
   which is the year the reader most needs to see. Same failure the :hover rule
   beside .yrpill--active was already fixed for. */
.content-area a:not(.btn):not(.dbtn):not(.dropdown-item):not(.page-link):not(.nav-link):not(.kpi):not(.trow):not(.step):not(.brkrow):not(.entrycard):not(.yrpill) { color: var(--brand-primary); text-decoration: none; font-weight: 600; }
.content-area a:not(.btn):not(.dbtn):not(.dropdown-item):not(.page-link):not(.nav-link):not(.kpi):not(.trow):not(.step):not(.brkrow):not(.entrycard):hover { text-decoration: underline; }
.content-area .card { border: 1px solid var(--ds-border); border-radius: var(--r-md); box-shadow: var(--sh-sm); }
.content-area .card-header { background: transparent; padding: 14px 17px; font-size: 14px; font-weight: 600; color: var(--ds-ink); border-bottom: 1px solid var(--ds-border); }
.content-area .card-body { padding: 17px; }
.content-area .card-body.p-0 { padding: 0; }
.content-area .table { font-size: 13.5px; }
.content-area .table thead th { background: var(--ds-bg); color: var(--ds-subtle); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 0; padding: 11px 17px; }
.content-area .table td { padding: 11px 17px; border-color: var(--ds-border); vertical-align: middle; }
.content-area .table > :not(caption) > * > * { box-shadow: none; }
.content-area .table-hover > tbody > tr:hover > * { background: var(--primary-softer); }
.content-area .btn { font-weight: 600; font-size: 13px; border-radius: var(--r-sm); display: inline-flex; align-items: center; gap: 7px; padding: 8px 13px; }
.content-area .btn svg { width: 15px; height: 15px; }
.content-area .btn-sm { padding: 6px 11px; font-size: 12.5px; }
.content-area .btn-primary { background: var(--brand-primary); border-color: var(--brand-primary); color: var(--on-primary); }
.content-area .btn-primary:hover { background: var(--brand-primary); border-color: var(--brand-primary); filter: brightness(1.09); }
.content-area .btn-outline-primary, .content-area .btn-outline-secondary { color: var(--ds-ink); border-color: var(--ds-border-strong); background: var(--ds-surface); }
.content-area .btn-outline-primary:hover, .content-area .btn-outline-secondary:hover { background: var(--ds-bg); color: var(--ds-ink); border-color: var(--ds-border-strong); }
.content-area .text-primary { color: var(--brand-primary) !important; }
.content-area .form-control, .content-area .form-select { font-size: 13.5px; color: var(--ds-ink); border-color: var(--ds-border-strong); border-radius: var(--r-sm); }
.content-area .form-control:focus, .content-area .form-select:focus { border-color: var(--brand-primary); box-shadow: 0 0 0 3px var(--primary-line); }
.content-area .form-label { font-size: 12px; font-weight: 600; color: var(--ds-muted); }
.content-area .alert { border-radius: var(--r-sm); }

/* ============================================================================
   .ds — design-system components (scoped under .ds). Renames vs Bootstrap:
   .card→.panel, .btn→.dbtn, .row→.trow, .alert→.callout.
   ========================================================================== */
.ds { color: var(--ds-ink); font-size: 15px; line-height: 1.5; }
.ds h1, .ds h2, .ds h3, .ds h4 { margin: 0; font-weight: 600; letter-spacing: -0.01em; }
.ds .ic { width: 18px; height: 18px; stroke-width: 1.8; flex: none; }
.ds .ic-sm { width: 15px; height: 15px; stroke-width: 1.9; flex: none; }
.ds .ic-lg { width: 22px; height: 22px; stroke-width: 1.7; flex: none; }

.ds .pagehead { display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; margin-bottom: 12px; flex-wrap: wrap; }
.ds .pagehead h1 { font-size: 19px; }
.ds .pagehead p { margin: 3px 0 0; color: var(--ds-muted); font-size: 13px; }
.ds .qa { display: flex; gap: 9px; flex-wrap: wrap; }
/* ── the chip groups that sit inside a .filterbar ───────────────────────────
   सबै / त्रुटि भएका / त्रुटि नभएका on the प्रमाणीकरण queue, and the
   पूर्ण–स्वीकृत–अस्वीकृत–अपूर्ण विवरण counts on किसान विवरण. They live in the
   filter bar but are not filter FIELDS: they are one row of toggles that reads
   as a unit, so they opt out of the bar's 190px column basis and stay on one
   line.

   This was nine copies of the same inline style, and `flex-wrap: nowrap` in an
   inline style is unreachable from a media query — which is exactly why these
   chips ran off the side of a phone instead of wrapping. As a class the desktop
   intent survives and the narrow case can override it. */
.ds .filterbar > .qa--inline,
.ds .qa--inline { flex: 0 0 auto; max-width: none; align-self: center; flex-wrap: nowrap; }
.ds .filterbar > .qa--end, .ds .qa--end { align-self: flex-end; margin-left: auto; }
/* On a phone these are four-to-six chips of Devanagari with a count each; one
   line was never going to hold them. They take the full width of the bar and
   wrap within it, which is what every other control there already does. */
@media (max-width: 768px) {
  .ds .filterbar > .qa--inline,
  .ds .qa--inline { flex: 1 1 100%; flex-wrap: wrap; align-self: stretch; }
  .ds .filterbar > .qa--end, .ds .qa--end { margin-left: 0; align-self: stretch; }
}
/* Page actions stay right even when the head has nothing to their left —
   the heading itself lives in the topbar, so some heads are actions only. */
.ds .pagehead .qa { margin-left: auto; }

.ds .dbtn { display: inline-flex; align-items: center; gap: 8px; font: inherit; font-weight: 600; font-size: 13px; padding: 9px 13px; border-radius: var(--r-sm); border: 1px solid var(--ds-border); background: var(--ds-surface); color: var(--ds-ink); cursor: pointer; white-space: nowrap; text-decoration: none; }
.ds .dbtn:hover { background: var(--ds-bg); color: var(--ds-ink); }
/* flex: none is load-bearing, not tidiness. A .dbtn is a flex container and an
   svg is a shrinkable flex item, so in a narrow grid cell the icon gives up its
   width before the padding does. In the प्रमाणीकरण register's 0.4fr छाप्नुहोस्
   column the printer came out 2px wide — present, clickable, and invisible. */
.ds .dbtn svg { width: 16px; height: 16px; flex: none; }

/* An icon-only button. Without a label there is no text to hold the box open, so
   it needs a floor of its own: square, and big enough to read as a control
   rather than as a decoration sitting next to one. 36px on a mouse, the full 44
   on touch — see the touch-target block further down. */
.ds .dbtn--icon { padding: 0; min-width: 36px; min-height: 36px; justify-content: center; gap: 0; }
.ds .dbtn--icon svg { width: 18px; height: 18px; }
.ds .dbtn--primary { background: var(--brand-primary); color: var(--on-primary); border-color: var(--brand-primary); }
.ds .dbtn--primary:hover { filter: brightness(1.09); background: var(--brand-primary); color: var(--on-primary); }
.ds .dbtn--accent { background: var(--brand-accent); color: var(--on-accent); border-color: var(--brand-accent); }
.ds .dbtn--sm { padding: 7px 11px; font-size: 12.5px; }
/* Row action that keeps its label whole: the icon takes the line ABOVE the
   word instead of the width beside it, so a narrow कार्य column splits the
   button in two rather than splitting हेर्नुहोस् in two. */
.ds .dbtn--stack { flex-direction: column; gap: 3px; line-height: 1.25; padding: 6px 10px; }
.ds .dbtn--stack svg { width: 14px; height: 14px; }
.ds .dbtn--danger { color: var(--bad-ink); border-color: var(--ds-border-strong); }
.ds .dbtn--danger:hover { background: var(--bad-bg); }
.ds .dbtn--good { color: var(--ok-ink); border-color: var(--ds-border-strong); }
.ds .dbtn--good:hover { background: var(--ok-bg); }
/* Inside a निर्णय panel the forward action is FILLED green (2026-09-04 client):
   प्रमाणित गरी स्थानीय तह पठाउनुहोस् is the one thing that page exists to do, and
   as a pale outline button it sat at the same weight as the फिर्ता beside it.
   Scoped to .factions--decide, not to .dbtn--good at large — the same class marks
   ordinary "पेश गर्नुहोस्" buttons on forms all over the app, which are not the
   destination of a page and should stay outlines.
   --ok is too light to carry white text (≈3.5:1); --ok-ink clears AA at ~6.5:1
   and still reads unmistakably green. Bigger than a .dbtn too (15px/46px vs
   13px/38px): it is the last control on a long record page, so it has to be
   findable after a full scroll and comfortable to hit on a phone. */
.ds .factions--decide .dbtn--good { background: var(--ok-ink); color: #fff; border-color: var(--ok-ink);
                                    font-size: 15px; padding: 13px 22px; min-height: 46px; }
.ds .factions--decide .dbtn--good:hover { background: var(--ok-ink); color: #fff; filter: brightness(1.12); }
.ds .factions--decide .dbtn--good svg { width: 18px; height: 18px; }
/* The वडा's bare button (no निर्णय panel) — drop the sticky gradient band. */
.ds .factions--bare { position: static; background: none; padding: 0; }

/* KPI cards */
.ds .kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; }
.ds .kpi { background: var(--ds-surface); border: 1px solid var(--ds-border); border-radius: var(--r-md); box-shadow: var(--sh-sm); padding: 17px 17px 15px; position: relative; overflow: hidden; }
/* Tiles that link through to a filtered list (see dashboard_overview.html /
   dashboard_enumerator.html) — same box, but as an <a> so it needs its own
   reset plus a hover cue proving it's clickable, unlike a plain display box. */
.ds a.kpi { display: block; text-decoration: none; color: inherit; transition: box-shadow 0.15s, transform 0.15s; }
.ds a.kpi:hover { box-shadow: var(--sh-md); transform: translateY(-1px); }
.ds .kpi__top { display: flex; align-items: center; gap: 9px; color: var(--ds-muted); font-size: 12.5px; font-weight: 600; }
.ds .kpi__ic { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; flex: none; }
.ds .kpi__ic svg { width: 17px; height: 17px; }
.ds .kpi__val { font-size: 29px; font-weight: 700; letter-spacing: -0.02em; margin: 12px 0 2px; line-height: 1; }
.ds .kpi__val .u { font-size: 16px; color: var(--ds-muted); font-weight: 600; }
.ds .kpi__sub { font-size: 12.5px; color: var(--ds-muted); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ds .meter { height: 6px; border-radius: 6px; background: var(--ds-border); overflow: hidden; margin: 12px 0 8px; }
.ds .meter > span { display: block; height: 100%; background: var(--brand-primary); border-radius: 6px; }
.ds .kpi--danger { border-color: #f0c9c7; background: linear-gradient(var(--bad-bg), var(--ds-surface) 62%); }
.ds .kpi--danger .kpi__ic { background: var(--bad-bg); color: var(--bad); }
.ds .kpi--warn { border-color: #ecd6ac; background: linear-gradient(var(--warn-bg), var(--ds-surface) 66%); }
.ds .kpi--warn .kpi__ic { background: var(--warn-bg); color: var(--warn); }
.ds .kpi--good .kpi__ic { background: var(--ok-bg); color: var(--ok); }
.ds .kpi--brand .kpi__ic { background: var(--primary-soft); color: var(--brand-primary); }
.ds .kpi--info .kpi__ic { background: var(--inf-bg); color: var(--inf); }

/* secondary stat strip */
.ds .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 15px; }
.ds .stat { background: var(--ds-surface); border: 1px solid var(--ds-border); border-radius: var(--r-md); box-shadow: var(--sh-sm); padding: 13px 15px; display: flex; align-items: center; gap: 12px; }
.ds .stat__ic { width: 34px; height: 34px; border-radius: 9px; background: var(--ds-bg); color: var(--ds-muted); display: grid; place-items: center; flex: none; }
.ds .stat b { font-size: 19px; font-weight: 700; display: block; line-height: 1.1; }
.ds .stat span { font-size: 12px; color: var(--ds-muted); }

/* Profile-details "real card" look (templates/web/farmer/detail.html) — mirrors
   the printable Kisan Card's own typography (dotted-underline values, photo
   inset — see the .dotted convention in templates/web/farmer/card.html) so a
   farmer's summary reads like an ID document instead of a plain label/value grid. */
.ds .idcard { border: 1.5px solid var(--ds-border-strong); border-radius: var(--r-md); padding: 16px 20px; background: var(--ds-surface); }
.ds .idcard__top { display: flex; align-items: center; gap: 14px; padding-bottom: 12px; margin-bottom: 12px; border-bottom: 1px solid var(--ds-border); }
.ds .idcard__photo { width: 64px; height: 72px; object-fit: cover; border: 1px solid var(--ds-border-strong); border-radius: 4px; background: var(--ds-bg); flex: none; }
.ds .idcard__photo--empty { display: flex; align-items: center; justify-content: center; color: var(--ds-muted); }
.ds .idcard__name { font-size: 16px; font-weight: 700; }
.ds .idcard .fields { font-size: 13px; line-height: 2.3; }
.ds .idcard .fields b { font-weight: 600; margin-right: 4px; }
.ds .idcard .fields .pair { display: inline-block; min-width: 48%; }
.ds .dotted { border-bottom: 1px dotted var(--ds-muted); padding: 0 6px 1px; font-weight: 600; }

/* Read-back pairs — the label/value sheets that अन्तिम जाँच (farmer/review.html)
   and the record page print section by section.
   .fields above is the परिचय पत्र idiom: "label: value" on one dotted line, sized
   for a card 86mm wide. Reused in a full-width panel it has no column structure at
   all, so twenty fields came out as twenty full-width lines with the values landing
   at twenty different x-positions — nothing for the eye to run down, and most of
   the panel empty. This is the same content as a proper sheet: auto-fit tracks, so
   a wide panel gets two or three columns and a narrow one (the record page renders
   its sections two-up) silently gets one, without the template knowing which.
   360px, not 300: at 300 a desktop panel fitted three tracks and the labels — Nepali
   phrases like "कति महिना कृषि कार्यमा संलग्न" — wrapped to two lines in every one of
   them. Two roomy columns read faster than three cramped ones; a very wide screen
   still gets the third. And the min() is what keeps a single track from demanding
   360px inside a panel narrower than that, which is the difference between a phone
   stacking and a phone scrolling sideways. */
.ds .pairgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr)); gap: 0 30px; margin: 0; }
.ds .pairgrid > div { display: grid; grid-template-columns: minmax(0, 42%) minmax(0, 1fr); gap: 10px;
                      align-items: baseline; padding: 8px 0; border-bottom: 1px dashed var(--ds-border);
                      font-size: 13px; min-width: 0; }
.ds .pairgrid dt { color: var(--ds-muted); font-weight: 500; margin: 0; }
/* overflow-wrap, because these values are unbroken strings a user typed — a
   ठेगाना or a NID number must wrap inside its cell rather than widen the track. */
.ds .pairgrid dd { font-weight: 600; margin: 0; min-width: 0; overflow-wrap: anywhere; }
/* An unfilled field is worth showing (it says what was skipped) but not worth
   reading first, so the — recedes and the answers keep the weight. */
.ds .pairgrid dd.is-empty { color: var(--ds-subtle); font-weight: 500; }
@media (max-width: 560px) {
  .ds .pairgrid > div { grid-template-columns: 1fr; gap: 2px; }
  .ds .pairgrid dt { font-size: 12px; }
}
/* The same card inside the narrow profile aside (farmer/detail.html): photo
   centred above the name, and every label/value pair on its own line — the
   two-per-line flow of the wide layout has no room in a 340px column, and the
   680px viewport breakpoint that normally unstacks them never fires here
   because the VIEWPORT is wide even when this column is not. */
.ds .idcard--stacked { padding: 18px 16px; }
.ds .idcard--stacked .idcard__top { flex-direction: column; text-align: center; gap: 10px; }
.ds .idcard--stacked .idcard__photo { width: 96px; height: 108px; }
.ds .idcard--stacked .fields { line-height: 2; }
.ds .idcard--stacked .fields .pair { min-width: 100%; }

/* Dashboard "Holdings summary" — land/livestock/production side by side, each
   its own header + stat tiles. A divider replaces the plain flat text rows'
   lack of any visual boundary between the three groups (dashboard_overview.html). */
.ds .holdgroups { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 22px; }
.ds .holdgroup { padding-left: 20px; border-left: 1px solid var(--ds-border); }
.ds .holdgroup:first-child { padding-left: 0; border-left: 0; }
.ds .holdgroup__hd { display: flex; align-items: baseline; gap: 7px; font-weight: 600; font-size: 13.5px; color: var(--ds-ink); margin-bottom: 12px; }
.ds .holdgroup__hd svg { width: 15px; height: 15px; color: var(--ds-muted); }
.ds .holdgroup__hd span { font-weight: 400; font-size: 12px; color: var(--ds-muted); }
.ds .holdgroup .stats { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); margin-top: 0; }
.ds .holdgroup .stat { padding: 11px 12px; box-shadow: none; background: var(--ds-bg); border-color: transparent; }
.ds .holdgroup .stat__ic { width: 28px; height: 28px; }
.ds .holdgroup .stat__ic svg { width: 15px; height: 15px; }
.ds .holdgroup .stat b { font-size: 16px; }
/* Stacked on a narrow screen the left rule reads as an indent, not a divider —
   the boundary moves to the top edge so the three groups stay distinguishable. */
@media (max-width: 760px) {
  .ds .holdgroups { gap: 16px; }
  .ds .holdgroup { padding-left: 0; border-left: 0; padding-top: 16px; border-top: 1px solid var(--ds-border); }
  .ds .holdgroup:first-child { padding-top: 0; border-top: 0; }
}

/* Dashboard breakdown list — one tappable row per status/category: label, a
   share bar and the count. Replaces the two-column .trow, which on a phone
   collapsed to a label with a right-aligned number and nothing tying the two
   together, in a hit area too small to aim at. The bar is the shared .meter,
   scaled against the largest row so the shape of the distribution is readable
   without doing arithmetic. */
.ds .brkrow { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
              gap: 6px 14px; min-height: 52px; padding: 9px 12px; border-radius: var(--r-sm);
              text-decoration: none; color: inherit; }
.ds a.brkrow:hover { background: var(--ds-bg); }
.ds .brkrow__n { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; text-align: right; }
.ds .brkrow .meter { grid-column: 1 / -1; height: 5px; margin: 0; }

/* Card-per-record list rows (the enumerator dashboard's recent entries): the
   record's identity leads, its state sits at the end, and the whole card is one
   thumb-sized link to the farmer. On a phone the state wraps to its own line
   instead of squeezing the name to an ellipsis. */
.ds a.entrycard { display: block; text-decoration: none; color: inherit; }
.ds a.entrycard:hover { background: var(--ds-bg); border-color: var(--ds-border-strong); }
.ds .entryrow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ds .entryrow__main { flex: 1 1 170px; min-width: 0; }
.ds .entryrow__end { flex: none; margin-left: auto; display: flex; align-items: center; gap: 12px; }
/* Tighter spacing when the cards are a checklist rather than a feed of records
   (farmer/detail.html's "forms still to fill"), so all eight fit one screen. */
.ds .cardlist--tight { gap: 8px; }
.ds .cardlist--tight .entrycard { padding: 10px 12px; }
/* The numbered badge doubles as the legacy screen's ✓/✗ column: green once the
   section is saved, red while it is still outstanding, so the list can be
   scanned for what is left without reading a single label. */
.ds .entrycard--done .entrycard__no { background: var(--ok-bg); color: var(--ok-ink); }
.ds .entrycard--todo .entrycard__no { background: var(--bad-bg); color: var(--bad-ink); }

/* layout grid */
.ds .grid { display: grid; grid-template-columns: 1.7fr 1fr; gap: 15px; margin-top: 15px; align-items: start; }
.ds .grid--half { grid-template-columns: 1fr 1fr; }
/* Profile aside on the LEFT, the wide working column on the right — the shape
   of the legacy किसान profile screen (identity card beside the checklist of
   forms still to fill). A fixed left track, not a fraction: the identity card
   has a natural width and should not stretch with the viewport. Collapses to
   one column at 1120px via the .ds .grid rule in the responsive block below,
   which is why the width lives in a class and not an inline style — an inline
   grid-template-columns would outrank the media query and never collapse. */
.ds .grid--profile { grid-template-columns: 340px minmax(0, 1fr); }
.ds .col { display: grid; gap: 15px; min-width: 0; }

/* panel */
.ds .panel { background: var(--ds-surface); border: 1px solid var(--ds-border); border-radius: var(--r-md); box-shadow: var(--sh-sm); }
.ds .panel__hd { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--ds-border); }
.ds .panel__hd h3 { font-size: 14px; }
.ds .panel__hd .sub { font-size: 12px; color: var(--ds-subtle); }

/* A .panel whose body collapses — <details class="panel disclose"> with the
   .panel__hd as its <summary>. The header keeps its normal look and gains a
   twisty; the native marker is removed on both engines. Closed by default, so
   the panel costs one line until someone asks for it. */
.ds .disclose > summary { cursor: pointer; list-style: none; user-select: none; }
.ds .disclose > summary::-webkit-details-marker { display: none; }
.ds .disclose > summary:hover { background: var(--ds-bg); }
.ds .disclose > summary:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: -2px; }
/* border-bottom belongs to the open state only — closed, the panel is just a bar */
.ds .disclose:not([open]) > summary { border-bottom: 0; }
/* the twisty sits before the title, so the title must not be pushed to the far
   edge by the header's space-between; give it the remaining room instead */
.ds .disclose > summary h3 { flex: 1 1 auto; }
.ds .disclose__tw { display: inline-flex; flex: none; color: var(--ds-muted); transition: transform .15s ease; }
.ds .disclose__tw svg { width: 16px; height: 16px; }
.ds .disclose[open] > summary .disclose__tw { transform: rotate(90deg); }
/* A header whose title carries a second explanatory line: the two are grouped
   in one flex child, so `justify-content: space-between` still puts the
   header's action button hard right instead of spreading three items evenly. */
.ds .panel__hd > div > .sub { display: block; margin-top: 2px; }
.ds .panel__bd { padding: 12px 14px; }
/* Chart.js needs an ancestor with a fixed height when maintainAspectRatio is
   off (see static/js/reports.js) — the canvas otherwise grows unbounded. */
/* The bar chart that sits above a dashboard's summary tiles. .panel carries no
   margin of its own — every other use supplies one inline — so without this the
   chart butts straight into the .kpis grid below it. Matches the 15px the tile
   grid uses between its own cards, so the chart reads as one more row of it. */
.ds .chart-panel { margin-bottom: 15px; }
.ds .chart-box { position: relative; height: 280px; }
/* ड्यासबोर्ड's certification chart: four Devanagari category labels along the
   x-axis plus value labels above the bars need more vertical room than the
   report charts, which carry their categories on the y-axis or in a legend. */
.ds .chart-box--tall { height: 360px; }

/* Count chips under the certification chart — the legacy dashboard's coloured
   row, restated. An <a> and a <span> both use this, so the reset is explicit. */
.ds .statchips { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }
.ds .statchip {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 8px 10px 8px 14px; border-radius: var(--r-sm);
  font-size: 13px; font-weight: 600; text-decoration: none;
  border: 1px solid transparent; color: var(--ds-ink);
}
.ds a.statchip:hover { border-color: currentColor; }
.ds .statchip__n {
  display: inline-grid; place-items: center; min-width: 26px;
  padding: 2px 8px; border-radius: 20px; font-weight: 700;
  background: rgba(255, 255, 255, 0.72); color: inherit;
}
.ds .statchip--warn { background: var(--warn-bg); color: var(--warn-ink); }
.ds .statchip--good { background: var(--ok-bg); color: var(--ok-ink); }
.ds .statchip--danger { background: var(--bad-bg); color: var(--bad-ink); }
.ds .statchip--info { background: var(--inf-bg); color: var(--inf-ink); }
.ds .statchip--muted { background: var(--ds-bg); color: var(--ds-muted); }
/* Share printed beside a chip's count. Deliberately quieter than the count: the
   count is the answer, the share is the context for it. */
.ds .statchip__pct { font-size: 11.5px; font-weight: 600; opacity: 0.75; margin-left: -3px; }

/* Custom KPI Card grid matching user request */
.ds .kpi-grid-custom {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
@media (max-width: 1100px) {
  .ds .kpi-grid-custom { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 580px) {
  .ds .kpi-grid-custom { grid-template-columns: 1fr; }
}

.ds .kpi-card-custom {
  display: block;
  background: var(--ds-surface);
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  padding: 16px 20px;
  text-decoration: none;
  color: var(--ds-ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.ds a.kpi-card-custom:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.ds .kpi-card-custom__lbl {
  font-size: 13px;
  font-weight: 500;
  color: #475569;
  margin-bottom: 8px;
}
.ds .kpi-card-custom__val {
  font-size: 28px;
  font-weight: 700;
  color: #0f172a;
  line-height: 1.1;
  margin-bottom: 6px;
}
.ds .kpi-card-custom__sub {
  font-size: 12px;
  color: #64748b;
}

.ds .kpi-card-custom--danger {
  border-color: #fecdd3;
  background: linear-gradient(180deg, #fff1f2 0%, #ffffff 50%);
}
.ds .kpi-card-custom--warn {
  border-color: #fef08a;
  background: linear-gradient(180deg, #fefce8 0%, #ffffff 50%);
}

/* Chart key — the ड्यासबोर्ड certification chart's replacement for the Chart.js
   legend, which is switched off in reports.js.

   Chart.js draws a one-dataset legend from backgroundColor[0], so a chart whose
   bars are individually coloured gets a legend that names the whole series after
   the FIRST bar's colour. On this chart that read as "gold = total households,
   and the other three colours are other quantities", when in fact all four bars
   count किसान परिवार and differ only by status.

   This key has one entry per BAR instead, so a swatch can only ever stand for
   the bucket it actually paints, and each entry carries the sentence saying which
   households the bucket holds.

   The swatch colours are the reports.js TONE values verbatim, NOT the softer
   --*-bg pill tints: a key whose colours only approximate the bars is a second
   way to mislead. Keep the two lists in step. */
.ds .chartkey {
  list-style: none; margin: 4px 0 0; padding: 14px 0 0;
  border-top: 1px solid var(--ds-border);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 4px 18px;
}
.ds .chartkey__item { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
                      align-items: start; gap: 10px; padding: 7px 2px; }
.ds .chartkey__sw { width: 12px; height: 12px; border-radius: 3px; margin-top: 4px; flex: none; }
/* reports.js: TONE.warn / .good / .danger / .info */
.ds .chartkey__sw--warn { background: #c9a227; }
.ds .chartkey__sw--good { background: #3f8b63; }
.ds .chartkey__sw--danger { background: #b4553f; }
.ds .chartkey__sw--info { background: #5f7a9e; }
.ds .chartkey__sw--brand { background: #2f6f4f; }
.ds .chartkey__sw--muted { background: #7fb69b; }
.ds .chartkey__txt b { display: block; font-size: 13px; font-weight: 600; line-height: 1.35; }
.ds .chartkey__txt span { display: block; font-size: 12px; color: var(--ds-muted); line-height: 1.45; margin-top: 1px; }
.ds .chartkey__n { text-align: right; font-weight: 700; font-size: 15px;
                   font-variant-numeric: tabular-nums; line-height: 1.2; }
.ds .chartkey__n small { display: block; font-size: 11.5px; font-weight: 600; color: var(--ds-muted); }

/* Second line on a breakdown row: names the stage's meaning under its pill, so
   "वडा प्रमाणीकरण" does not have to be decoded from the pill alone. */
.ds .brkrow__hint { display: block; font-size: 12px; color: var(--ds-muted); margin-top: 3px; }
.ds .croplist {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 6px 16px;
  margin: 0; padding-left: 22px; font-size: 13.5px;
}
.ds .croplist li { padding: 2px 0; }
.ds .croplist label { font-weight: normal; }
.ds .link { color: var(--brand-primary); font-weight: 600; font-size: 12.5px; text-decoration: none; display: inline-flex; align-items: center; gap: 3px; cursor: pointer; }
.ds .link:hover { text-decoration: underline; }
.ds .chartwrap { position: relative; height: 230px; }
.ds .chartwrap.sm { height: 190px; }

/* pills */
/* an unstyled button that reads as a clickable value (LU figure, त्रुटि badges) */
.ds .linklike { background: none; border: none; padding: 0; cursor: pointer; font: inherit; color: var(--brand-primary); display: inline-flex; align-items: center; gap: 4px; }
.ds .linklike:hover { text-decoration: underline; }
.ds .pill { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 600; padding: 3px 9px; border-radius: 20px; white-space: nowrap; }
.ds .pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: 0.9; }
.ds .pill--info { background: var(--inf-bg); color: var(--inf-ink); }
.ds .pill--warn, .ds .pill--pending, .ds .pill--submitted,
.ds .pill--pending_ward, .ds .pill--pending_section, .ds .pill--pending_executive,
.ds .pill--pending_central, .ds .pill--appeal { background: var(--warn-bg); color: var(--warn-ink); }
.ds .pill--brand { background: var(--primary-soft); color: var(--brand-primary); }
.ds .pill--good, .ds .pill--approved, .ds .pill--verified { background: var(--ok-bg); color: var(--ok-ink); }
.ds .pill--danger, .ds .pill--rejected, .ds .pill--returned { background: var(--bad-bg); color: var(--bad-ink); }
.ds .pill--muted, .ds .pill--draft { background: var(--ds-bg); color: var(--ds-muted); }

/* rows table */
.ds .rows { display: flex; flex-direction: column; }
.ds .trowh, .ds .trow { display: grid; align-items: center; gap: 12px; padding: 11px 17px; }
.ds .trowh > *, .ds .trow > * { min-width: 0; overflow-wrap: anywhere; }
.ds .trowh { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ds-subtle); font-weight: 600; background: var(--ds-bg); }
.ds .trow { border-bottom: 1px solid var(--ds-border); }
.ds .trow:last-child { border-bottom: 0; }
.ds .trow b { font-weight: 600; font-size: 13.5px; }
/* A .trow that is itself the link (e.g. dashboard status breakdown → filtered
   list), rather than just containing one — needs the anchor reset plus a
   hover cue, unlike the plain display rows this class is normally used for. */
.ds a.trow { text-decoration: none; color: inherit; }
.ds a.trow:hover { background: var(--ds-bg); }
.ds .cellmuted { color: var(--ds-muted); font-size: 13px; }
/* नागरिकता scans inline on the profile — thumbnails that open the full page in
   the lightbox, sized so two sit beside the number they were read off. */
/* "आज: २०८२ भदौ ५" beside the wizard's आर्थिक वर्ष pills — the date the entry is
   being made on, muted so it reads as context, not as a second choice. The row is
   centre-justified, so this rides along after the pills instead of being pushed
   to an edge, with a separator that disappears when the row wraps. */
.ds .wizard__today { font-size: 12.5px; color: var(--ds-muted); white-space: nowrap; }
.ds .wizard__today::before { content: "·"; margin-right: 6px; color: var(--ds-border-strong, #c9ced6); }

.ds .docthumbs { display: inline-flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.ds .docthumb { width: 84px; height: 56px; object-fit: cover; border-radius: 6px; border: 1px solid var(--ds-border); cursor: zoom-in; }
/* Free-text cell (the अद्यावधिक अनुरोध कैफियत): two lines, then an ellipsis —
   the full text is on the cell's title and on the detail page. */
.ds .cellclamp { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
.ds .u-name { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ds .u-name b { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds .u-name span { font-size: 11.5px; color: var(--ds-subtle); }

/* .rows--ruled — the legacy register look, shared by किसान विवरण and the
   प्रमाणीकरण queue. Carries only the TREATMENT; each list keeps its own
   grid-template-columns, because they do not have the same columns (and the
   queue's set changes with the stage and the bulk checkbox).

   NO min-width, deliberately, and that is the whole point of the class. Both
   lists used to declare one — 1220px here, 1500px inline on the queue — which is
   why they scrolled sideways on a 1366px laptop while the legacy screens they
   replace fit thirteen columns on the same monitor. The legacy table
   (staffbackend/digitalKishan/wardwise-approved-index.blade.php) sets percentage
   widths and nothing else: Bootstrap 3's .table-responsive only scrolls under
   768px, so on a desktop the table simply squeezed and the CELLS WRAPPED —
   "मिश्रित (कृषि र पशु)" over two lines rather than one long row nobody can read
   without dragging. A declared floor could never do that; it forced the scrollbar
   regardless of how little the content actually needed.

   So: fr columns that shrink, cells that wrap (the shared .trowh/.trow rule
   already sets overflow-wrap: anywhere), and .tablescroll kept as the safety net
   for a genuinely narrow window rather than as the normal case.

   Every selector below uses the CHILD combinator. The queue nests a .trow inside
   a cell (the त्रुटि modal's contents), and a descendant selector would rule and
   centre that too. */
.ds .tablescroll { overflow-x: auto; }
.ds .rows--ruled > .trowh, .ds .rows--ruled > .trow {
  align-items: stretch;
  gap: 0;
  padding: 0;
}
.ds .rows--ruled > .trowh > *, .ds .rows--ruled > .trow > * {
  padding: 8px 9px;
  text-align: center;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* The legacy table is ruled on every cell, not just between rows — with this
     many columns of wrapped text the vertical rules are what keep the eye on one
     record. */
  border-right: 1px solid var(--ds-border);
}
.ds .rows--ruled > .trowh > *:last-child,
.ds .rows--ruled > .trow > *:last-child { border-right: 0; }
/* Solid header bar, as the legacy .kelvin row had it (#428bca on white). Uses the
   brand token rather than that literal so it stays one blue with the rest of the
   system, and drops the uppercase/letter-spacing the default .trowh applies —
   both are no-ops on Devanagari and the tracking pulls conjuncts apart. */
.ds .rows--ruled > .trowh {
  background: var(--brand-primary);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.35;
}
.ds .rows--ruled > .trowh > * { border-right-color: rgba(255, 255, 255, 0.25); }
.ds .rows--ruled > .trow { font-size: 12.5px; line-height: 1.45; }
/* The नाम column reads as a name, not as a centred data cell. Second child on
   किसान विवरण, third on the queue when the bulk checkbox takes the first slot —
   so it is keyed off .u-name, which is the name cell on both. */
.ds .rows--ruled > .trow > .u-name { text-align: left; flex-direction: row; align-items: center; }
/* The bulk-select tick sits in a 32px column, and the 9px cell padding either side
   would leave 14px for a 16px checkbox — enough to clip it or push it out of the
   column. It needs the row rules and the centring, not the padding. */
.ds .rows--ruled > .trowh > .bulkcell,
.ds .rows--ruled > .trow > .bulkcell { padding-left: 0; padding-right: 0; align-items: center; }
/* A pill inside a ruled cell has to WRAP. .pill is white-space: nowrap by design —
   it is a chip, and a chip that breaks reads as broken — but that assumes a column
   wide enough to hold it. वर्ग is three words ("मझौला व्यावसायिक किसान", ~140px at
   11.5px) in a column sized around 95px, so the nowrap chip simply ran out through
   the cell's right-hand rule and sat on top of त्रुटि.

   Wrapping on WORD boundaries, not anywhere: the generic .trow cell rule sets
   overflow-wrap: anywhere, which inside a pill would hyphenate a Devanagari word
   mid-conjunct. The row gets taller instead, which is what the legacy table did. */
.ds .rows--ruled > .trow > .pill,
.ds .rows--ruled > .trow > * > .pill {
  white-space: normal;
  overflow-wrap: normal;
  max-width: 100%;
  text-align: center;
  border-radius: 10px;
}
/* the leading dot must not be squeezed out when the label takes the rest */
.ds .rows--ruled > .trow > .pill::before,
.ds .rows--ruled > .trow > * > .pill::before { flex: 0 0 auto; }

/* ── nothing in a ruled cell may refuse to shrink ────────────────────────────
   Removing the min-width was necessary but not sufficient: a grid track can be
   told to shrink and the CONTENT still spills, and visible overflow counts
   toward .tablescroll's scrollable area, so the bar came back anyway. Three
   things were doing it, all of them nowrap or fixed-size by design elsewhere:

     .dbtn          — white-space: nowrap, so a कार्य button's label set the
                      column's real floor no matter what the fr said
     .u-name b/a    — the farmer's name, nowrap with an ellipsis
     .avatar        — flex: none at 30px, plus a 10px gap, off the top of the
                      name column's width before a single letter was drawn

   Overridden HERE rather than at source because nowrap is right everywhere else
   — a button that breaks mid-label reads as broken. Inside these registers the
   row gets taller instead, which is what the legacy table did. */
.ds .rows--ruled > .trow .dbtn {
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: center;
  min-width: 0;
  padding-left: 6px;
  padding-right: 6px;
}
/* ...except an icon button, whose whole content is the icon: there is no text to
   re-wrap, so shrinking it just makes the control smaller until it disappears. */
.ds .rows--ruled > .trow .dbtn--icon {
  min-width: 36px;
  padding-left: 0;
  padding-right: 0;
}
.ds .rows--ruled > .trow .u-name { gap: 6px; }
.ds .rows--ruled > .trow .u-name b,
.ds .rows--ruled > .trow .u-name a { white-space: normal; overflow: visible; text-overflow: clip; }
.ds .rows--ruled > .trow .u-name > span { min-width: 0; }
/* the avatar is decoration; at these widths it is 30px the name cannot have */
.ds .rows--ruled > .trow .u-name .avatar { display: none; }
/* `1fr` is minmax(auto, 1fr), and an `auto` minimum resolves to min-content —
   unless the ITEM overrides it, which is what min-width: 0 here does. Restated
   rather than left to the shared .trow rule because it is load-bearing now: drop
   it and every track silently reacquires a min-content floor. overflow: hidden is
   the backstop, and with the wrapping above it should never actually clip. */
.ds .rows--ruled > .trowh > *, .ds .rows--ruled > .trow > * { min-width: 0; overflow: hidden; }
/* Headings break at SPACES, not anywhere. The shared cell rule sets
   overflow-wrap: anywhere, which is right for a citizenship number in a narrow
   column and wrong for a heading — "पशु (LU)" came out with the closing bracket
   alone on the second line. Every heading here has a space to break at. */
.ds .rows--ruled > .trowh > * { overflow-wrap: normal; }

/* No sticky कार्य column on a register that does not scroll. It exists to keep
   the buttons reachable while the rest slides under them; with everything on
   screen it is dead weight, and its -17px margin was itself adding width. */
.ds .tablescroll .rows--ruled > .trowh > *:last-child,
.ds .tablescroll .rows--ruled > .trow > *:last-child {
  position: static;
  margin-right: 0;
  padding-right: 9px;
  padding-left: 9px;
  box-shadow: none;
}

/* किसान विवरण's nine columns. */
.ds .rows--wide > .trowh, .ds .rows--wide > .trow {
  /* eight columns since पेश गरेको मिति became a filter rather than a column */
  grid-template-columns: 42px 1.8fr 1fr 1.05fr 1.4fr 1.2fr 1.05fr 1fr;
}
.ds .rows--wide > .trowh > *:nth-child(2) { text-align: left; }

/* The centred three-line caption the legacy screen printed above its table —
   report title, issuing office, ward. It is part of the printed/exported sheet,
   so it sits inside the scroll container with the table, not in the page head. */
.ds .tablehead { text-align: center; padding: 14px 12px 12px; border-bottom: 1px solid var(--ds-border); }
.ds .tablehead b { display: block; font-size: 15px; font-weight: 700; color: var(--ds-ink); }
.ds .tablehead span { display: block; font-size: 13px; color: var(--ds-subtle); margin-top: 2px; }
/* कार्य stays put while the rest of the row scrolls. These lists are wide enough
   that the buttons sit off-screen at the scroll position a reviewer actually reads
   from, which meant scrolling right, acting, then scrolling back for every record.
   The last cell is the action cell in every one of these tables, so it pins to the
   right edge and everything else slides under it.

   The cells need an OPAQUE background of their own — a grid child is transparent by
   default, so a sticky one would have the scrolling columns visibly slide through
   it. The header and body cells therefore repeat their own row's colour. */
.ds .tablescroll .trowh > *:last-child,
.ds .tablescroll .trow > *:last-child {
  position: sticky;
  right: 0;
  z-index: 2;
  /* the shared gap would otherwise leave a transparent strip the columns show
     through as they pass under the pinned cell */
  margin-right: -17px;
  padding-right: 17px;
  padding-left: 12px;
  box-shadow: -6px 0 6px -6px rgba(16, 24, 40, 0.18);
}
.ds .tablescroll .trowh > *:last-child { background: var(--ds-bg); }
.ds .rows--ruled > .trowh > *:last-child { background: var(--brand-primary); }
.ds .tablescroll .trow > *:last-child { background: var(--ds-surface); }
/* a.trow highlights on hover; the pinned cell has to follow or it reads as a
   detached white block sitting on a tinted row */
.ds .tablescroll a.trow:hover > *:last-child { background: var(--ds-bg); }
/* Below the card breakpoint the shared .trow rule restacks each row into a card,
   where nothing scrolls sideways and a pinned cell would just float oddly over
   the stack. */
@media (max-width: 768px) {
  .ds .tablescroll .trowh > *:last-child,
  .ds .tablescroll .trow > *:last-child {
    position: static; margin-right: 0; padding-right: 0; box-shadow: none;
  }
}
.ds .rowacts { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
/* Stacked actions: सम्पादन over किसानको विवरण हेर्नुहोस्, both full-width so the two
   read as a small menu for the row rather than two half-wrapped buttons. */
.ds .rowacts--stack { flex-direction: column; align-items: stretch; }
.ds .rowacts--stack .dbtn { justify-content: center; }
@media (max-width: 680px) { .ds .rowacts--stack { flex-direction: row; align-items: center; } }
.ds .statchip.is-on { box-shadow: inset 0 0 0 2px currentColor; }
/* ── the ruled registers restack as cards below 900px, not 680 ──────────────
   The shared .trow rule cards every row below 680px. These registers needed a
   HIGHER breakpoint of their own: they carry nine to fourteen columns, and the
   fr tracks that let them fit a 1366px laptop without a scrollbar (see the
   .rows--ruled note above) go on shrinking below that. At 700px the कार्य
   button was setting one glyph per line, "आय (रु)" and "पशु (LU)" were clipped
   mid-heading, and the वर्ग pill was cut off by its own cell rule — the columns
   had run out of room long before the 680px card breakpoint rescued them.

   900px is where the widest of them (the प्रमाणीकरण queue's fourteen) stops
   being readable. Between 901 and 1200 they scroll instead — see the band
   below; above that the fr layout is genuinely comfortable and is left alone.

   Restated here rather than left to the shared ≤680 block, because that block
   cannot reach the 681–900 band and every one of its rules is outranked by the
   .rows--ruled desktop rules anyway (child combinator, one class more). */
@media (max-width: 900px) {
  .ds .rows--ruled > .trowh { display: none; }
  /* !important for the same reason the shared .trow rule needs it: every one of
     these registers declares its grid-template-columns inline. */
  .ds .rows--ruled > .trow {
    grid-template-columns: 1fr !important;
    gap: 5px;
    /* the desktop rule zeroes this so the cell rules can meet; as a card the row
       is the thing with edges again, and the text was sitting flush against them */
    padding: 13px 16px;
  }
  /* Label left, value right — the same pair the shared card layout makes of a
     plain .trow. display:block was leaving the ::before label glued to the value
     with nothing between them ("मोबाइल नम्बर९८४१२३४५६७"). */
  .ds .rows--ruled > .trow > * {
    display: flex;
    /* the desktop cell is a COLUMN flex box (it centres a value in a tall ruled
       cell); left as it was, the label simply sat on the line above the value
       and the pair read as two rows rather than one */
    flex-direction: row;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    text-align: left;
    border-right: 0;
    padding: 3px 0;
  }
  /* Flex also disarms the inline text-align:right the numeric columns carry:
     it aligns text inside the items, and no longer drags the label to the edge
     with the value. */
  .ds .rows--ruled > .trow [data-l]::before {
    content: attr(data-l);
    flex: none;
    font-size: 11px;
    color: var(--ds-subtle);
    font-weight: 600;
    letter-spacing: 0.04em;
  }
  .ds .rows--ruled > .trow > .u-name { justify-content: flex-start; align-items: center; }
  /* the bulk tick is the one cell with no label to sit opposite; centred, it
     floated alone in the middle of the card's first line */
  .ds .rows--ruled > .trow > .bulkcell { justify-content: flex-start; align-items: center; }
  /* the avatar is 30px the desktop name column could not spare; a card can, and
     without it these rows read differently from every other list on a phone */
  .ds .rows--ruled > .trow .u-name .avatar { display: inline-grid; }
  /* stacked cards are not scrolling, so the pinned-cell shadow has nothing to do */
  .ds .tablescroll .rows--ruled > .trowh > *:last-child,
  .ds .tablescroll .rows--ruled > .trow > *:last-child {
    position: static; margin-right: 0; box-shadow: none;
  }
}

/* ── 901–1200px: the ruled registers scroll rather than crush ───────────────
   The desktop rule deliberately declares no min-width, so the columns shrink to
   whatever is left and the cells wrap. That is right on a 1366px laptop, where
   they wrap to two lines and the whole register is on screen. It is not right
   on a tablet, where "मझौला व्यावसायिक किसान" in a 68px column wraps to four
   lines and every row is 110px tall.

   So the floor comes back, but only in this band: 1080px is what the fourteen
   columns actually measure at 1366px, i.e. the width at which they were last
   comfortable. Above 1200 the declaration is absent again and nothing scrolls,
   which is the behaviour the .rows--ruled note above was protecting. */
@media (min-width: 901px) and (max-width: 1200px) {
  .ds .tablescroll .rows--ruled > .trowh,
  .ds .tablescroll .rows--ruled > .trow { min-width: 1080px; }
  /* and with it scrolling, कार्य pins to the right edge again — the general
     .tablescroll rule that does this is switched off for .rows--ruled above,
     because a register that does not scroll has no use for it */
  .ds .tablescroll .rows--ruled > .trowh > *:last-child,
  .ds .tablescroll .rows--ruled > .trow > *:last-child {
    position: sticky;
    right: 0;
    z-index: 2;
    margin-right: -17px;
    padding-right: 17px;
    padding-left: 12px;
    box-shadow: -6px 0 6px -6px rgba(16, 24, 40, 0.18);
  }
}

/* lightbox */
.lightbox-overlay { position: fixed; inset: 0; z-index: 1090; background: rgba(0,0,0,.85); display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: opacity .2s, visibility .2s; cursor: zoom-out; }
.lightbox-overlay.is-open { opacity: 1; visibility: visible; }
.lightbox-overlay img { max-width: 92vw; max-height: 90vh; object-fit: contain; border-radius: 6px; box-shadow: 0 8px 40px rgba(0,0,0,.5); cursor: default; }
.lightbox-close { position: absolute; top: 16px; right: 20px; background: none; border: none; color: #fff; font-size: 32px; line-height: 1; cursor: pointer; opacity: .8; z-index: 1; }
.lightbox-close:hover { opacity: 1; }
@media print { .lightbox-overlay { display: none !important; } }

/* callouts */
.ds .callout { display: flex; gap: 10px; padding: 10px 12px; border-radius: var(--r-sm); border: 1px solid; align-items: flex-start; }
.ds .callout + .callout { margin-top: 10px; }
.ds .callout__ic { width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center; flex: none; }
.ds .callout b { font-size: 13.5px; font-weight: 600; display: block; }
.ds .callout p { margin: 2px 0 0; font-size: 12.5px; color: var(--ds-muted); }
.ds .callout--warn { background: var(--warn-bg); border-color: #ecd6ac; }
.ds .callout--warn .callout__ic { background: #fff; color: var(--warn); }
.ds .callout--danger { background: var(--bad-bg); border-color: #f0c9c7; }
.ds .callout--danger .callout__ic { background: #fff; color: var(--bad); }
.ds .callout--calm { background: var(--ds-bg); border-color: var(--ds-border); }
.ds .callout--calm .callout__ic { background: var(--ds-surface); color: var(--ds-subtle); }
.ds .callout--good { background: var(--ok-bg); border-color: #b7e0c7; }
.ds .callout--good .callout__ic { background: #fff; color: var(--ok); }

/* Blocker chips in the "not yet submittable" callout — one link per unfilled
   section, wrapping instead of running off the callout on a narrow screen. */
.ds .blocker-links { display: flex; flex-wrap: wrap; gap: 8px; }

/* empty state */
.ds .empty { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px 18px; color: var(--ds-muted); min-height: 150px; }
.ds .empty__ic { width: 42px; height: 42px; border-radius: 11px; background: var(--ds-bg); color: var(--ds-subtle); display: grid; place-items: center; margin-bottom: 12px; }
.ds .empty__ic svg { width: 22px; height: 22px; }
.ds .empty b { font-size: 14px; color: var(--ds-ink); }
.ds .empty p { margin: 4px 0 12px; font-size: 12.5px; max-width: 280px; }
.ds .sectlabel { font-size: 12px; font-weight: 600; color: var(--ds-subtle); text-transform: uppercase; letter-spacing: 0.05em; margin: 16px 0 8px; }

/* Master-data hub section heading. Deliberately not .sectlabel: that is a 12px
   uppercase eyebrow, and the hub now carries ten groups (जग्गा, यन्त्र, बाली,
   पशुपन्छी, …) that have to read as headings separating the page, not as quiet
   captions above it. .sectlabel's uppercase + letter-spacing are also wasted or
   worse on Devanagari — there is no case to fold, and tracking pulls conjuncts
   apart. The trailing rule carries the eye across to the next group. */
.ds .mgroup {
  display: flex; align-items: center; gap: 12px;
  font-size: 15px; font-weight: 700; color: var(--ds-ink);
  margin: 26px 0 10px; letter-spacing: 0;
}
.ds .mgroup::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--ds-border);
}
/* Never :first-child — .pagehead sits above the first group, so that selector
   silently matches nothing. Same trap as .entrycard__hd + .sectlabel below. */
.ds .pagehead + .mgroup { margin-top: 0; }

/* forms */
.ds .form-control, .ds .form-select,
.ds input[type="time"], .ds input[type="date"], .ds input[type="number"], .ds input[type="tel"], .ds input[type="text"], .ds input[type="email"], .ds input[type="password"] {
  width: 100%; font: inherit; font-size: 13.5px; color: var(--ds-ink);
  background: var(--ds-surface); border: 1px solid var(--ds-border-strong);
  border-radius: var(--r-sm); padding: 6px 10px; box-shadow: none;
  transition: border-color 0.1s, box-shadow 0.1s; min-height: 0; line-height: 1.4;
}
.ds .form-select { padding-right: 30px; }
.ds .form-control:focus, .ds .form-select:focus, .ds input:focus {
  outline: none; border-color: var(--brand-primary); box-shadow: 0 0 0 3px var(--primary-line);
}
.ds .form-control::placeholder { color: var(--ds-subtle); }
.ds textarea.form-control { min-height: 38px; resize: vertical; }
.ds .form-control.is-invalid, .ds .form-select.is-invalid, .ds input.is-invalid { border-color: var(--bad); }
.ds .form-control:disabled, .ds .form-select:disabled, .ds input:disabled {
  background: var(--ds-bg); color: var(--ds-subtle); border-color: var(--ds-border);
  cursor: not-allowed; pointer-events: none;
}

/* Search box (input + attached button) used by every list filter bar. Bootstrap's
   own `.input-group > .form-control` sizing (flex:1 1 auto; width:1%) has the same
   specificity as `.ds .form-control{width:100%}` above and loses to it on cascade
   order (this file loads after bootstrap.min.css) — the input claims the whole
   flex line and the button wraps onto a line of its own. One more selector level
   of specificity reasserts the sizing and joins the two into one visual pill. */
.ds .input-group { flex-wrap: nowrap; }
.ds .input-group .form-control { width: auto; flex: 1 1 auto; min-width: 0; border-radius: var(--r-sm) 0 0 var(--r-sm); }
.ds .input-group .dbtn { flex: 0 0 auto; border-radius: 0 var(--r-sm) var(--r-sm) 0; margin-left: -1px; }
.ds .input-group .form-control:focus { position: relative; z-index: 1; }
.ds .flabel { display: block; font-size: 12px; font-weight: 600; color: var(--ds-muted); margin-bottom: 3px; }
.ds .flabel .req { color: var(--bad); }
.ds .ferr { color: var(--bad-ink); font-size: 12px; margin-top: 4px; }
.ds .fhelp { color: var(--ds-subtle); font-size: 11.5px; margin-top: 4px; }

/* Read-only counterpart to .form-control: a value the system fills in and the
   user cannot edit (login audit, account identifiers). Same height and baseline
   as a real input so a mixed .fgrid row still lines up, but visibly inert — no
   border or white field, so it never reads as a box someone forgot to enable. */
.ds .fstatic {
  font-size: 13.5px; color: var(--ds-ink); font-weight: 500;
  padding: 7px 0; min-height: 34px; word-break: break-word;
}

/* checklist dropdown — a <details> styled to look like a closed form-select,
   opening into a checkbox list (marketing methods and similar multi-selects).
   The panel is positioned absolutely so opening it overlays the page instead
   of growing the <details> box in normal flow — inside an .fgrid/.fgrid row
   (default align-items: stretch), an in-flow panel would stretch every other
   column in that row to match its open height, distorting the whole row. */
.ds .ms-dropdown { position: relative; }
.ds .ms-dropdown > summary { list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ds .ms-dropdown > summary::-webkit-details-marker { display: none; }
.ds .ms-dropdown > summary::after { content: ""; width: 8px; height: 8px; border-right: 2px solid var(--ds-muted); border-bottom: 2px solid var(--ds-muted); transform: rotate(45deg); margin-top: -4px; flex: none; }
.ds .ms-dropdown[open] > summary::after { transform: rotate(-135deg); margin-top: 2px; }
.ds .ms-dropdown .ms-panel {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 30;
  min-width: 100%; width: max-content; max-width: 340px;
  border: 1px solid var(--ds-border-strong); border-radius: var(--r-sm);
  padding: 10px 12px; background: var(--ds-surface); box-shadow: var(--sh-md);
  display: flex; flex-wrap: wrap; gap: 6px 16px;
}
.ds .ms-dropdown .ms-panel label { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 400; cursor: pointer; white-space: nowrap; }

/* Tag/chip face for a checklist dropdown — opt in with data-tags on the
   <details> (see renderChecklistTags in erp.js). "6 छानिएको" said how many were
   picked but not which, so on the 13-species माछा जात/प्रजाती list you had to
   open the panel and hunt for ticks to read your own answer back. The closed
   control now shows the picks themselves, each removable in one tap.
   The checkboxes inside .ms-panel remain the source of truth. */
/* nowrap on the summary, wrap INSIDE .ms-tags: the chips take one flexible
   column that grows downward and the chevron stays pinned top-right, instead of
   being pushed onto a line of its own once the chips wrap. */
.ds .ms-dropdown[data-tags] > summary {
  flex-wrap: nowrap; align-items: flex-start; gap: 8px;
  height: auto; min-height: 34px; padding-top: 5px; padding-bottom: 5px;
}
.ds .ms-dropdown[data-tags] > summary::after { margin-top: 7px; }
.ds .ms-tags { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; flex: 1 1 auto; min-width: 0; }
.ds .ms-tags-empty { color: var(--ds-subtle); }
.ds .ms-tag {
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
  background: var(--primary-soft); color: var(--brand-primary);
  border-radius: 20px; padding: 3px 3px 3px 10px;
  font-size: 12.5px; font-weight: 600; line-height: 1.5;
}
.ds .ms-tag > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds .ms-tag__x {
  flex: none; width: 18px; height: 18px; border: 0; border-radius: 50%;
  background: transparent; color: inherit; cursor: pointer;
  font-size: 15px; line-height: 1; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.ds .ms-tag__x:hover { background: rgba(0, 0, 0, 0.12); }
/* Comfy pages get a thumb-sized chip and remove target. */
.ds--comfy .ms-dropdown[data-tags] > summary { min-height: 48px; }
.ds--comfy .ms-tag { font-size: 14px; padding: 5px 5px 5px 12px; }
.ds--comfy .ms-tag__x { width: 24px; height: 24px; font-size: 17px; }

/* searchable select (js/searchable-select.js) — the native <select> is kept for
   the POST value and hidden, with a text input + filtered list drawn over it.
   Panel is absolute for the same reason as .ms-panel above: an in-flow panel
   would stretch every other column of its .fgrid row to match. */
.ds .ss { position: relative; }
.ds .ss > select { display: none; }
.ds .ss__panel {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 30;
  width: 100%; max-height: 240px; overflow-y: auto;
  border: 1px solid var(--ds-border-strong); border-radius: var(--r-sm);
  padding: 4px; background: var(--ds-surface); box-shadow: var(--sh-md);
}
.ds .ss__opt { padding: 7px 9px; border-radius: var(--r-sm); font-size: 13.5px; cursor: pointer; }
.ds .ss__opt--active { background: var(--primary-soft); color: var(--brand-primary); }
.ds .ss__opt--on { font-weight: 600; }
.ds .ss__empty { padding: 9px; font-size: 12.5px; color: var(--ds-subtle); }

/* 12-col form grid */
.ds .fgrid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 8px 10px; }
/* Every span 1-12 is defined: an undefined class (f1/f5/f7 were all in use) has
   no grid-column rule, so the field silently collapses to a single 1/12 column
   instead of the width the markup asks for. */
.ds .f1 { grid-column: span 1; } .ds .f2 { grid-column: span 2; }
.ds .f3 { grid-column: span 3; } .ds .f4 { grid-column: span 4; }
.ds .f5 { grid-column: span 5; } .ds .f6 { grid-column: span 6; }
.ds .f7 { grid-column: span 7; } .ds .f8 { grid-column: span 8; }
.ds .f9 { grid-column: span 9; } .ds .f10 { grid-column: span 10; }
.ds .f11 { grid-column: span 11; } .ds .f12 { grid-column: span 12; }
/* The save row sticks to the bottom of the viewport: on a long section (जग्गा,
   बाली, पशुपन्छी) it was only reachable after scrolling past every row, which is
   what made those pages feel endless. Sticky, not fixed — it still sits in flow
   at the end of the form, it just stops scrolling out of reach on the way there. */
.ds .factions { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 14px;
                position: sticky; bottom: 0; z-index: 5;
                padding: 10px 0; background: linear-gradient(to bottom, transparent, var(--ds-bg) 22%); }
/* Checkbox checklist — the बाली page's "which crop kinds do you grow" picker,
   which decides how many detail sections open below it. */
.ds .chkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(165px, 1fr)); gap: 7px 14px; }
.ds .chkitem { display: flex; align-items: center; gap: 8px; font-size: 13.5px; cursor: pointer; }
.ds .chkitem input { width: auto; margin: 0; }
/* A grouping wrapper (e.g. Capacity + Capacity unit, or Amount vs Qty+Unit on
   Subsidy rows) that shouldn't itself occupy a grid cell — its children lay
   out directly in the parent .fgrid as if the wrapper weren't there. Defined
   as a class, not an inline style: the JS that shows/hides these groups sets
   `style.display = ''` to reveal one, which would wipe out an inline
   `display:contents` and fall back to block (stacking the children) instead
   of back to this rule. */
.ds .capacity-fields, .ds .own-fields, .ds .subsidy-amount-fields, .ds .subsidy-qty-fields { display: contents; }

/* Livestock rows where which cells are visible depends on the selected species
   (identity: Type/Specify name/Breed; headcount: Count vs. up to 4 role counts;
   measures: milk/egg/meat) — a 12-col .fgrid reserved all 12 columns even when
   a kind only populates 1-2 cells, leaving a wide blank gap. Flex instead:
   cells grow to share whatever width is available (capped so a lone cell
   doesn't stretch absurdly wide) instead of stacking a fixed 3-of-12 span
   next to empty grid track. */
.ds .fitrow { display: flex; flex-wrap: wrap; gap: 10px 12px; }
.ds .fitcell { flex: 1 1 200px; max-width: 340px; }
/* Free-text cell (कैफियत) sharing a flow row with number cells — same row,
   just a wider share of it so the text box stays usable. */
.ds .fitcell--wide { flex: 2 1 260px; max-width: 560px; }
/* A tag-face multi-select needs a row of its own. Squeezed into .fitcell's
   340px cap, माछा जात/प्रजाती wrapped seven chips onto four lines and pushed the
   measure cells beside it out of alignment. flex-basis:100% breaks the line so
   the chips get the full width; max-width stops them stringing out across a
   very wide monitor, and the measure cells flow underneath as their own row. */
.ds .fitcell--tags { flex: 1 1 100%; max-width: 760px; }

/* repeatable-row furniture ------------------------------------------------- */
/* Row-collapse and the mid-row action cluster were removed (2026-08-08 phone-UX
   feedback): each row's single हटाउनुहोस् sits after its inputs (.rowact-end). */
/* A समूह section folded to its header from the chevron in .panel__hd —
   display-only, its fields keep posting too. */
.ds .panel.is-folded > .panel__bd { display: none; }
.ds .panel.is-folded [data-action="toggle-section"] svg { transform: rotate(180deg); }
/* Opt-in modifier for a repeat list whose rows are stacked multi-line blocks
   rather than a single grid row — draws a dashed separator between rows. Not
   applied to every .repeat-rows: the lists with their own inline bottom-border
   separator (family, income, subsidy) would get a second line drawn on them. */
.ds .repeat-rows--stacked > .repeat-row + .repeat-row {
  margin-top: 7px; padding-top: 7px; border-top: 1px dashed var(--ds-border); }
/* A conditionally-revealed sub-group belonging to the row above it. */
.ds .subfields { margin-top: 7px; padding: 8px 10px; border-radius: var(--r-sm);
                 background: var(--ds-bg); border: 1px solid var(--ds-border); }
/* Three equal cells for a primary/secondary/tertiary measurement. */
.ds .triplet { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
/* A repeatable sub-row nested inside one .repeat-row (a livestock row's market
   sales). Separated by a rule rather than another card, so the nesting depth
   stays readable inside an already-shaded .subfields panel. */
.ds .sale-row + .sale-row { margin-top: 12px; padding-top: 12px;
                            border-top: 1px solid var(--ds-border); }
/* Checkbox that sits on the same baseline as the inputs beside it. */
.ds .fcheck { display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
              font-size: 13px; font-weight: 500; color: var(--ds-ink); }
.ds .fbottom { display: flex; align-items: flex-end; padding-bottom: 9px; }
@media (max-width: 720px) {
  .ds .fbottom { padding-bottom: 0; }
  .ds .triplet { gap: 6px; }
}

/* wizard header (web/partials/stepper.html) --------------------------------- */
/* Sits above every section page: form title, "चरण X / N" progress readout, and
   the tappable step navigator. One DOM, two layouts — see .stepnav below. */
.ds .wizard { margin-bottom: 16px; }
/* Dark ink, not brand green. Green now carries state on this header — the
   चरण pill, the fill bar, the done/current step chips — so a green title
   competed with the one thing on the row the enumerator is meant to read as
   progress. Sized up to 17px because .ds--comfy sets body type to 15px, which
   left a 15px "title" indistinguishable from the form's own labels. */
.ds .wizard__title {
  text-align: center; font-size: 17px; font-weight: 700; color: var(--ds-ink);
  letter-spacing: 0.01em; padding: 2px 0 12px;
}
/* Fiscal-year selector — a centred row of year pills above the progress line,
   shown only for farmers with data in more than one year (migrated records). */
.ds .wizard__years { display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; margin: 0 0 12px; }
.ds .wizard__years-label { font-size: 13px; font-weight: 600; color: var(--ds-muted); flex: none; }
.ds .yrpill { font-size: 13px; font-weight: 600; color: var(--ds-ink); background: var(--ds-surface-2, #f1f3f5);
  border: 1px solid var(--ds-border, #dde1e6); border-radius: 20px; padding: 3px 11px; text-decoration: none; line-height: 1.6; }
/* :not(--active): the pseudo-class outranks .yrpill--active, so a plain :hover
   painted the ACTIVE pill's text brand-green on its brand-green fill */
.ds .yrpill:hover:not(.yrpill--active) { border-color: var(--brand-primary); color: var(--brand-primary); }
.ds .yrpill--active { background: var(--brand-primary); border-color: var(--brand-primary); color: #fff; }
.ds .wizard__prog { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.ds .wizard__count { font-size: 13px; font-weight: 700; color: var(--brand-primary); background: var(--primary-soft); border-radius: 20px; padding: 4px 11px; flex: none; }
.ds .wizard__now { font-size: 14px; font-weight: 600; color: var(--ds-ink); flex: none; }
/* The bar takes the rest of the line on desktop and drops to its own full-width
   line on a phone, where the count + section name already fill the first one. */
.ds .wizard__bar { flex: 1 1 120px; min-width: 90px; height: 6px; border-radius: 6px; background: var(--ds-border-strong); overflow: hidden; }
.ds .wizard__bar > span { display: block; height: 100%; background: var(--brand-primary); border-radius: 6px; }

/* Step navigator. Desktop: the full tab bar — icon + label for every step,
   wrapping onto as many lines as it needs so nothing is hidden behind a scroll.
   Phone: numbered circles in one scrolling strip with only the current step
   spelled out, keeping all N steps reachable in a thumb-sized target each. */
.ds .stepnav { display: flex; flex-wrap: wrap; gap: 6px; }
.ds .stepnav .step {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  font-size: 13px; font-weight: 600; text-decoration: none; min-height: 40px;
  padding: 6px 12px; border-radius: 22px; border: 1px solid var(--ds-border);
  background: var(--ds-surface); color: var(--ds-muted);
}
.ds .stepnav .step:hover { background: var(--ds-bg); color: var(--ds-ink); text-decoration: none; }
.ds .stepnav .step--done { border-color: var(--primary-line); background: var(--primary-soft); color: var(--brand-primary); }
.ds .stepnav .step--done:hover { background: var(--primary-soft); color: var(--brand-primary); }
.ds .stepnav .step--current { border-color: var(--brand-primary); background: var(--brand-primary); color: var(--on-primary); }
.ds .stepnav .step--current:hover { background: var(--brand-primary); color: var(--on-primary); filter: brightness(1.06); }
.ds .stepnav .step__ic { width: 14px; height: 14px; flex: none; }
.ds .stepnav .step__no {
  display: none; width: 22px; height: 22px; border-radius: 50%; flex: none;
  align-items: center; justify-content: center; font-size: 12.5px; font-weight: 700;
  background: rgba(0, 0, 0, 0.06);
}
.ds .stepnav .step--current .step__no { background: rgba(255, 255, 255, 0.22); }
.ds .stepnav .step__no svg { width: 13px; height: 13px; }
@media (max-width: 899.98px) {
  .ds .stepnav { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 4px; }
  .ds .stepnav .step { min-height: 44px; padding: 6px 9px; }
  .ds .stepnav .step__no { display: inline-flex; }
  .ds .stepnav .step__ic { display: none; }
  /* Every step keeps its name. Hiding the labels of all but the current step
     left a strip of bare Devanagari numerals on exactly the device the
     enumerator uses in the field: going back to fix परिवार meant guessing which
     circle it was, and a wrong guess navigates away from a page that only
     autosaves every 30s. The strip already scrolls horizontally, so the labels
     cost scrolling, not reachability — and `aria-label` was carrying the names
     for screen readers while the sighted user got nothing. */
  .ds .wizard__bar { flex-basis: 100%; }
}

/* entry cards --------------------------------------------------------------- */
/* One record = one card, with a Devanagari-numbered badge and its own हटाउनुहोस्.
   Replaces the dense table-style repeat rows on phones (जग्गाका टुक्रा and the
   list of अन्य स्थान). Numbering is a pure CSS counter so that cloning a row
   (window.addRow) or deleting one renumbers the rest with no JS involved. */
.ds .cardlist, .ds .repeat-rows--cards { display: grid; gap: 12px; counter-reset: entry; }
.ds .cardlist > *, .ds .repeat-rows--cards > .repeat-row { counter-increment: entry; }
.ds .entrycard { border: 1px solid var(--ds-border); border-radius: var(--r-md); background: var(--ds-surface); padding: 12px 13px; }
.ds .entrycard__hd { display: flex; align-items: center; gap: 10px; margin-bottom: 11px; padding-bottom: 10px; border-bottom: 1px solid var(--ds-border); }
.ds .entrycard__no { width: 28px; height: 28px; border-radius: 50%; flex: none; display: grid; place-items: center;
                     background: var(--primary-soft); color: var(--brand-primary); font-size: 13.5px; font-weight: 700; }
.ds .entrycard__no::before { content: counter(entry, devanagari); }
.ds .entrycard__ttl { font-size: 14px; font-weight: 600; color: var(--ds-ink); min-width: 0; }
.ds .entrycard__act { margin-left: auto; flex: none; display: flex; gap: 7px; }
/* A labelled block inside a card (मुख्य विवरण / क्षेत्रफल / सिँचाई / सम्झौता), so a
   long card reads as three short questions rather than one wall of inputs. */
/* Adjacent-sibling, not :first-child — a card always opens with .entrycard__hd,
   so the first .sectlabel is never its first child and the reset never applied.
   The 16px it was added to remove was still shipping under every card header. */
.ds .entrycard .entrycard__hd + .sectlabel { margin-top: 0; }

/* filter bar */
/* Master-data inline row editor. The row becomes one spanning cell holding a
   labelled flex strip, because the editable fields and the display columns are
   different sets — mapping inputs onto column positions would misalign them. */
.ds .mrow-edit > td { background: var(--primary-soft); padding: 12px 14px; }
.ds .mrow-fields { display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: flex-end; }
.ds .mrow-field { flex: 1 1 150px; min-width: 120px; }
.ds .mrow-field .flabel { margin-bottom: 3px; }
.ds .mrow-field--check { flex: 0 0 auto; display: flex; align-items: center; gap: 7px; padding-bottom: 8px; }
.ds .mrow-field--check .flabel { margin: 0; order: 2; }
.ds .mrow-actions { flex: 0 0 auto; margin-left: auto; display: flex; gap: 7px; align-items: center; }
@media (max-width: 640px) {
  .ds .mrow-field { flex: 1 1 100%; }
  .ds .mrow-actions { margin-left: 0; width: 100%; }
}

.ds .filterbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; background: var(--ds-surface); border: 1px solid var(--ds-border); border-radius: var(--r-md); box-shadow: var(--sh-sm); padding: 12px 14px; margin-bottom: 15px; }
/* 190px, not 140: a 140px basis let four filters share a 700px tablet and every
   one of them truncated its option ("बूढानीलकण्ठ नगरपालिका" → "बूढानीलकण्ठ
   नगरपालि"). These are geography selects whose whole job is to be read, so the
   bar wraps to three and keeps them legible instead. min-width goes to 0 so the
   last row of a wrap can still shrink to fit. */
.ds .filterbar > div { min-width: 0; flex: 1 1 190px; max-width: 260px; }
.ds .filterbar .flabel { margin-bottom: 3px; }
.ds .filterbar .factions { margin-top: 0; flex: none; }

/* segmented status filter (tabs) */
.ds .geo-toggle { border: none; background: none; padding: 0; margin-right: 5px; cursor: pointer; color: var(--ds-muted); vertical-align: middle; }
.ds .geo-toggle svg { width: 15px; height: 15px; transition: transform 0.15s; }
.ds .geo-row.open > span > .geo-toggle svg { transform: rotate(90deg); }
.ds .geo-leaf { display: inline-block; width: 20px; }
.ds .bulkbar { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid var(--ds-border); flex-wrap: wrap; background: var(--ds-surface-2, #f8fafc); }
.ds .bulkbar__chk { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap; }
.ds .bulkbar__count { font-size: 12.5px; color: var(--ds-muted); font-variant-numeric: tabular-nums; }
.ds .segtabs { display: inline-flex; background: var(--ds-bg); border: 1px solid var(--ds-border); border-radius: var(--r-sm); padding: 3px; gap: 2px; }
.ds .segtabs a { padding: 6px 13px; border-radius: 6px; font-size: 12.5px; font-weight: 600; color: var(--ds-muted); text-decoration: none; display: inline-flex; align-items: center; gap: 6px; }
.ds .segtabs a.active { background: var(--ds-surface); color: var(--brand-primary); box-shadow: var(--sh-sm); }
.ds .segtabs a .n { font-variant-numeric: tabular-nums; opacity: 0.7; }
/* The विश्लेषण section's tab strip: six destinations, so it must be allowed to
   wrap on a phone rather than scroll off the edge of a screen an officer is
   holding in one hand. Inline-flex (the base rule) would keep it one line. */
.ds .segtabs--wrap { display: flex; flex-wrap: wrap; }
.ds .segtabs--wrap a svg { width: 15px; height: 15px; }
/* Divider inside the tab strip: the analysis views and the parameterised tables
   are one section but two reading modes, and a hairline says so without a
   second heading. */
.ds .segtabs__sep { width: 1px; align-self: stretch; margin: 2px 4px; background: var(--ds-border); }

/* Breadcrumb — नेपाल › बागमती › चितवन. This IS the drill control on the
   analytics pages, so the links are the ordinary link colour and only the
   current geography is inert; a trail whose last item still looked clickable
   would invite a click that reloads the same page. */
.ds .crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 13.5px; }
.ds .crumbs__sep { color: var(--ds-subtle); }
.ds .crumbs__here { font-weight: 700; color: var(--ds-ink); }

/* ── choropleth (विश्लेषण → नक्सा) ──────────────────────────────────────────
   The map is an inline SVG of pre-projected paths; there is no tile layer and
   no mapping library, so all the styling it needs is here. */
.ds .chmap-wrap { position: relative; width: 100%; }
.ds .chmap { width: 100%; height: auto; display: block; }
.ds .chmap__shape { transition: opacity .12s ease; outline: none; }
/* Hover/focus lift the shape rather than recolouring it — a hover colour would
   momentarily put a fill on the map that means nothing in the legend. */
.ds .chmap__shape:hover, .ds .chmap__shape:focus { opacity: 0.72; }
.ds .chmap__shape:focus { stroke: var(--brand-primary); stroke-width: 1.6; }
.ds .chmap__loading, .ds .chmap__err { padding: 40px 0; text-align: center; color: var(--ds-subtle); }
.ds .chmap__tip {
  position: absolute; pointer-events: none; z-index: 5;
  background: rgba(23, 23, 20, 0.93); color: #fff; padding: 8px 10px;
  border-radius: 6px; font-size: 12.5px; line-height: 1.5; max-width: 240px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
}
.ds .chmap__tip .tip__sub { opacity: 0.72; font-size: 11.5px; }
.ds .chlegend { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 12px;
                font-size: 12px; color: var(--ds-muted); }
.ds .chlegend__ttl { font-weight: 700; color: var(--ds-ink); }
.ds .chlegend__item { display: inline-flex; align-items: center; gap: 6px; }
.ds .chlegend__item i { width: 16px; height: 12px; border-radius: 2px; display: inline-block;
                        border: 1px solid rgba(0, 0, 0, 0.08); }

/* key-value list */
.ds .kv { display: grid; grid-template-columns: 160px 1fr; }
.ds .kv > dt { color: var(--ds-muted); font-weight: 500; font-size: 13px; padding: 7px 0; border-bottom: 1px solid var(--ds-border); margin: 0; }
.ds .kv > dd { font-size: 13.5px; padding: 7px 0; border-bottom: 1px solid var(--ds-border); margin: 0; min-width: 0; overflow-wrap: break-word; }
.ds .kv > dt:last-of-type, .ds .kv > dd:last-of-type { border-bottom: 0; }

/* pagination */
/* Both rows wrap at every width. The number strip is up to eleven boxes
   (अघिल्लो, seven numbers, two gaps, अर्को) and the 44px targets below make that
   ~550px on their own — with nothing telling either row what to do past the
   content width, the 681–768px band (tablet portrait, a narrow desktop window)
   pushed the strip straight out of .content-area. */
.ds .pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 12px; margin-top: 15px; font-size: 12.5px; color: var(--ds-muted); }
.ds .pager .pages { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ds .pager .sizes { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ds .pager .sizes a, .ds .pager .sizes span.curr { padding: 3px 8px; border: 1px solid var(--ds-border); border-radius: 6px; text-decoration: none; color: var(--ds-ink); font-weight: 600; }
.ds .pager .sizes .curr { background: var(--brand-primary); color: #fff; border-color: var(--brand-primary); }
.ds .pager .pages a, .ds .pager .pages span { padding: 5px 10px; border: 1px solid var(--ds-border); border-radius: 6px; text-decoration: none; color: var(--ds-ink); font-weight: 600; }
.ds .pager .pages .curr { background: var(--brand-primary); color: #fff; border-color: var(--brand-primary); }
/* The elision between two runs of page numbers is a mark, not a target: no box
   around it, and it keeps its width so the strip does not jump as the runs move. */
.ds .pager .pages .gap { border-color: transparent; padding-left: 2px; padding-right: 2px; color: var(--ds-muted); display: inline-flex; align-items: center; }
/* the "Page [ 2 ] of 184" jump box */
.ds .pager .pagejump { display: flex; align-items: center; gap: 7px; margin: 0; }
.ds .pager .pagejump input { width: 64px; padding: 4px 6px; font: inherit; font-weight: 600; text-align: center;
  color: var(--ds-ink); background: var(--ds-surface); border: 1px solid var(--ds-border-strong); border-radius: 6px; }
.ds .pager .pagejump input:focus { outline: none; border-color: var(--brand-primary); box-shadow: 0 0 0 3px var(--primary-line); }

/* The pager is three flex children pushed apart by space-between, with nothing
   telling them what to do when they stop fitting. On a phone the caption
   ("जम्मा ४,८२३ मध्ये १–२० देखाइएको") was squeezed into a three-line column
   beside the page buttons and the पृष्ठ jump wrapped its "/ २४२" onto its own
   line. Stacked and centred, each of the three gets the full width. */
@media (max-width: 680px) {
  .ds .pager { flex-direction: column; align-items: stretch; gap: 10px; text-align: center; }
  .ds .pager .pages { justify-content: center; }
  .ds .pager .pagejump { justify-content: center; }
}
/* A page number was a 27×31px target. WCAG 2.2 asks 24px as a floor and these
   are the control a reader uses most on a register, so they get a comfortable
   44. inline-flex rather than extra padding, so the box grows around the digit
   instead of pushing the row apart.

   Two conditions, deliberately, because neither catches the other's case: a
   phone-width window reports a fine pointer when it is a narrow desktop window,
   and a tablet at 1024px reports a coarse one. Both want the bigger target.
   Kept as separate blocks rather than a comma list — an unsupported media
   feature invalidates the WHOLE query, so pairing them would lose the
   width case on any engine that does not know `pointer`. */
@media (max-width: 768px) {
  .ds .pager .pages a, .ds .pager .pages span:not(.gap) {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* the browser default is 13×13 — unmissable with a mouse, unhittable with a
     thumb, and these are the bulk-select ticks a reviewer taps down a whole list */
  .ds input[type="checkbox"], .ds input[type="radio"] { width: 20px; height: 20px; }
  /* nowrap is right for "सबै छान्नुहोस्" and wrong for the anomaly
     acknowledgement beside it, which is a full sentence — at 320px it ran
     straight out of the page and was the only horizontal overflow left. The
     tick keeps its size while the sentence wraps under itself. */
  .ds .bulkbar__chk { min-height: 44px; white-space: normal; align-items: flex-start; }
  .ds .bulkbar__chk input { flex: none; margin-top: 2px; }
  .ds .dbtn--icon, .ds .rows--ruled > .trow .dbtn--icon { min-width: 44px; min-height: 44px; }
}
@media (pointer: coarse) {
  .ds .pager .pages a, .ds .pager .pages span:not(.gap) {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .ds input[type="checkbox"], .ds input[type="radio"] { width: 20px; height: 20px; }
  .ds .bulkbar__chk { min-height: 44px; white-space: normal; align-items: flex-start; }
  .ds .bulkbar__chk input { flex: none; margin-top: 2px; }  .ds .dbtn--icon, .ds .rows--ruled > .trow .dbtn--icon { min-width: 44px; min-height: 44px; }
}

/* ── registers become cards at tablet portrait ──────────────────────────────
   768px, not 680: at 700px a six-column list still had ~110px per column and
   was breaking an address mid-word ("sita.adhikari@exa / mple.gov.np"). 768 is
   iPad portrait, the width these registers are actually read at in an office,
   and a three-column list carded there is more legible than a six-column one
   squeezed. The wider .rows--ruled registers card earlier still, at 900 — see
   their own block above.

   !important for the same reason noted below: every list declares its
   grid-template-columns inline, and an inline style outranks any media query. */
@media (max-width: 768px) {
  .ds .trowh { display: none; }
  /* Three registers declare a pixel floor inline (enumerator/review 720px,
     reports/palika 900px) so their columns keep a usable width while the
     .tablescroll around them scrolls. Carded, that floor is only harm: the
     cards themselves would be 720–900px wide inside a 375px scroll box, and the
     stack would scroll sideways for no reason. !important because the
     declaration is inline and nothing else can reach it. */
  .ds .rows { min-width: 0 !important; }
  .ds .trow { grid-template-columns: 1fr !important; gap: 5px; padding: 13px 16px; }
  .ds .trow > * { display: flex; justify-content: space-between; gap: 12px; }
  .ds .trow .u-name { justify-content: flex-start; }
  .ds .trow [data-l]::before { content: attr(data-l); font-size: 11px; color: var(--ds-subtle); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
}

/* responsive */
/* !important on .kpis/.stats/.trow column counts: several pages set their own
   grid-template-columns inline (e.g. reports/categorization.html, master/hub.html)
   to get non-default column counts on wide screens. An inline style beats any
   plain class selector regardless of media query, so without !important those
   pages would ignore these breakpoints entirely and stay multi-column on phones. */
@media (max-width: 1120px) { .ds .grid, .ds .grid--half { grid-template-columns: 1fr; } .ds .kpis { grid-template-columns: repeat(2, 1fr) !important; } .ds .stats { grid-template-columns: repeat(2, 1fr) !important; } }
@media (max-width: 680px) {
  .ds .kpis, .ds .stats { grid-template-columns: 1fr !important; }
  .ds .f1, .ds .f2, .ds .f3, .ds .f4, .ds .f5, .ds .f6, .ds .f7, .ds .f8, .ds .f9, .ds .f10, .ds .f11 { grid-column: span 12; }
  .ds .fitcell { flex-basis: 100%; max-width: none; }
  /* max-width alone was not enough: flex-basis stayed 140px, so three selects
     still shared a phone's width and "बूढानीलकण्ठ नगरपालिका" was truncated to
     "बूढानीलकण्ठ नगरपालि" in every one of them. A 200px basis gives two per row
     on a large phone and one on a small one. */
  .ds .filterbar > div { max-width: none; flex-basis: 200px; }
  .ds .kv { grid-template-columns: 120px 1fr; }
  .ds .pagehead { align-items: flex-start; }
  .ds .idcard__top { flex-wrap: wrap; }
  .ds .idcard .fields .pair { min-width: 100%; }
}

/* Topbar: page titles (often longer Nepali headings) and the signed-in user's
   name are both `flex: none`, so on narrow phones they don't shrink — they
   force the topbar to overflow sideways instead of wrapping. Truncate the
   title and drop the name text, keeping just the avatar, before that happens. */
@media (max-width: 560px) {
  .topbar { gap: 10px; padding: 0 14px; }
  .topbar-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; }
  .user-menu span:not(.avatar) { display: none; }
  .user-menu { padding: 5px; gap: 0; border-color: transparent; }
  /* the role stays visible as its icon — it is the one thing the shared-machine
     case needs at a glance — but the word goes, like the user's name above */
  .role-chip { padding: 5px; gap: 0; }
  .role-chip > span { display: none; }
}

/* Nepali datepicker (vendor/nepali.datepicker.v5.0.6) — stacking fix.
   The vendor popup is z-index:1000, which is BELOW every piece of fixed shell
   chrome we put around it: .topbar (1030), .sidebar (1040), .gov-strip (1050).
   A field far enough down the page makes the picker open upward, and its
   month/year header — the only way to change year — slid under the topbar and
   became invisible and unclickable; the जन्म मिति field on Register Farmer hit
   this every time. Raised above all three so a transient popup always paints
   over the chrome, which is what a popup is meant to do.
   Overridden here rather than in the vendored file: those are kept
   byte-identical to upstream (see the pre-commit config).

   `body .ndp-container`, not `.ndp-container`: base/shell.html loads the
   datepicker stylesheet AFTER this one, so a plain class selector ties on
   specificity and loses on source order. One element up the chain settles it
   without reordering the global stylesheet links, which would silently flip
   every other vendor-vs-app tie at the same time. */
body .ndp-container { z-index: 1060; }

/* Password show/hide (login + all account-creation forms). */
.pw-row { position: relative; }
.pw-row .form-control { padding-right: 40px; }
.pw-toggle { position: absolute; right: 8px; top: 7px; background: none; border: 0; color: var(--ds-subtle); cursor: pointer; padding: 5px; display: inline-flex; }
.pw-toggle svg { width: 17px; height: 17px; }

/* ============================================================================
   .ds--comfy — the field-entry TOUCH scale
   ----------------------------------------------------------------------------
   Carried by every wizard section page (they are one continuous flow, so a
   per-page opt-in was what let type size jump between consecutive steps).
   Same palette, same fonts, same components, same body type — what this adds is
   touch: every input, select, button and tick becomes a ~48px target, with
   labels and help text sized to match, which is what makes the form usable
   one-handed on a phone in a field.
   Type scale is NOT part of this any more. Base .ds is 15px/1.5 to match the
   shell chrome, so the declaration below is deliberately the same value — kept
   explicit rather than deleted so that raising the entry scale later does not
   need the rest of this block rediscovered.
   Sits last in the file deliberately: these rules and the base .ds ones carry
   identical specificity, so source order is what decides.
   ========================================================================== */
.ds--comfy { font-size: 15px; line-height: 1.5; }
.ds--comfy .form-control, .ds--comfy .form-select,
.ds--comfy input[type="time"], .ds--comfy input[type="date"], .ds--comfy input[type="number"],
.ds--comfy input[type="tel"], .ds--comfy input[type="text"], .ds--comfy input[type="email"] {
  font-size: 15.5px; min-height: 48px; padding: 11px 12px; border-radius: var(--r-sm);
}
.ds--comfy .form-select { padding-right: 34px; }
.ds--comfy textarea.form-control { min-height: 72px; }
.ds--comfy .flabel { font-size: 14px; margin-bottom: 5px; color: var(--ds-ink); }
/* The example hints under ambiguous fields — big enough to actually be read,
   and never crowding the input above them. */
.ds--comfy .fhelp { font-size: 13px; line-height: 1.45; margin-top: 5px; }
.ds--comfy .ferr { font-size: 13px; }
.ds--comfy .dbtn { font-size: 15px; min-height: 48px; padding: 12px 17px; }
.ds--comfy .dbtn--sm { font-size: 14px; min-height: 44px; padding: 10px 14px; }
.ds--comfy .dbtn svg { width: 18px; height: 18px; }
.ds--comfy .fcheck { font-size: 15px; min-height: 48px; gap: 10px; }
.ds--comfy .fcheck input[type="checkbox"], .ds--comfy .fcheck input[type="radio"],
.ds--comfy .chkitem input[type="checkbox"], .ds--comfy .chkitem input[type="radio"] {
  width: 22px; height: 22px; flex: none; margin: 0; accent-color: var(--brand-primary);
}
/* Choice tiles: the same .chkgrid/.chkitem picker every other page uses, grown
   into tappable bordered tiles that can carry a one-line explanation. */
.ds--comfy .chkgrid { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 10px; }
.ds--comfy .chkitem {
  align-items: flex-start; gap: 11px; font-size: 15px; font-weight: 600; min-height: 48px;
  padding: 12px 13px; border: 1px solid var(--ds-border-strong); border-radius: var(--r-sm);
  background: var(--ds-surface);
}
.ds--comfy .chkitem:hover { border-color: var(--brand-primary); }
.ds--comfy .chkitem .fhelp { display: block; margin-top: 3px; font-weight: 400; }
.ds--comfy .panel__hd { padding: 13px 15px; }
.ds--comfy .panel__hd h3 { font-size: 16px; }
.ds--comfy .panel__hd .sub { font-size: 13px; }
.ds--comfy .panel__bd { padding: 15px; }
.ds--comfy .sectlabel { font-size: 12.5px; }
.ds--comfy .entrycard { padding: 14px 15px; }
.ds--comfy .entrycard__ttl { font-size: 15px; }
.ds--comfy .callout p { font-size: 14px; }
.ds--comfy .callout b { font-size: 15px; }
.ds--comfy .kv > dt, .ds--comfy .kv > dd { font-size: 14.5px; padding: 9px 0; }
/* Dashboard readouts at the same scale — a KPI nobody can read across a desk
   is not a KPI. The tiles are already links, so they also want the 48px floor. */
.ds--comfy .pagehead h1 { font-size: 21px; }
.ds--comfy .pagehead p { font-size: 14px; }
.ds--comfy .kpi { padding: 18px 18px 16px; }
.ds--comfy .kpi__top { font-size: 14px; }
.ds--comfy .kpi__val { font-size: 32px; }
.ds--comfy .kpi__sub { font-size: 13.5px; }
.ds--comfy .stat b { font-size: 20px; }
.ds--comfy .stat span { font-size: 13px; }
.ds--comfy .holdgroup__hd { font-size: 15px; }
.ds--comfy .holdgroup__hd span { font-size: 13px; }
.ds--comfy .pill { font-size: 12.5px; padding: 5px 11px; }
.ds--comfy .link { font-size: 14px; min-height: 44px; align-items: center; }
.ds--comfy .cellmuted { font-size: 14px; }
.ds--comfy .empty b { font-size: 15.5px; }
.ds--comfy .empty p { font-size: 14px; }
/* Phones: the triplet keeps its three boxes side by side (they hold 1-3 digits
   each) but trades horizontal padding for the space, so a 48px-tall box still
   fits three-across at 320px instead of overflowing the card. */
@media (max-width: 520px) {
  .ds--comfy .triplet input { padding-left: 7px; padding-right: 7px; text-align: center; }
  .ds--comfy .entrycard { padding: 13px 12px; }
  .ds--comfy .panel__bd { padding: 13px 12px; }
  .ds--comfy .factions .dbtn { flex: 1 1 100%; justify-content: center; }
}

/* ── किसान photo cropper (js/photo-crop.js) ───────────────────────────────
   The server crops around a detected face on its own; this is the override for
   the ones it gets wrong. Square only, because the stored photo is square. */
.pcrop { margin-top: 10px; max-width: 320px; }
/* overflow stays on the stage — it is what keeps the box's 9999px dimming shadow
   inside the widget instead of greying the whole page. The padding is new: with
   the box opening on the whole image, its corner grips sit exactly on the image
   edge, and a clip there cut them to quarter-dots nobody could hit with a thumb.
   The canvas is the box's positioning origin, aligned to the image, so the
   padding moves nothing the JS computes. */
.pcrop__stage { position: relative; display: inline-block; line-height: 0; padding: 10px;
                border: 1px solid var(--ds-border); border-radius: var(--r-sm); overflow: hidden; }
.pcrop__canvas { position: relative; line-height: 0; }
/* The widget's own [hidden], rather than trusting whatever page it lands on to
   ship Bootstrap's: .pcrop__bar is display:flex and .dbtn inline-flex, and either
   beats the browser's default [hidden] — which left the slider and the reset
   button on screen in the preview-failed state. */
.pcrop [hidden] { display: none !important; }
.pcrop__img { max-width: 300px; height: auto; display: block; user-select: none; }
.pcrop__box { position: absolute; box-sizing: border-box; cursor: move;
              border: 2px solid var(--brand-primary); border-radius: 2px;
              /* the ring is what makes the discarded area obvious at a glance */
              box-shadow: 0 0 0 9999px rgba(11, 21, 36, 0.45); touch-action: none; }
/* Corner grips. The box was resizable only by the slider, which scales about the
   centre — fine for framing a face, useless for trimming the white margin off a
   scanned signature, where what you want is to pull ONE edge in. Each grip
   anchors the opposite corner. 18px hit area on a 10px dot: this is dragged with
   a thumb on a phone. */
.pcrop__h { position: absolute; width: 18px; height: 18px; touch-action: none; }
.pcrop__h::after { content: ""; position: absolute; inset: 4px;
                   background: var(--ds-surface); border: 2px solid var(--brand-primary);
                   border-radius: 50%; }
.pcrop__h--nw { left: -9px; top: -9px; cursor: nwse-resize; }
.pcrop__h--ne { right: -9px; top: -9px; cursor: nesw-resize; }
.pcrop__h--sw { left: -9px; bottom: -9px; cursor: nesw-resize; }
.pcrop__h--se { right: -9px; bottom: -9px; cursor: nwse-resize; }
.pcrop__bar { display: flex; align-items: center; gap: 10px; margin-top: 8px; flex-wrap: wrap; }
.pcrop__mode { font-size: 12px; color: var(--ds-muted); }
.pcrop__size { flex: 1 1 120px; min-width: 110px; }

/* ── नक्सा (verification workspace map) ─────────────────────────────────────
   No mapping library: the shapes are pre-projected SVG paths, so everything
   here is ordinary CSS on <path> and <text>. --npmap-empty is a token rather
   than a literal because "no farmers recorded" must stay clearly distinct from
   the lightest step of the choropleth scale, in both themes. */
/* The three fills must stay tellable apart at a glance:
     --npmap-ctx    surrounding areas, out of play — clearly the greyest
     --npmap-empty  IN play, but no farmers recorded — near white, "lit"
     the STEPS scale in nepal-map.js — in play, with farmers
   Getting ctx and empty too close was the first version's mistake: a district
   with no records looked exactly like a neighbouring province. */
:root { --npmap-empty: #fbfcfe; --npmap-stroke: #ffffff;
        --npmap-ctx: #e3e8f0; --npmap-ctx-line: #ccd3e0; }
.npmap { position: relative; }
/* the selects above the map — one control with it, not a parallel filter */
.npmap__filters { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.npmap__filter { display: flex; flex-direction: column; gap: 4px; flex: 1 1 200px; min-width: 170px; }
.npmap__filter > span { font-size: 12px; font-weight: 600; color: var(--ds-muted); }
.npmap__filter select { font-size: 13px; padding: 7px 9px; border: 1px solid var(--ds-border-strong);
                        border-radius: var(--r-sm); background: #fff; width: 100%; }
.npmap__filter select:disabled { background: var(--ds-bg, #f4f6f9); color: var(--ds-subtle); }

.npmap__bar { display: flex; align-items: center; justify-content: space-between;
              gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.npmap__crumbs { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.npmap__crumb { background: none; border: 0; padding: 3px 7px; border-radius: var(--r-sm);
                font: inherit; font-size: 13px; font-weight: 600; color: var(--np-blue);
                cursor: pointer; }
.npmap__crumb:hover { background: var(--np-blue-softer); text-decoration: underline; }
.npmap__crumb:last-child { color: var(--ds-ink); cursor: default; }
.npmap__sep { color: var(--ds-subtle); font-size: 13px; }
.npmap__legend { display: flex; align-items: center; gap: 3px; }
.npmap__swatch { width: 22px; height: 10px; border-radius: 1px; }
.npmap__legend-lbl { font-size: 11px; color: var(--ds-muted); margin: 0 4px; }

/* `auto`, not a fixed 250px: the ward panel is empty until a स्थानीय तह is
   clicked, and a fixed column would hold a quarter of the width blank until
   then — the national map would sit squeezed beside nothing. */
.npmap__body { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; }
@media (max-width: 860px) { .npmap__body { grid-template-columns: 1fr; } }
.npmap__stage { position: relative; min-width: 0; }
.npmap__zoom { position: absolute; z-index: 4; top: 8px; left: 8px; display: grid; gap: 3px; }
.npmap__zoom button { width: 27px; height: 27px; border: 1px solid var(--ds-border-strong);
                      background: #fff; border-radius: var(--r-sm); font-size: 15px; line-height: 1;
                      color: var(--ds-ink); cursor: pointer; box-shadow: var(--sh-sm); }
.npmap__zoom button:hover { background: var(--np-blue-softer); border-color: var(--np-blue); }

/* The areas AROUND the one being drilled into. Drawn, named, and deliberately
   inert: they are there so a district is seen inside its country rather than
   floating alone, but clicking one would mean guessing whether the reader meant
   to jump sideways or up, and the breadcrumb already answers that. */
.npmap__ctx { fill: var(--npmap-ctx); stroke: var(--npmap-ctx-line);
              stroke-width: var(--npmap-sw, 0.6px); pointer-events: none; }
.npmap__ctxlbl { pointer-events: none; text-anchor: middle; font-size: var(--npmap-fs, 10px);
                 font-weight: 500; fill: var(--ds-subtle); opacity: 0.75; }
.npmap__svg { display: block; width: 100%; height: auto; max-height: 62vh; }

/* --npmap-sw / --npmap-fs are set by nepal-map.js from the viewBox in view:
   SVG units are not screen pixels, and a border that reads as a hairline over
   the whole country would be a fat band drawn around one स्थानीय तह. */
.npmap__area { stroke: var(--npmap-stroke); stroke-width: calc(var(--npmap-sw, 0.6px) * 1.5);
               cursor: pointer; transition: fill 120ms ease, stroke-width 120ms ease; }
/* the UA ring is drawn around the bounding BOX of an SVG path, which for a
   district is a rectangle swallowing its neighbours — the keyboard indicator
   below replaces it with something shaped like the actual area */
.npmap__area:focus { outline: none; }
.npmap__area:hover { stroke-width: calc(var(--npmap-sw, 0.6px) * 2.6); stroke: var(--ds-ink); }
/* focus must be visible on a shape of ANY fill, so it is the stroke that changes */
.npmap__area:focus-visible { outline: none; stroke: var(--brand-primary);
                             stroke-width: calc(var(--npmap-sw, 0.6px) * 4); }
/* labels are decoration over a clickable shape — they must never eat the click */
/* The halo that keeps a label readable over any fill must scale WITH the label:
   stroke-width is in user units too, so a fixed 2.5 was a hairline over the
   whole country and a blob that swallowed the glyphs at municipality zoom. */
/* 0.28em, not 0.28 * --npmap-fs: nepal-map.js sizes a cramped label DOWN from
   that variable (a city-centre वडा), and a halo tied to the variable rather than
   to the element's own type came out a blob around the shrunken glyphs. */
.npmap__lbl, .npmap__num { pointer-events: none; text-anchor: middle; paint-order: stroke;
                           stroke: rgba(255,255,255,0.9);
                           stroke-width: 0.28em;
                           stroke-linejoin: round; }
.npmap__lbl { font-size: var(--npmap-fs, 10px); font-weight: 600; fill: var(--ds-ink); }
.npmap__num { font-size: var(--npmap-fs, 10px); font-weight: 700; fill: var(--np-blue-dark); }

/* Where the build gives a dissolved `outline`, the fill carries no stroke and
   the border is its own path — otherwise the internal district rings inside a
   province's unioned fill get stroked and the province looks subdivided. */
.npmap__area--nostroke { stroke: none; }
.npmap__border, .npmap__ctxborder { fill: none; pointer-events: none;
                                    stroke-width: calc(var(--npmap-sw, 0.6px) * 1.5); }
.npmap__border { stroke: var(--npmap-stroke); }
.npmap__ctxborder { stroke: var(--npmap-ctx-line); }

/* The MARKED area — the वडा the reader clicked, which the table below is now
   cut to. It is not a hover or a focus ring: it says "these numbers are this
   shape", and it has to survive a repaint and read over any fill in the scale. */
.npmap__area--on { stroke: var(--brand-primary);
                   stroke-width: calc(var(--npmap-sw, 0.6px) * 4); }

.npmap__tip { position: absolute; z-index: 5; pointer-events: none; background: var(--ds-ink);
              color: #fff; border-radius: var(--r-sm); padding: 6px 9px; font-size: 12px;
              line-height: 1.5; box-shadow: var(--sh-md); display: grid; gap: 1px; }
.npmap__tip b { font-size: 12.5px; }
.npmap__tip-bad { color: #ffb4ad; }

.npmap__panel { width: 250px; max-width: 100%; border: 1px solid var(--ds-border);
                border-radius: var(--r-md); background: var(--ds-surface, #fff);
                min-height: 60px; overflow: hidden; }
.npmap__panel:empty { display: none; }
.npmap__panel-hd { display: flex; align-items: baseline; justify-content: space-between;
                   gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--ds-border);
                   font-size: 13px; }
.npmap__wards { list-style: none; margin: 0; padding: 0; max-height: 300px; overflow-y: auto; }
.npmap__wards a { display: flex; justify-content: space-between; gap: 8px; padding: 7px 12px;
                  font-size: 13px; color: var(--ds-ink); text-decoration: none;
                  border-bottom: 1px dashed var(--ds-border); }
.npmap__wards a:hover { background: var(--np-blue-softer); color: var(--np-blue); }
.npmap__all { display: block; padding: 9px 12px; font-size: 12.5px; font-weight: 600;
              color: var(--np-blue); text-decoration: none; }
.npmap__all:hover { text-decoration: underline; }
.npmap__loading, .npmap__err { padding: 12px; font-size: 13px; color: var(--ds-muted); margin: 0; }

/* ── the नक्सा and the table below it are one control ───────────────────────
   Clicking an area re-cuts the geography table (verification_geo_panel swaps
   the fragment in), so the table has to SAY which area it is showing — a table
   that quietly changed meaning under a map would be worse than an unlinked one.
   The chip is that sentence, and the button beside it is the way back out. */
.geo-hd { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.geo-pin { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px;
           border-radius: 999px; font-size: 12.5px; font-weight: 600;
           color: var(--np-blue-dark); background: var(--np-blue-soft);
           border: 1px solid var(--np-blue-line); }
.geo-pin__clear { background: none; border: 0; padding: 2px 4px; font: inherit; font-size: 12.5px;
                  font-weight: 600; color: var(--np-blue); cursor: pointer; text-decoration: underline; }
.geo-pin__clear:hover { color: var(--np-blue-dark); }
/* the fragment is being fetched: dim it rather than empty it, so the numbers do
   not flash away and back on every click across the map */
[data-geo-panel][aria-busy="true"] { opacity: 0.55; transition: opacity 120ms ease; }
