/* =====================================================================
   UB ERP — design system
   ---------------------------------------------------------------------
   The palette and typography are taken from uzhavarbumi.com (requirement
   23), read out of its own stylesheet rather than guessed:

     brand green   #76A543   .btn.btn-success, footer a, .text-success
     active green  #7CA133   .date-btn.active, .property-type-btn.active
     section green #75A543   #section-products, #team
     accent blue   #0099E5   .logo a, .copy_right, newsletter button
     warm orange   #FF5722   nav hover
     ink           #292A2B   h2.title-w3ls, h3.title-w3ls
     body font     'Lato', sans-serif           (400, 900)
     heading font  'Montserrat', sans-serif     (400, 500), letter-spacing 1px
     radii         4px images/inputs, 10px cards, 20px pill groups
     card shadow   0 2px 6px rgba(0,0,0,.08)

   An accounting screen is read for hours, so the surface is quieter than
   the marketing site: green is kept for identity, action and positives,
   and the working surface stays near-white.
   ===================================================================== */

:root {
  /* brand */
  --brand:        #76A543;
  --brand-600:    #6B9A3B;
  --brand-700:    #5C8632;
  --brand-800:    #4A6C28;
  --brand-100:    #EDF4E4;
  --brand-050:    #F6FAF1;
  --accent:       #0099E5;
  --accent-700:   #0077B5;
  --accent-050:   #E8F6FE;
  --warn:         #FF5722;
  --warn-050:     #FFF1EC;
  --danger:       #D13030;
  --danger-050:   #FDECEC;
  --amber:        #B7791F;
  --amber-050:    #FDF6E7;

  /* surface */
  --ink:          #292A2B;
  --ink-2:        #4A4C4E;
  --ink-3:        #6B6E70;
  --ink-4:        #93989B;
  --line:         #E4E7E2;
  --line-2:       #EFF1ED;
  --bg:           #F7F8F5;
  --surface:      #FFFFFF;
  --surface-2:    #FBFCFA;
  /* Zebra striping was a literal, so alternate rows stayed near-white in
     dark mode — on the widest tables in the system. It is a token now. */
  --zebra:        #FCFDFB;

  /* type */
  --font-body:    'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-head:    'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* Figures line up only in a tabular face. Money is never in Lato. */
  --font-num:     'Segoe UI', 'Roboto Mono', 'Consolas', ui-monospace, monospace;

  --r-sm: 4px;
  --r-md: 8px;
  --r-lg: 10px;
  --r-pill: 20px;

  /* A plain <select> is never given appearance:none-and-nothing-else — the
     OS then draws its own dropdown button inside our rounded box, and that
     native button's corner does not match --r-sm. The fix is to remove the
     native chrome entirely and paint the arrow ourselves, so the whole
     control — box, border and arrow — is one rounded shape. The arrow colour
     is a token, like everything else, because it has to flip with the theme
     the same way --ink-3 does; a literal here would leave it wrong in dark
     mode the way a literal zebra stripe once did. */
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236B6E70' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  --shadow-1: 0 1px 2px rgba(0,0,0,.05);
  --shadow-2: 0 2px 6px rgba(0,0,0,.08);
  --shadow-3: 0 8px 24px rgba(0,0,0,.12);

  --sidebar-w: 236px;
  --header-h: 56px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-head);
  font-weight: 500;
  letter-spacing: .01em;
  margin: 0 0 .5rem;
  color: var(--ink);
}
h1 { font-size: 1.35rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }

a { color: var(--accent-700); text-decoration: none; }
a:hover { text-decoration: underline; }

code, kbd, .num, .mono { font-family: var(--font-num); }

/* ---------------------------------------------------------------------
   Layout: fixed sidebar, sticky header, scrolling main
   --------------------------------------------------------------------- */
.app { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }

.sidebar {
  background: var(--ink);
  color: #D9DDD6;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}
.sidebar-brand {
  display: flex; align-items: center; gap: .6rem;
  padding: .85rem 1rem;
  border-bottom: 1px solid rgba(255,255,255,.08);
  position: sticky; top: 0; background: var(--ink); z-index: 2;
}
.sidebar-brand .mark {
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--brand); color: #fff;
  display: grid; place-items: center;
  font-family: var(--font-head); font-weight: 500; font-size: .82rem;
  flex: none;
}
.sidebar-brand .name {
  font-family: var(--font-head); font-weight: 500;
  color: #fff; font-size: .95rem; letter-spacing: .04em;
  text-transform: uppercase; line-height: 1.1;
}
.sidebar-brand .sub { display: block; font-size: .62rem; color: var(--ink-4); letter-spacing: .08em; }

.nav-group { padding: .5rem 0 .25rem; }
.nav-group > .label {
  font-size: .64rem; text-transform: uppercase; letter-spacing: .12em;
  color: #7C817A; padding: .5rem 1rem .3rem; font-family: var(--font-head);
}

/* Each section is a <details>, so it collapses with no script at all. The
   default disclosure triangle is replaced by a caret on the right: the
   triangle sits where the section name should start and pushes seven
   labels out of alignment with the items beneath them. Both prefixes are
   needed — `::marker` covers Firefox, the WebKit pseudo-element the rest. */
details.nav-group > summary.label {
  display: flex; align-items: center; cursor: pointer;
  list-style: none; user-select: none;
}
details.nav-group > summary.label::-webkit-details-marker { display: none; }
details.nav-group > summary.label::marker { content: ''; }
details.nav-group > summary.label:hover { color: #C9CEC4; }
details.nav-group > summary.label:focus-visible {
  outline: 2px solid var(--brand); outline-offset: -2px;
}
.nav-group > .label .chev {
  margin-left: auto; width: .5rem; height: .5rem; flex: none;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg) translate(-1px, -1px);
  transition: transform .12s ease;
}
details.nav-group[open] > summary.label .chev {
  transform: rotate(45deg) translate(-1px, -1px);
}
.sidebar a.nav-item {
  display: flex; align-items: center; gap: .55rem;
  padding: .42rem 1rem; color: #C9CEC4; font-size: .855rem;
  border-left: 3px solid transparent; text-decoration: none;
}
.sidebar a.nav-item:hover { background: rgba(255,255,255,.05); color: #fff; }
.sidebar a.nav-item.is-active {
  background: rgba(118,165,67,.16); color: #fff;
  border-left-color: var(--brand);
}
.sidebar a.nav-item .ico { width: 16px; text-align: center; opacity: .8; flex: none; }

/* A screen that is not built yet. Shown, but plainly not a link — the menu
   is a map of the system, and pretending a room exists is worse than
   labelling it unfinished. */
.sidebar .nav-item.is-soon {
  display: flex; align-items: center; gap: .55rem;
  padding: .42rem 1rem; font-size: .855rem;
  border-left: 3px solid transparent;
  color: #6A6F66; cursor: default;
}
.sidebar .nav-item.is-soon .ico { width: 16px; text-align: center; opacity: .45; flex: none; }
.sidebar .nav-item.is-soon .soon {
  margin-left: auto; font-size: .58rem; letter-spacing: .08em;
  text-transform: uppercase; color: #5E635A;
  border: 1px solid #3A3F37; border-radius: var(--r-pill);
  padding: 0 .38rem; font-family: var(--font-head);
}

.main { display: flex; flex-direction: column; min-width: 0; }

.topbar {
  height: var(--header-h);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 1rem;
  padding: 0 1.1rem;
  position: sticky; top: 0; z-index: 20;
}
.topbar .crumb { font-family: var(--font-head); font-size: .95rem; color: var(--ink); }
.topbar .spacer { flex: 1; }
.topbar .chip {
  font-size: .76rem; background: var(--brand-050); color: var(--brand-800);
  border: 1px solid var(--brand-100); border-radius: var(--r-pill);
  padding: .2rem .7rem; white-space: nowrap;
}
.topbar .chip.year { background: var(--accent-050); color: var(--accent-700); border-color: #CDE9FA; }

.content { padding: 1.1rem 1.25rem 3rem; max-width: 1500px; width: 100%; }

/* ---------------------------------------------------------------------
   Page header
   --------------------------------------------------------------------- */
.page-head { display: flex; align-items: flex-start; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; }
.page-head h1 { margin: 0; }
.page-head .lede { color: var(--ink-3); font-size: .875rem; margin: .15rem 0 0; max-width: 62ch; }
.page-head .actions { margin-left: auto; display: flex; gap: .5rem; flex-wrap: wrap; }

/* ---------------------------------------------------------------------
   Cards
   --------------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  margin-bottom: 1rem;
  overflow: hidden;
}
.card-head {
  padding: .7rem 1rem; border-bottom: 1px solid var(--line-2);
  display: flex; align-items: center; gap: .75rem;
  background: var(--surface-2);
}
.card-head h2, .card-head h3 { margin: 0; font-size: .95rem; }
.card-head .spacer { flex: 1; }
.card-body { padding: 1rem; }
.card-body.tight { padding: 0; }
.card-foot { padding: .7rem 1rem; border-top: 1px solid var(--line-2); background: var(--surface-2); }

/* Statistic tiles */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: .8rem; margin-bottom: 1rem; }
.tile {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: .85rem 1rem; box-shadow: var(--shadow-1);
  border-left: 3px solid var(--brand);
}
.tile .k { font-size: .7rem; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-3); font-family: var(--font-head); }
.tile .v { font-family: var(--font-num); font-size: 1.4rem; font-weight: 600; color: var(--ink); line-height: 1.25; }
.tile .sub { font-size: .76rem; color: var(--ink-4); }
.tile.accent { border-left-color: var(--accent); }
.tile.warn   { border-left-color: var(--warn); }
.tile.danger { border-left-color: var(--danger); }

/* ---------------------------------------------------------------------
   Tables — the workhorse of an accounting screen
   --------------------------------------------------------------------- */
.table-wrap { overflow-x: auto; }
table.data { width: 100%; border-collapse: collapse; font-size: .855rem; }
table.data th {
  text-align: left; font-family: var(--font-head); font-weight: 500;
  font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-3); background: var(--surface-2);
  padding: .5rem .65rem; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 1; white-space: nowrap;
}
table.data td { padding: .45rem .65rem; border-bottom: 1px solid var(--line-2); vertical-align: top; }
table.data tbody tr:hover { background: var(--brand-050); }
table.data tr.is-total td { font-weight: 700; background: var(--surface-2); border-top: 2px solid var(--line); }
table.data tr.is-subtotal td { font-weight: 600; background: var(--surface-2); }
table.data tr.is-group td { background: var(--brand-050); font-weight: 600; }
/* A journal line carrying an amount but no ledger — the server would drop
   it, so it is marked before it can be submitted. */
table.data tr.needs-ledger td { background: var(--danger-050); }
table.data tr.needs-ledger .f-ledger { border-color: var(--danger); }

/* Not wrong, just worth a second look — an overdue approval, or a line
   carrying both IGST and CGST/SGST. Never blocks the submit. */
table.data tr.warn-row td { background: var(--warn-050); }

/* A checkbox that reads as one control with its label. */
label.check { display: inline-flex; align-items: center; gap: .35rem; font-size: .8rem;
              white-space: nowrap; cursor: pointer; }
label.check input { margin: 0; }

/* A set of choices that reads as a list rather than a row. `label.check` is
   inline-flex and nowrap at (0,1,1), so a wrapper class on its own loses and
   the options run together mid-line — the same way `.perm-row` lost below.
   Naming the element too makes this (0,2,1). */
.radio-stack label.check { display: flex; white-space: normal; margin-bottom: .3rem; }

/* Figures: right aligned, tabular, never wrapped. */
.num, td.num, th.num { text-align: right; font-family: var(--font-num); font-variant-numeric: tabular-nums; white-space: nowrap; }
td.num.neg, .neg { color: var(--danger); }
td.dim { color: var(--ink-4); }
.w-min { width: 1%; white-space: nowrap; }

/* Zebra for long registers */
table.data.zebra tbody tr:nth-child(even) { background: var(--zebra); }
table.data.zebra tbody tr:nth-child(even):hover { background: var(--brand-050); }

/* Compact variant for dense registers */
table.data.compact th, table.data.compact td { padding: .3rem .5rem; font-size: .8rem; }

/* A numeric HEADING has to sit over its own numbers.
   `table.data th` is (0,1,2) and sets text-align:left, so the `th.num` in the
   rule above — (0,1,1) — silently lost, and every numeric column in the
   system was left-aligned above right-aligned figures. Both selectors below
   are (0,2,2)/(0,3,2), and they come after `table.data.compact th` so a tie
   is resolved in their favour rather than by luck. Same rule as the
   `table.data.compact th` note in CLAUDE.md: anything weaker loses. */
table.data th.num,
table.data.compact th.num { text-align: right; }

/* A totals row is a footer, not a second heading.
   `table.data th` carries `position: sticky; top: 0` for the real heading,
   and a tfoot th inherited it — so inside a scrolling .table-wrap the totals
   row floated up and sat on top of the headings. Pinned to the BOTTOM
   instead, which is where a total belongs and where it stays useful while a
   long table is scrolled. `bottom: 0` needs the `top` from the heading rule
   unset, or the two fight and top wins. */
table.data tfoot th {
  position: sticky; top: auto; bottom: 0;
  background: var(--surface-2); color: var(--ink);
  font-size: .8rem; text-transform: none; letter-spacing: 0; font-weight: 700;
  border-top: 2px solid var(--line); border-bottom: none;
}

/* ---------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: .4rem;
  font-family: var(--font-head); font-size: .82rem; font-weight: 500;
  letter-spacing: .02em;
  padding: .45rem .95rem; border-radius: var(--r-sm);
  border: 1px solid transparent; cursor: pointer;
  background: var(--surface); color: var(--ink-2);
  border-color: var(--line); text-decoration: none;
  transition: background .15s, border-color .15s, color .15s;
  white-space: nowrap;
}
.btn:hover { background: var(--surface-2); border-color: var(--ink-4); text-decoration: none; }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn:disabled, .btn.is-disabled { opacity: .5; cursor: not-allowed; }

.btn-primary { background: var(--brand); border-color: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-700); border-color: var(--brand-700); color: #fff; }
.btn-accent { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-accent:hover { background: var(--accent-700); border-color: var(--accent-700); color: #fff; }
.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover { background: #B02525; color: #fff; }
.btn-ghost { background: transparent; border-color: transparent; color: var(--ink-3); }
.btn-ghost:hover { background: var(--line-2); }
.btn-sm { padding: .25rem .6rem; font-size: .76rem; }
.btn-lg { padding: .6rem 1.4rem; font-size: .9rem; }
.btn-block { width: 100%; justify-content: center; }

/* ---------------------------------------------------------------------
   Forms
   --------------------------------------------------------------------- */
.field { margin-bottom: .85rem; }
.field > label, label.lbl {
  display: block; font-family: var(--font-head); font-size: .74rem;
  text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3);
  margin-bottom: .25rem; font-weight: 500;
}
/* A label may carry a computed aside — the invoice value beside the
   capitalised cost. The label itself is uppercased and letter-spaced,
   which is right for a field name and wrong for a sentence with figures
   in it, so the aside opts out. More specific than `.field > label`, or
   it would lose to it silently. */
.field > label .label-note {
  text-transform: none; letter-spacing: normal; font-weight: 400;
  margin-left: .3rem;
}
.field .hint { font-size: .76rem; color: var(--ink-4); margin-top: .22rem; }
.field .err { font-size: .78rem; color: var(--danger); margin-top: .22rem; }

input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=tel], input[type=search], select, textarea {
  width: 100%; font-family: var(--font-body); font-size: .875rem;
  padding: .42rem .6rem; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface); color: var(--ink);
  transition: border-color .15s, box-shadow .15s;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(118,165,67,.15);
}
input.is-error, select.is-error { border-color: var(--danger); }
input[type=number], input.num-input { text-align: right; font-family: var(--font-num); font-variant-numeric: tabular-nums; }
input:disabled, select:disabled { background-color: var(--line-2); color: var(--ink-4); }
textarea { min-height: 4.5rem; resize: vertical; }

/* A native <select> left with the OS's own appearance draws its dropdown
   button INSIDE our rounded border, and that button's own corner is square
   — on Windows Chrome it is visible as a flat-edged notch at the top-right
   and bottom-right of every dropdown, on every voucher screen, because a
   voucher is mostly dropdowns (voucher type, party, place of supply, and
   an item line's ledger/godown/UOM). appearance:none removes that button so
   the border-radius above applies to the WHOLE control, arrow included. */
select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  background-image: var(--select-arrow);
  background-repeat: no-repeat;
  background-position: right .65rem center;
  background-size: .65rem .4rem;
  padding-right: 1.9rem;
  /* A long ledger or godown name in a narrow column has to lose to the
     arrow, not sit under it — without this the text simply overflows the
     padding box and runs into the arrow instead of being cut off before it. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
select:disabled { background-image: none; }

.form-grid { display: grid; gap: 0 1rem; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.form-grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.form-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.form-grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.form-grid.cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.form-grid.cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }

/* ---------------------------------------------------------------------
   Voucher entry: one screenful
   ---------------------------------------------------------------------
   An entry screen is used dozens of times a day by somebody who already
   knows what the fields mean, and the sales invoice had grown to 1,112px
   against a 900px viewport — so the total and the Save button were both
   below the fold while the lines were being typed. Everything here is in
   service of one thing: the figure and the button stay in view.
   --------------------------------------------------------------------- */

/* A tighter field: the label sits on the line above but claims less of it,
   and the hint is a title attribute rather than 35px of permanent prose. */
.form-grid.dense { gap: 0 .7rem; }
.form-grid.dense .field { margin-bottom: .5rem; }
.form-grid.dense .field > label { font-size: .68rem; margin-bottom: .12rem; }

/* The item grid, at the density of a spreadsheet rather than a report. The
   invoice grid is 15 columns and overflowed its own card horizontally; the
   padding is what it was spending. */
table.data.grid-tight th,
table.data.grid-tight td { padding: .22rem .3rem; font-size: .78rem; }
table.data.grid-tight input,
table.data.grid-tight select { padding: .2rem .35rem; font-size: .78rem; }
/* The general select rule's own right padding (for the custom arrow) would
   otherwise be wiped out by the shorthand `padding` above — same property,
   later declaration wins, so the arrow needs its own smaller room back. */
table.data.grid-tight select {
  padding-right: 1.45rem;
  background-position: right .4rem center;
  background-size: .5rem .32rem;
}
/* The per-cell padding above is column-to-column spacing; the FIRST and
   LAST column also need to clear the card's own rounded corner, which sits
   right at the table's edge because .card-body.tight strips its own padding
   to give the grid the full card width. Without this the "Item" heading
   sits closer to the card border than "Items" does one row above it —
   the row of column headers reads as uninset while everything around it
   is not. */
table.data.grid-tight th:first-child, table.data.grid-tight td:first-child { padding-left: .75rem; }
table.data.grid-tight th:last-child, table.data.grid-tight td:last-child { padding-right: .75rem; }

/* The bar that replaces a totals card. Sticky to the bottom of the
   viewport, so the running total and the Save button are reachable from
   anywhere on the form without scrolling to the end of it. */
/* The voucher key bar is ALSO sticky to bottom 0 on these screens, so the
   two would occupy the same strip and the later one in the DOM — the key bar
   — would cover the Save button. Offset by the key bar's own height, which
   is the one number below, and raise the z-index so that even where :has()
   is not understood the entry bar is on top and still clickable rather than
   buried. */
/* voucher-entry.js measures the key bar and publishes it as --vbar-h on
   :root. The fallback in the var() call covers the moment before that runs —
   it must live HERE and not as a declaration on .entry-bar, or the local
   value would shadow the inherited one and the measurement would never be
   used. */
.app:has(.vbar) .entry-bar { bottom: var(--vbar-h, 3.7rem); }

.entry-bar {
  position: sticky; bottom: 0; z-index: 21;
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  padding: .55rem .9rem; margin-top: .6rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-2);
}
.entry-sums { display: flex; align-items: baseline; gap: .9rem; flex-wrap: wrap; }
.entry-chip { font-size: .74rem; color: var(--ink-3); white-space: nowrap; }
.entry-chip strong {
  display: block; font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: .95rem; font-weight: 600; color: var(--ink);
}
.entry-chip.total strong { font-size: 1.2rem; color: var(--accent-ink, var(--ink)); }
.entry-bar .spacer { flex: 1 1 auto; }

/* On a narrow screen the bar keeps the total and the button and drops the
   breakdown, which is a preview and readable on the saved voucher anyway. */
@media (max-width: 700px) {
  .entry-chip:not(.total) { display: none; }
}
@media print { .entry-bar { position: static; box-shadow: none; } }

.inline-form { display: flex; gap: .5rem; align-items: flex-end; flex-wrap: wrap; }

/* ---------------------------------------------------------------------
   Searchable select
   ---------------------------------------------------------------------
   Any <select> carrying more than eight options is covered by a button
   and a filter popup (public/assets/js/searchable-select.js). The real
   select is still underneath and still the form control — it is covered,
   not hidden, because a hidden required control makes Chrome refuse to
   submit with no message at all. So the cover must be opaque and exactly
   the size of what it covers.
   --------------------------------------------------------------------- */
.ss { position: relative; display: block; width: 100%; }
.ss > select.ss-native { width: 100%; }

.ss > .ss-trigger {
  position: absolute; inset: 0; width: 100%;
  display: flex; align-items: center; gap: .4rem;
  font-family: var(--font-body); font-size: .875rem; text-align: left;
  padding: .42rem .6rem; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface); color: var(--ink);
  cursor: pointer; transition: border-color .15s, box-shadow .15s;
}
.ss > .ss-trigger:focus-visible, .ss.is-open > .ss-trigger {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(118, 165, 67, .15);
}
.ss > .ss-trigger.is-error { border-color: var(--danger); }
.ss > .ss-trigger:disabled { background: var(--line-2); color: var(--ink-4); cursor: not-allowed; }
.ss > .ss-trigger.is-empty .ss-text { color: var(--ink-4); }
.ss .ss-text {
  flex: 1; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.ss .ss-caret {
  flex: none; width: 0; height: 0; margin-left: auto;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid var(--ink-4);
}

/* Fixed and parented to <body>: these fields sit inside .table-wrap,
   which is overflow-x:auto, and anything positioned within one is
   clipped at the cell. */
.ss-pop {
  position: fixed; z-index: 80; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--shadow-3);
  padding: .35rem; overflow: hidden;
}
.ss-pop .ss-search {
  width: 100%; flex: none; margin-bottom: .3rem;
  font-size: .85rem; padding: .38rem .55rem;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--ink); font-family: var(--font-body);
}
.ss-pop .ss-search:focus { outline: none; border-color: var(--brand); box-shadow: none; }
.ss-list { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1; }
.ss-list .ss-opt {
  padding: .32rem .55rem; border-radius: var(--r-sm);
  font-size: .85rem; color: var(--ink); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* --brand-800 is a dark green, and in dark mode --brand-100 is a dark
   green too — the highlighted row came out unreadable against its own
   highlight. The ink token flips with the theme; the brand one does not. */
.ss-list .ss-opt.is-active { background: var(--brand-100); color: var(--ink); }
.ss-list .ss-opt.is-current { font-weight: 600; }
.ss-list .ss-opt.is-disabled { color: var(--ink-4); cursor: default; }
.ss-list .ss-group {
  padding: .4rem .55rem .2rem; font-family: var(--font-head);
  font-size: .68rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-4);
}
.ss-empty { margin: .4rem .55rem .5rem; font-size: .82rem; color: var(--ink-3); }

/* Segmented control, the pill group from the source site */
.segment { display: inline-flex; background: #E9ECEF; border-radius: var(--r-pill); padding: 2px; }
.segment button, .segment a {
  border: none; background: transparent; font-family: var(--font-head);
  font-weight: 500; font-size: .8rem; padding: .3rem .9rem;
  border-radius: var(--r-pill); cursor: pointer; color: var(--ink-2);
  text-decoration: none;
}
.segment .is-active { background: var(--brand-600); color: #fff; }

/* ---------------------------------------------------------------------
   Badges and status
   --------------------------------------------------------------------- */
.badge {
  display: inline-block; font-family: var(--font-head); font-size: .68rem;
  font-weight: 500; letter-spacing: .04em; text-transform: uppercase;
  padding: .12rem .5rem; border-radius: var(--r-pill);
  background: var(--line-2); color: var(--ink-2); white-space: nowrap;
}
.badge.ok      { background: var(--brand-100); color: var(--brand-800); }
.badge.info    { background: var(--accent-050); color: var(--accent-700); }
.badge.warn    { background: var(--amber-050); color: var(--amber); }
.badge.danger  { background: var(--danger-050); color: var(--danger); }
.badge.draft   { background: var(--line-2); color: var(--ink-3); }

/* ---------------------------------------------------------------------
   Alerts and flashes
   --------------------------------------------------------------------- */
.alert {
  border: 1px solid var(--line); border-left: 3px solid var(--ink-4);
  background: var(--surface); border-radius: var(--r-md);
  padding: .65rem .9rem; margin-bottom: .85rem; font-size: .875rem;
}
.alert.success { border-left-color: var(--brand); background: var(--brand-050); }
.alert.error   { border-left-color: var(--danger); background: var(--danger-050); }
.alert.warning { border-left-color: var(--amber); background: var(--amber-050); }
.alert.info    { border-left-color: var(--accent); background: var(--accent-050); }
.alert strong { font-family: var(--font-head); }

.flash-stack { position: fixed; top: 68px; right: 18px; z-index: 60; width: min(360px, calc(100vw - 36px)); }
.flash-stack .alert { box-shadow: var(--shadow-3); }

/* ---------------------------------------------------------------------
   Empty state — a screen with nothing on it should still explain itself
   --------------------------------------------------------------------- */
.empty { text-align: center; padding: 2.5rem 1rem; color: var(--ink-3); }
.empty .big { font-size: 2rem; opacity: .35; }
.empty h3 { margin: .5rem 0 .25rem; color: var(--ink-2); }
.empty p { margin: 0 auto .9rem; max-width: 44ch; font-size: .875rem; }

/* ---------------------------------------------------------------------
   Explain box — requirement 25 lives here.
   Every calculated figure can carry a "how is this worked out?" panel.
   --------------------------------------------------------------------- */
.explain {
  border: 1px dashed var(--brand); background: var(--brand-050);
  border-radius: var(--r-md); padding: .7rem .9rem; font-size: .83rem;
  color: var(--ink-2); margin: .75rem 0;
}
.explain > summary {
  cursor: pointer; font-family: var(--font-head); font-weight: 500;
  color: var(--brand-800); list-style: none;
}
.explain > summary::before { content: '?'; display: inline-grid; place-items: center;
  width: 16px; height: 16px; border-radius: 50%; background: var(--brand);
  color: #fff; font-size: .68rem; margin-right: .4rem; vertical-align: -2px; }
.explain[open] > summary { margin-bottom: .5rem; }
.explain .formula {
  font-family: var(--font-num); background: var(--surface);
  border: 1px solid var(--brand-100); border-radius: var(--r-sm);
  padding: .5rem .65rem; margin: .4rem 0; overflow-x: auto; font-size: .82rem;
}
.explain table { font-size: .8rem; width: 100%; border-collapse: collapse; margin: .4rem 0; }
.explain table td, .explain table th { padding: .2rem .45rem; border-bottom: 1px solid var(--brand-100); }
.explain .src { font-size: .76rem; color: var(--ink-3); }
.explain .src code { background: var(--surface); padding: .05rem .3rem; border-radius: 3px; }

/* ---------------------------------------------------------------------
   Login
   --------------------------------------------------------------------- */
.auth-wrap { min-height: 100vh; display: grid; place-items: center; padding: 1.5rem;
  background: linear-gradient(160deg, #F2F7EC 0%, #F7F8F5 45%, #EAF3FB 100%); }
.auth-card { width: min(420px, 100%); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-3); padding: 1.6rem 1.5rem; }
.auth-card .brand { text-align: center; margin-bottom: 1.1rem; }
.auth-card .brand .mark { width: 46px; height: 46px; border-radius: 50%;
  background: var(--brand); color: #fff; display: grid; place-items: center;
  font-family: var(--font-head); font-size: 1.05rem; margin: 0 auto .5rem; }
.auth-card .brand h1 { font-size: 1.1rem; letter-spacing: .06em; text-transform: uppercase; }
.auth-card .brand p { margin: 0; color: var(--ink-3); font-size: .8rem; }

/* The six OTP boxes */
.otp-row { display: flex; gap: .45rem; justify-content: center; margin: .5rem 0 1rem; }
.otp-row input {
  width: 2.6rem; height: 3rem; text-align: center; font-size: 1.25rem;
  font-family: var(--font-num); font-weight: 600; padding: 0;
  border-radius: var(--r-md);
}

/* ---------------------------------------------------------------------
   Utilities
   --------------------------------------------------------------------- */
.row { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
.row.tight { gap: .35rem; }
.spacer { flex: 1; }
.muted { color: var(--ink-3); }
.small { font-size: .8rem; }
.tiny  { font-size: .72rem; }
.right { text-align: right; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.mt0 { margin-top: 0; } .mb0 { margin-bottom: 0; }
/* The one-time key reveal. Monospace, wraps, selectable. */
.key-reveal { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap;
              background: var(--surface); border: 1px solid var(--line);
              border-radius: 6px; padding: .6rem .75rem; }
.key-reveal code { font-family: var(--font-mono, ui-monospace, monospace); font-size: .85rem;
                   word-break: break-all; user-select: all; flex: 1; min-width: 16rem; }

/* The search box in the header, present on every screen. */
.topbar-search input {
  width: 15rem; padding: .3rem .6rem; font-size: .82rem;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--surface-2); color: var(--ink); font-family: var(--font-body);
}
.topbar-search input:focus { outline: none; border-color: var(--brand); background: var(--surface); }
@media (max-width: 900px) { .topbar-search { display: none; } }

/* One exception check: its heading row. */
.exc-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
            padding: .75rem 1rem .25rem; }

/* A list of radio choices with room to explain each one. */
.choice-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
               gap: .6rem; padding: 1rem; }
.choice { display: flex; gap: .6rem; align-items: flex-start; padding: .75rem .85rem;
          border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
          background: var(--surface); }
.choice:hover { border-color: var(--brand); background: var(--brand-050); }
.choice:has(input:checked) { border-color: var(--brand); background: var(--brand-050);
                             box-shadow: inset 0 0 0 1px var(--brand); }
.choice input { margin-top: .2rem; flex: none; }
.choice-body { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.choice-body strong { font-size: .95rem; }

/* A row that expands under itself — the reconciliation worklist. */
tr.row-panel > td { background: var(--surface-2); padding: 0; }
tr.row-panel .panel { padding: .85rem 1rem; border-left: 3px solid var(--brand); }
.cand-list { display: flex; flex-direction: column; gap: .35rem; margin-bottom: .85rem; }
.cand-row { display: flex; align-items: center; gap: .6rem; padding: .4rem .55rem;
            background: var(--surface); border: 1px solid var(--line-2); border-radius: 6px; }
.cand-row .grow { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .1rem; }
.js-open.is-open { background: var(--brand-050); }
tr.is-dim td { opacity: .55; }

/* Label-above-value facts, for "how this was read" panels. */
.kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: .75rem 1.25rem; }
.kv-grid .k { display: block; font-size: .7rem; letter-spacing: .04em; text-transform: uppercase;
              color: var(--ink-3); }
.kv-grid .v { display: block; font-size: .95rem; font-weight: 600; }

.mt1 { margin-top: .5rem; } .mb1 { margin-bottom: .5rem; }
.mt2 { margin-top: 1rem; } .mb2 { margin-bottom: 1rem; }
.hide { display: none !important; }

/* The `hidden` ATTRIBUTE, made to mean what it says.
 *
 * `[hidden] { display: none }` lives only in the browser's own stylesheet,
 * and an author rule beats a UA rule whatever the specificity. So
 * `.ss-pop { display: flex }` quietly defeated it: the searchable-select
 * popup set `hidden = true` on close, the attribute landed, and the panel
 * stayed on screen over the page — on every screen with a long dropdown,
 * because there is one popup shared by all of them. Selecting an option did
 * work; it just looked as though nothing had happened.
 *
 * Fixed here rather than on `.ss-pop` alone, and with `!important` like
 * `.hide` above, so source order cannot decide it. Four other components
 * toggle `hidden` — the reconcile row panels and the schedule frequency
 * fields — and they were only working by luck, having no `display` rule of
 * their own. An element carrying `hidden` is hidden; nothing legitimately
 * needs otherwise. */
[hidden] { display: none !important; }
hr.rule { border: 0; border-top: 1px solid var(--line); margin: 1rem 0; }

/* ---------------------------------------------------------------------
   Print — statements must come out clean
   --------------------------------------------------------------------- */
@media print {
  .sidebar, .topbar, .page-head .actions, .no-print, .flash-stack { display: none !important; }
  .app { display: block; }
  .content { padding: 0; max-width: none; }
  .card { border: none; box-shadow: none; margin: 0 0 .5rem; }
  .card-head { background: none; border-bottom: 1px solid #000; }
  body { background: #fff; font-size: 11pt; }
  table.data th { background: none; border-bottom: 1px solid #000; }
  /* The screen rule for a totals row is (0,2,2) and beats the (0,1,2) above,
     so on paper it kept a grey fill and stayed pinned. Neither means anything
     printed. */
  table.data tfoot th {
    position: static; background: none;
    border-top: 1px solid #000; border-bottom: none;
  }
  a { color: inherit; text-decoration: none; }
  @page { margin: 14mm; }
}

/* ---------------------------------------------------------------------
   Responsive — the sidebar collapses on a narrow screen
   --------------------------------------------------------------------- */
@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; }
  .sidebar {
    position: fixed; inset: 0 auto 0 0; width: var(--sidebar-w);
    transform: translateX(-100%); transition: transform .2s; z-index: 50;
  }
  .sidebar.is-open { transform: none; box-shadow: var(--shadow-3); }
  .content { padding: .9rem; }
  .form-grid.cols-2, .form-grid.cols-3, .form-grid.cols-4 { grid-template-columns: 1fr; }
}
@media (min-width: 901px) { .menu-toggle { display: none; } }

/* ---------------------------------------------------------------------
   Dark mode — an accounting screen is stared at all day
   --------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink: #E6E8E3;  --ink-2: #C6CAC2;  --ink-3: #969B92;  --ink-4: #6E736B;
    --line: #2E322C;  --line-2: #262A24;
    --bg: #161814;   --surface: #1D201B; --surface-2: #212420;
    --brand-050: #1E2618; --brand-100: #2A3520;
    --accent-050: #12222C;
    --danger-050: #2A1A1A; --amber-050: #2A2416; --warn-050: #2A1D17;
    --zebra: #212420;
    --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23969B92' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
}
:root[data-theme="dark"] {
  --ink: #E6E8E3;  --ink-2: #C6CAC2;  --ink-3: #969B92;  --ink-4: #6E736B;
  --line: #2E322C;  --line-2: #262A24;
  --bg: #161814;   --surface: #1D201B; --surface-2: #212420;
  --brand-050: #1E2618; --brand-100: #2A3520;
  --accent-050: #12222C;
  --danger-050: #2A1A1A; --amber-050: #2A2416; --warn-050: #2A1D17;
  --zebra: #212420;
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23969B92' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------------
   Stock journal: choosing what kind of movement this is. The choice
   changes the accounting completely, so it is a deliberate card to click
   rather than an item buried in a dropdown.
   --------------------------------------------------------------------- */
.kind-card {
  display: flex; gap: .6rem; align-items: flex-start;
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: .6rem .75rem; cursor: pointer; background: var(--surface);
  transition: border-color .15s, background .15s;
}
.kind-card:hover { border-color: var(--ink-4); }
.kind-card.is-on { border-color: var(--brand); background: var(--brand-050); }
.kind-card input { width: auto; margin-top: .2rem; flex: none; }
.kind-card strong { font-family: var(--font-head); font-weight: 500; font-size: .9rem; }

/* ------------------------------------------------------------------ */
/* Breadcrumbs and the up link                                         */
/* ------------------------------------------------------------------ */
/* The trail replaces a single static title. It comes from Erp\Core\Nav,
   which knows the parent of every path, so "up" is a real URL rather than
   history.back() — which was the reason those buttons appeared dead. */
.topbar .up {
  font-size: 1.05rem; line-height: 1; padding: .25rem .5rem;
  color: var(--ink-3); text-decoration: none;
}
.topbar .up:hover { color: var(--ink); background: var(--line-2); border-radius: var(--r-sm); }

.crumbs {
  display: flex; align-items: center; gap: .35rem;
  font-family: var(--font-head); font-size: .9rem; min-width: 0;
}
.crumbs a { color: var(--ink-3); text-decoration: none; white-space: nowrap; }
.crumbs a:hover { color: var(--accent-700); text-decoration: underline; }
.crumbs .sep { color: var(--ink-4); font-size: .85rem; }
.crumbs .here {
  color: var(--ink); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* On a narrow screen only the current page survives — a trail that wraps
   onto two lines is worse than no trail. */
@media (max-width: 800px) {
  .crumbs a, .crumbs .sep { display: none; }
}

/* ------------------------------------------------------------------ */
/* The voucher bar                                                     */
/* ------------------------------------------------------------------ */
/* Fixed to the bottom, like Tally's button bar, so the keys are visible
   rather than something you have to know. Only drawn on entry screens. */
.vbar {
  position: sticky; bottom: 0; z-index: 20;
  display: flex; align-items: stretch; gap: .3rem;
  padding: .4rem .6rem;
  background: var(--surface); border-top: 1px solid var(--line);
  box-shadow: 0 -2px 8px rgba(0,0,0,.05);
  overflow-x: auto;
}
.vbar .vbar-label {
  display: flex; align-items: center; padding-right: .5rem;
  font-family: var(--font-head); font-size: .72rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-4); white-space: nowrap;
}
.vbar .spacer { flex: 1; }
.vbar .vbar-hint { display: flex; align-items: center; color: var(--ink-4); white-space: nowrap; }
.vbar kbd {
  font-family: var(--font-num); font-size: .7rem;
  padding: .05rem .3rem; border: 1px solid var(--line);
  border-bottom-width: 2px; border-radius: 3px; background: var(--surface-2);
}
.vkey {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-width: 5.4rem; padding: .3rem .55rem;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface-2); text-decoration: none; white-space: nowrap;
}
.vkey:hover { border-color: var(--brand); background: var(--brand-050); text-decoration: none; }
.vkey .k {
  font-family: var(--font-num); font-size: .68rem; font-weight: 700;
  color: var(--ink-4); letter-spacing: .03em;
}
.vkey .n { font-size: .82rem; color: var(--ink-2); }
.vkey.is-on { background: var(--brand); border-color: var(--brand-700); }
.vkey.is-on .k { color: var(--brand-100); }
.vkey.is-on .n { color: #fff; font-weight: 600; }

/* ------------------------------------------------------------------ */
/* Roles, users and the permission grids                               */
/* ------------------------------------------------------------------ */
.role-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: .6rem; margin: .5rem 0;
}
.role-card {
  display: block; padding: .6rem .7rem; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface);
}
.role-card:hover { border-color: var(--brand); }
.role-card.is-on { border-color: var(--brand); background: var(--brand-050); }
.role-card input { margin-right: .4rem; }
.role-card .role-name { font-weight: 600; font-size: .92rem; }
.role-card .role-desc { display: block; margin-top: .2rem; line-height: 1.4; }
/* ---------------------------------------------------------------------
   The roles index.

   Roles as cards, then a grid of how far each reaches. The cards exist so a
   description has somewhere to go: it used to sit in the first column of a
   21-column table, about 100px wide, where a one-line sentence wrapped to
   five lines and made every row 120px tall.
   --------------------------------------------------------------------- */
.role-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  gap: .7rem; margin-bottom: 1rem;
}
.role-item {
  display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--shadow-1);
  padding: .75rem .85rem;
}
.role-item-head { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; }
.role-item-name {
  font-family: var(--font-head); font-size: .98rem; font-weight: 600;
  color: var(--ink);
}
.role-item-name:hover { color: var(--brand-700); }
/* flex: 1 so every card's actions sit on the same line however long the
   description is — a row of cards with buttons at different heights reads as
   a mistake. */
.role-item-desc {
  flex: 1; margin: .3rem 0 .5rem;
  font-size: .8rem; color: var(--ink-3); line-height: 1.45;
}
.role-item-facts {
  display: flex; flex-wrap: wrap; gap: .2rem .85rem;
  font-size: .76rem; color: var(--ink-3);
  padding-top: .45rem; border-top: 1px solid var(--line-2);
}
.role-item-facts strong { color: var(--ink); font-family: var(--font-num); }
.role-item-sens { color: var(--amber); }
.role-item-sens strong { color: var(--amber); }
.role-item-actions { display: flex; gap: .4rem; margin-top: .6rem; }
.role-item-actions form { display: inline; }

/* How far a role reaches into each module. Modules down, roles across —
   17 rows by 8 columns fits, where 7 rows by 21 columns did not. */
.reach-grid td.ctr, .reach-grid th.ctr { text-align: center; }
.reach-grid th.ctr { white-space: nowrap; font-size: .74rem; }
.reach-grid td.ctr { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
/* Two elements and three classes, (0,3,2). A plainer selector loses to the
   zebra rule at (0,3,1) if this table is ever given that class back, which
   is how the total row once came out white. */
table.data.reach-grid tbody td.reach-full {
  background: var(--brand-100); color: var(--brand-800); font-weight: 600;
}
table.data.reach-grid tbody td.reach-part { background: var(--amber-050); color: var(--amber); }
table.data.reach-grid tbody td.reach-none { color: var(--ink-4); }
.reach-key {
  display: inline-block; padding: 0 .4rem; border-radius: var(--r-sm);
  font-family: var(--font-head); font-size: .68rem;
}
.reach-key.reach-full { background: var(--brand-100); color: var(--brand-800); }
.reach-key.reach-part { background: var(--amber-050); color: var(--amber); }
.reach-key.reach-none { color: var(--ink-4); }

.perm-module {
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  margin-bottom: .4rem; background: var(--surface);
}
/* Flex, so the count sits at the right edge rather than trailing the name.
   That costs the native disclosure marker — display:flex stops summary being
   a list-item — so one is drawn back with ::before. */
.perm-module > summary {
  display: flex; align-items: center; gap: .5rem;
  padding: .5rem .7rem; cursor: pointer;
  font-family: var(--font-head); font-size: .88rem; font-weight: 600;
  list-style: none;
}
.perm-module > summary::-webkit-details-marker { display: none; }
.perm-module > summary::before {
  content: '\25B8'; font-size: .8rem; color: var(--ink-3); width: .7rem; flex: none;
}
.perm-module[open] > summary::before { content: '\25BE'; }
.perm-module > summary:hover { background: var(--surface-2); }
.perm-module[open] > summary { border-bottom: 1px solid var(--line-2); }
.perm-count { margin-left: auto; flex: none; font-weight: 400; }

/* ---------------------------------------------------------------------
   The permission picker on a role.

   It used to put every permission in a `label.check`, which is
   `inline-flex` AND `white-space: nowrap` at (0,1,1). `.perm-row` is
   (0,1,0), so its `display: flex` lost on specificity and never applied:
   the permissions ran together as inline boxes wrapping mid-list, and the
   code of one sat against the label of the next with nothing between them.
   Hence `label.perm-item` here — matching the element as well, so it wins.

   Each permission is its own row: a tick, the sentence, and the code at the
   right where it can be read down the column instead of interrupting the
   sentence. The list is a grid so a wide screen shows two columns rather
   than one long ribbon of 57 items.
   --------------------------------------------------------------------- */
.perm-list {
  display: grid;
  /* 26rem, not less: at 23rem a 1500px screen fits three columns and the
     sensitive pill ends up touching the code. Two roomy columns read faster
     than three cramped ones, and a screen wide enough still gets three. */
  grid-template-columns: repeat(auto-fill, minmax(26rem, 1fr));
  gap: .15rem .9rem;
  padding: .4rem .5rem .55rem;
}
label.perm-item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline; gap: .1rem .6rem;
  padding: .42rem .55rem;
  border-radius: var(--r-sm);
  border-left: 2px solid transparent;
  cursor: pointer; font-size: .82rem; line-height: 1.35;
  white-space: normal;
}
label.perm-item:hover { background: var(--surface-2); }
label.perm-item input { margin: 0; justify-self: start; }
/* Ticked rows are tinted so what the role reaches reads as a shape down the
   column, not as a hunt for filled boxes. :has() rather than a class set in
   JS, so it is still true with scripting off. */
label.perm-item:has(input:checked) { background: var(--brand-050); }
label.perm-item:has(input:disabled) { cursor: default; }
/* Sensitive changes money, approvals or access. The amber edge makes those
   findable in a scan; the pill alone is only findable by reading. */
label.perm-item.is-sensitive { border-left-color: var(--amber); }
.perm-item .perm-name { min-width: 0; overflow-wrap: break-word; }
.perm-item .badge { margin-left: .3rem; vertical-align: .05em; }
.perm-item .perm-code {
  font-size: .7rem; color: var(--ink-4); white-space: nowrap;
  justify-self: end; align-self: baseline;
}

/* Bulk controls, both the card-wide pair and the per-module one. They are
   built by role-perms.js rather than rendered here, so with scripting off
   there is no dead button — only the plain list, which works. */
.perm-toolbar {
  display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
  padding: 0 .1rem .6rem;
}
.perm-toolbar input[type=search] {
  flex: 1 1 12rem; min-width: 0; max-width: 22rem; margin: 0;
}
.perm-toolbar .spacer { flex: 1 1 auto; }
.perm-list-actions {
  grid-column: 1 / -1;
  display: flex; gap: .3rem; align-items: center;
  padding: .1rem .5rem .3rem;
}
.perm-empty { grid-column: 1 / -1; padding: .5rem; }

.perm-table { width: 100%; }
.perm-table th.ctr, .perm-table td.ctr { text-align: center; width: 6.5rem; }
.perm-table .ok { color: var(--brand-700); }

/* ---------------------------------------------------------------------
   The payout calendar. Seven columns whatever the width — a month that
   reflows to three columns is no longer a month — so the cells shrink and
   the detail inside them drops away rather than the grid breaking up.
   --------------------------------------------------------------------- */
.cal {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px; background: var(--line-2); padding: 1px;
}
.cal-dow {
  background: var(--surface-2); padding: .35rem .5rem;
  font-family: var(--font-head); font-size: .68rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3); text-align: center;
}
.cal-day {
  background: var(--surface); min-height: 6.5rem; padding: .35rem .45rem;
  display: flex; flex-direction: column; gap: .15rem;
}
/* Days from the neighbouring months keep the weeks whole but must not read
   as part of this month's total. */
.cal-day.is-outside { background: var(--surface-2); opacity: .5; }
.cal-day.is-today { box-shadow: inset 0 0 0 2px var(--accent); }
.cal-day.has-payout { background: var(--brand-050); }
.cal-day.is-late { background: var(--danger-050); }
.cal-num { font-family: var(--font-num); font-size: .72rem; color: var(--ink-3); }
.cal-day.is-today .cal-num { color: var(--accent-700); font-weight: 700; }
.cal-amt {
  font-family: var(--font-num); font-size: .82rem; font-weight: 600;
  color: var(--ink); line-height: 1.2;
}
.cal-day.is-late .cal-amt { color: var(--danger); }
.cal-bar { height: 3px; background: var(--line-2); border-radius: 2px; overflow: hidden; }
.cal-bar span { display: block; height: 100%; background: var(--brand); }
.cal-day.is-late .cal-bar span { background: var(--danger); }
.cal-n { font-size: .64rem; color: var(--ink-4); }
.cal-list { list-style: none; margin: .1rem 0 0; padding: 0; }
.cal-list li {
  font-size: .68rem; color: var(--ink-3); line-height: 1.35;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

@media (max-width: 760px) {
  /* Still seven columns. The party names are the first thing to go, then
     the item count — the figure and the bar are what a glance is for. */
  .cal-day { min-height: 4.2rem; padding: .25rem .3rem; }
  .cal-list, .cal-n { display: none; }
  .cal-amt { font-size: .7rem; }
}

/* How far a role reaches into each module. */
.reach-full { color: var(--brand-700); }
.reach-part { color: var(--amber); }
.reach-none { color: var(--line); }

/* ------------------------------------------------------------------ */
/* Components the newer screens use                                    */
/* ------------------------------------------------------------------ */
/* A row of headline figures — stock on hand, value, average cost. */
.stat-row {
  display: grid; gap: .6rem; margin-bottom: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
}
.stat {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: .6rem .8rem;
}
.stat .label {
  font-family: var(--font-head); font-size: .68rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-4);
}
.stat .value {
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: 1.25rem; font-weight: 600; color: var(--ink); line-height: 1.3;
}
.stat .value.bad { color: var(--danger); }

/* Two columns that become one when there is no room. */
.grid-2 { display: grid; gap: .9rem; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); }

/* Filter tabs — all / holding stock / expiring / expired. */
.tabs { display: flex; gap: .25rem; margin-bottom: .8rem; flex-wrap: wrap; }
.tab {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .35rem .75rem; border-radius: var(--r-pill);
  font-family: var(--font-head); font-size: .8rem;
  color: var(--ink-3); border: 1px solid var(--line); background: var(--surface);
  text-decoration: none;
}
.tab:hover { border-color: var(--brand); color: var(--ink); text-decoration: none; }
.tab.is-active { background: var(--brand); border-color: var(--brand); color: #fff; }
.tab .count {
  font-family: var(--font-num); font-size: .7rem;
  background: var(--line-2); color: var(--ink-3);
  border-radius: var(--r-pill); padding: 0 .35rem;
}
.tab.is-active .count { background: rgba(255,255,255,.25); color: #fff; }

/* A card body with nothing to show, where a full empty state is too much. */
.empty-line { color: var(--ink-4); font-size: .85rem; padding: .8rem .75rem; margin: 0; }

/* A row that is wrong rather than merely worth noticing. */
table.data tr.bad-row td { background: var(--danger-050); }

.form-actions {
  display: flex; gap: .5rem; align-items: center; flex-wrap: wrap;
  margin: 1rem 0 2rem;
}

/* ------------------------------------------------------------------ */
/* Mobile                                                              */
/* ------------------------------------------------------------------ */
/* An accounting screen on a phone is mostly two problems: tables that are
   wider than the glass, and controls too small to hit. Neither is solved by
   shrinking — a trial balance at 8px is not a trial balance. So: tables keep
   their width and scroll, with the name column pinned so you never lose
   track of which row you are reading; and everything you touch grows. */

@media (max-width: 900px) {
  /* The drawer needs something to close it. Without a backdrop the only way
     out is the toggle, which is behind the drawer on a narrow screen. */
  .sidebar.is-open::after {
    content: ''; position: fixed; inset: 0 0 0 var(--sidebar-w);
    background: rgba(0,0,0,.45);
  }

  /* The company name is the widest thing in the top bar and it does not
     shrink, so it crushed the breadcrumb to nothing — on a phone there was
     no page title at all. It is in the sidebar already. */
  .topbar .chip:not(.year) { display: none; }
  .topbar > .small.muted { display: none; }
  .topbar { gap: .5rem; padding: 0 .6rem; }
  .crumbs { flex: 1; min-width: 0; }
  .crumbs .here { font-size: .92rem; }

  .page-head h1 { font-size: 1.35rem; }
  .page-head .actions { margin-left: 0; width: 100%; }
  .page-head .actions .btn { flex: 1 1 auto; justify-content: center; }

  /* iOS zooms the whole page when a field smaller than 16px takes focus,
     and does not zoom back out. 16px is the price of not doing that. */
  input[type=text], input[type=email], input[type=password], input[type=number],
  input[type=date], input[type=tel], input[type=search], select, textarea {
    font-size: 16px; padding: .55rem .65rem;
  }
  /* The cover has to grow with what it covers, or the label sits off
     centre in a taller box. Same reason the filter box is 16px: iOS
     zooms into anything smaller and never zooms back out. */
  .ss > .ss-trigger, .ss-pop .ss-search { font-size: 16px; padding: .55rem .65rem; }
  .ss-list .ss-opt { padding: .5rem .6rem; font-size: .95rem; }

  /* A finger is about 9mm. Anything smaller is a game of chance. */
  .btn { padding: .6rem 1rem; font-size: .875rem; min-height: 44px; }
  .btn-sm { padding: .4rem .7rem; min-height: 36px; }
  .sidebar .nav-item { padding-top: .6rem; padding-bottom: .6rem; }
  .tab { min-height: 40px; }
  .check input[type=checkbox], .check input[type=radio],
  .perm-table input[type=radio] { width: 20px; height: 20px; }

  /* Tables scroll rather than shrink, and the first column stays put so the
     row you are reading keeps its name while the figures move. */
  .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .table-wrap table.data th:first-child,
  .table-wrap table.data td:first-child {
    position: sticky; left: 0; z-index: 1;
    background: var(--surface);
    box-shadow: 1px 0 0 var(--line-2);
    max-width: 9.5rem;
  }
  /* The pinned column is there to say WHICH row you are on, not to describe
     it. A secondary line wrapping in a 9rem column turns every row six lines
     tall; it is still on the record's own page. */
  .table-wrap table.data td:first-child .tiny.muted,
  .table-wrap table.data td:first-child .role-desc { display: none; }
  .table-wrap table.data.zebra tbody tr:nth-child(even) td:first-child { background: var(--zebra); }
  /* These paint OVER the stripe, so they need at least its specificity —
     .zebra tbody tr:nth-child(even) td:first-child is four classes deep, and
     a plainer selector silently lost to it. That is how the total row came
     out white. */
  .table-wrap table.data.zebra tbody tr.is-total td:first-child,
  .table-wrap table.data.zebra tbody tr.is-subtotal td:first-child,
  .table-wrap table.data tr.is-total td:first-child,
  .table-wrap table.data tr.is-subtotal td:first-child { background: var(--surface-2); }
  .table-wrap table.data.zebra tbody tr.warn-row td:first-child,
  .table-wrap table.data tr.warn-row td:first-child { background: var(--warn-050); }
  .table-wrap table.data.zebra tbody tr.bad-row td:first-child,
  .table-wrap table.data tr.bad-row td:first-child { background: var(--danger-050); }
  .table-wrap table.data.zebra tbody tr.is-group td:first-child,
  .table-wrap table.data tr.is-group td:first-child { background: var(--brand-050); }

  /* The bar is the only way to change voucher without a keyboard, so it
     matters more on a phone, not less. Narrower keys, no hint text. */
  .vbar { padding: .35rem .4rem; gap: .25rem; }
  .vbar .vbar-label, .vbar .vbar-hint, .vbar .spacer { display: none; }
  .vkey { min-width: 4.2rem; padding: .35rem .4rem; min-height: 44px; }
  .vkey .n { font-size: .75rem; }

  /* Five columns will not fit in 375px, and the two that matter — allow and
     deny — were the ones pushed off the edge. Nothing is removed: the words
     shrink to a mark and the headers to one word, so every column survives
     and the choice is still made in one tap. */
  .perm-table th.ctr, .perm-table td.ctr { width: 3rem; padding: .3rem .1rem; }
  .perm-table .tiny.muted.mono { display: none; }
  .perm-table th, .perm-table td { padding-left: .35rem; padding-right: .35rem; }

  /* The table carries class="data compact perm-table", and
     `table.data.compact th` sets a font-size at (0,2,2). Anything weaker than
     that silently loses — which is how "FROM ROLES" and the shortened "Role"
     both ended up on screen at once. Three classes wins. */
  .perm-table.compact th:nth-child(2) { font-size: 0; width: 2.6rem; text-align: center; }
  .perm-table.compact th:nth-child(2)::after { content: 'Role'; font-size: .68rem; }
  .perm-table.compact th:nth-child(3) { font-size: 0; }
  .perm-table.compact th:nth-child(3)::after { content: 'Auto'; font-size: .68rem; }

  .perm-table.compact td:nth-child(2) { width: 2.6rem; text-align: center; }
  .perm-table.compact td:nth-child(2) .ok,
  .perm-table.compact td:nth-child(2) .dim { font-size: 0; }
  .perm-table.compact td:nth-child(2) .ok::before {
    content: '✓'; font-size: .95rem; color: var(--brand-700);
  }
  .perm-table.compact td:nth-child(2) .dim::before { content: '·'; font-size: 1.2rem; }

  /* A card head with a note beside the title stacks rather than squeezing
     the title into two words per line. */
  .card-head { flex-direction: column; align-items: flex-start; gap: .15rem; }

  .stat-row { grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); }
  .stat .value { font-size: 1.1rem; }

  /* A filter row of four fields is a wall. Stack it. */
  .inline-form { flex-direction: column; align-items: stretch; }
  .inline-form .field { width: 100%; }
  .inline-form .btn { width: 100%; justify-content: center; }
}

/* Narrow phones: the search belongs somewhere reachable, not gone. */
@media (max-width: 560px) {
  /* Three columns in a row need about 23rem. Below that the code drops
     under the sentence rather than squeezing it to two words a line. */
  .perm-list { grid-template-columns: 1fr; }
  label.perm-item { grid-template-columns: auto 1fr; }
  .perm-item .perm-code { grid-column: 2; justify-self: start; }
  .content { padding: .7rem .6rem 4rem; }
  .card-body { padding: .7rem; }
  .page-head h1 { font-size: 1.2rem; }
  .page-head .lede { font-size: .82rem; }
  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  table.data.compact th, table.data.compact td { padding: .35rem .45rem; }
}

/* The drawer swallows the search on a phone, so put one inside it. */
@media (max-width: 900px) {
  .sidebar .drawer-search { display: block; padding: .6rem .75rem; }
  .sidebar .drawer-search input {
    width: 100%; font-size: 16px; background: rgba(255,255,255,.06);
    border: 1px solid rgba(255,255,255,.14); color: #E9EDE4;
  }
  .sidebar .drawer-search input::placeholder { color: #8A9182; }
}
@media (min-width: 901px) { .sidebar .drawer-search { display: none; } }
