/* ============================================================================
 * EPAL GROUP ERP  ·  css/base.css
 * ----------------------------------------------------------------------------
 * RESET · TYPOGRAPHY · PRIMITIVES (buttons, inputs, tables, badges, utilities)
 * These are the atoms every module composes with. Kept small and consistent.
 * ==========================================================================*/

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html, body { height: 100%; }
body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;                         /* the app manages its own scroll regions */
}
h1,h2,h3,h4,h5 { font-family: var(--font-display); font-weight: 600; line-height: 1.2; letter-spacing: -.01em; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; cursor: pointer; background: none; border: none; }
input, select, textarea { font: inherit; color: inherit; }
img, svg { display: block; max-width: 100%; }
::selection { background: color-mix(in srgb, var(--accent) 40%, transparent); }

/* Numbers everywhere use tabular figures for clean alignment --------------*/
.num, .mono, td.num, .kpi-value, .stat-value { font-variant-numeric: tabular-nums; font-family: var(--font-mono); }

/* Scrollbars --------------------------------------------------------------*/
* { scrollbar-width: thin; scrollbar-color: var(--surface-hi) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb { background: var(--surface-hi); border-radius: 999px; border: 3px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover { background: var(--text-mute); background-clip: content-box; }

/* Focus ring --------------------------------------------------------------*/
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* ---------------------------------------------------------------- Buttons */
.btn {
  --btn-bg: var(--surface-2);
  display: inline-flex; align-items: center; gap: 8px; justify-content: center;
  padding: 9px 16px; border-radius: var(--r-sm); font-weight: 600; font-size: var(--fs-small);
  background: var(--btn-bg); color: var(--text); border: 1px solid var(--border);
  transition: transform var(--t-fast) var(--e-out), background var(--t), border-color var(--t), box-shadow var(--t);
  white-space: nowrap; user-select: none;
}
.btn:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn i { font-size: 1.05em; }
.btn-primary { --btn-bg: linear-gradient(160deg, var(--epal-accent, var(--accent)), var(--epal-deep, var(--accent)));
  color: #fff; border-color: transparent;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.22), 0 8px 22px -10px color-mix(in srgb, var(--accent) 70%, transparent); }
[data-theme="light"] .btn-primary { color: #fff; }
.btn-primary:hover { filter: brightness(1.07); transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 14px 32px -10px color-mix(in srgb, var(--accent) 78%, transparent); }
.btn-gold { --btn-bg: linear-gradient(135deg, var(--gold), var(--gold-soft)); color:#ffffff; border-color: transparent; }
.btn-ghost { --btn-bg: transparent; }
.btn-ghost:hover { background: var(--surface-2); }
.btn-danger { --btn-bg: var(--bad); color: #fff; border-color: transparent; }
.btn-outline { --btn-bg: transparent; border-color: var(--border-strong); }
/* three distinct steps, none of them near-duplicates: 12 · 13 · 16 */
.btn-sm { padding: 6px 11px; font-size: var(--fs-tiny); border-radius: var(--r-xs); }
.btn-lg { padding: 12px 22px; font-size: var(--fs-lead); }
.btn-icon { padding: 9px; width: 38px; height: 38px; }
.btn[disabled] { opacity: .5; pointer-events: none; }
.btn-block { width: 100%; }

.icon-btn {
  width: 38px; height: 38px; display: inline-grid; place-items: center; border-radius: var(--r-sm);
  color: var(--text-dim); border: 1px solid transparent; position: relative; transition: all var(--t);
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.icon-btn i { font-size: 18px; }
.link-btn { color: var(--accent); font-weight: 600; font-size: var(--fs-small); }
.link-btn:hover { text-decoration: underline; }

/* ------------------------------------------------------------- Form fields */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field > label, .form-label { font-size: var(--fs-tiny); font-weight: 600; color: var(--text-dim); letter-spacing: .01em; }
.field .req { color: var(--bad); }
.input, .select, textarea.input {
  width: 100%; padding: 10px 12px; border-radius: var(--r-sm);
  background: var(--surface); border: 1px solid var(--border-strong); color: var(--text);
  transition: border-color var(--t), box-shadow var(--t), background var(--t);
}
.input::placeholder { color: var(--text-mute); }
.input:focus, .select:focus, textarea.input:focus {
  border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); outline: none;
}
.select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%238a93a9' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; padding-right: 34px; }
.hint { font-size: var(--fs-tiny); color: var(--text-mute); }

/* Toggle switch (used all over the Module Manager) ------------------------*/
.switch { position: relative; display: inline-block; width: 42px; height: 24px; flex: none; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .track { position: absolute; inset: 0; background: var(--surface-hi); border-radius: 999px; transition: background var(--t); border: 1px solid var(--border); }
.switch .track::before { content:''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%;
  background: #fff; transition: transform var(--t) var(--e-out); box-shadow: var(--shadow-sm); }
.switch input:checked + .track { background: var(--accent); border-color: transparent; }
.switch input:checked + .track::before { transform: translateX(18px); }

/* ---------------------------------------------------------------- Tables */
/* A wide table SCROLLS horizontally inside this wrapper (never overflows the
   page). The background is a Lea-Verou scroll-shadow stack: a soft shadow shows
   on whichever edge still has hidden content and disappears at the ends — so a
   clipped column reads as "scroll me", not "broken". */
.table-wrap {
  overflow: auto; max-width: 100%; border: 1px solid var(--border); border-radius: var(--r-md);
  background:
    linear-gradient(to right, var(--surface), transparent) left / 42px 100% no-repeat,
    linear-gradient(to left, var(--surface), transparent) right / 42px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.20), transparent) left / 15px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.20), transparent) right / 15px 100% no-repeat;
  background-color: var(--surface); background-attachment: local, local, scroll, scroll;
  overscroll-behavior-x: contain;
}
.table-wrap::-webkit-scrollbar { height: 10px; }
.table-wrap::-webkit-scrollbar-thumb { background: var(--border-strong, var(--border)); border-radius: 8px; border: 2px solid var(--surface); }
.table-wrap::-webkit-scrollbar-track { background: transparent; }
table.tbl { width: 100%; border-collapse: collapse; font-size: var(--fs-small); table-layout: auto; }
/* Column headers (owner review 2026-07-15): were 10.5px, uppercase, letter-spaced
   AND --text-mute — four legibility taxes stacked on the densest screens (Chart
   of Accounts / Manage Accounts carry ~30 of these each). Uppercase + tracking
   needs MORE size, not less, so: 12px on the scale, and --text-dim instead of
   --text-mute (4.3:1 → 7.7:1 light, 4.9:1 → 8.6:1 dark). Still quieter than the
   body text below it — the hierarchy now comes from weight + colour, not from
   shrinking it past the point of reading. */
.tbl thead th {
  text-align: left; font-weight: 600; font-size: var(--fs-tiny); letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-dim); padding: 8px 9px; background: var(--surface-2); position: sticky; top: 0; z-index: 1;
  border-bottom: 1px solid var(--border); white-space: normal; line-height: 1.15;   /* headers wrap → columns stay narrow */
}
/* FIT, don't scroll: text cells WRAP so every column (incl. the Actions buttons)
   stays visible without a horizontal scrollbar at normal zoom. Numbers, status
   badges and the action icons stay on one line; long tokens break. Force a cell
   back to one line with .nowrap; the .table-wrap scroll remains only as a last
   resort on very narrow (mobile) widths. */
.tbl tbody td { padding: 6px 9px; border-bottom: 1px solid var(--border); color: var(--text-dim);
  font-variant-numeric: tabular-nums; vertical-align: middle; white-space: normal; overflow-wrap: anywhere; }
.tbl td.dt-actions, .tbl td.nowrap, .tbl th.nowrap { white-space: nowrap; overflow-wrap: normal; }
.tbl td .badge, .tbl td .mono { white-space: nowrap; }
.tbl td.wrap, .tbl th.wrap { white-space: normal; }
.tbl tbody tr { transition: background var(--t-fast); }
.tbl tbody tr:hover { background: var(--surface-2); }
.tbl tbody tr:last-child td { border-bottom: none; }
.tbl td .strong, .tbl td strong { color: var(--text); font-weight: 600; }
/* numeric cells are ATOMIC — a money value must NEVER break across lines
   (owner 2026-07-19: "৳300,0⏎00" mid-digit wrapping looked broken). They stay
   right-aligned, tabular, and on ONE line; if the table gets too wide for the
   container it scrolls inside its .table-wrap (which has fade + scrollbar
   affordances) rather than fracturing a figure. */
.tbl td.num, .tbl thead th.num { text-align: right; white-space: nowrap; overflow-wrap: normal; }
.tbl td.num { color: var(--text); }
.tbl .row-click { cursor: pointer; }
/* THE TOTALS ROW (opt-in via EPAL.table's opts.totals — owner 2026-07-30: "every
   numeric column must foot"). Footed the way an accountant expects to see it:
   a rule above, a banded row, and figures at the same weight as the column head
   they close. It is a <tfoot>, so it sits under the last body row on screen and
   repeats nowhere — the printed register carries its own. Sticky-bottom so the
   figure stays in view while a long table scrolls inside its wrapper. */
.tbl tfoot td { padding: 9px; border-top: 2px solid var(--border); background: var(--surface-2);
  color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap;
  position: sticky; bottom: 0; z-index: 1; }
.tbl tfoot td.num { text-align: right; }
.tbl tfoot .dt-total-lbl { text-transform: uppercase; letter-spacing: .05em; font-size: var(--fs-tiny); }
/* a totals cell states the figure, never the tone — a total is not an exception */
.tbl tfoot td .text-good, .tbl tfoot td .text-bad, .tbl tfoot td .text-warn { color: var(--text); }
/* left-align numeric figures in a cell while keeping tabular spacing (phones, refs) */
.tbl td.lnum, .tnum { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* DENSE variant for very-wide financial sheets (12+ columns, e.g. the Master
   Payroll salary sheet) — fits at 90-100% Windows zoom with NO horizontal
   scrollbar (owner premium directive). Bloomberg/IBM-style density: one step
   smaller tabular font + tighter cells, so the extra money columns + status +
   actions all stay on screen. Apply `.tbl-dense` to the .card-body / wrapper
   that holds the wide table; normal tables are untouched. */
.tbl-dense table.tbl { font-size: calc(var(--fs-tiny) * 0.88); }   /* ~12% smaller again (owner: still overflowed at narrower windows) */
.tbl-dense .tbl thead th { padding: 5px 6px; letter-spacing: 0; }
.tbl-dense .tbl tbody td { padding: 4px 6px; }
.tbl-dense .tbl td.num, .tbl-dense .tbl th.num { padding-left: 4px; padding-right: 6px; }
/* pack the row-action icons tight — three 38px buttons ate ~120px of a 13-col
   sheet; shrink them and kill the gap so real columns get the width back. */
.tbl-dense .tbl td.dt-actions { padding-left: 2px; padding-right: 2px; }
.tbl-dense .dt-actions .row-actions { gap: 0; }
.tbl-dense .dt-actions .icon-btn { width: 26px; height: 26px; }
.tbl-dense .dt-actions .icon-btn i { font-size: 15px; }
/* SNUG variant (2026-07-29, owner: Staff Accounts). Same goal as .tbl-dense —
   every column on screen, no horizontal scrollbar — but for a table that must
   stay COMFORTABLY readable: exactly ONE 10% step down from the normal table
   font (13px → 11.7px) instead of dense's 10.56px, which is under the 11px
   --fs-micro floor the tokens call "never go under".
   Density comes from the cells, not from shrinking the type: tighter padding,
   tighter badges, smaller action buttons. And because small text is only small,
   not faint, the body text goes UP in contrast — full --text (near-black in
   light) instead of the default --text-dim, with secondary captions ("we owe",
   "/mo") at --text-dim rather than the pale --text-mute. */
/* The last few pixels come from the CONTAINER, not the content. Several columns
   are floored by their header word — ADVANCE, POSITION, RECORDS cannot be made
   narrower without renaming them or breaking words mid-letter — so the card
   gives up 8px of its own side padding instead. Nothing in the table changes. */
.card-body.tbl-snug { padding-left: 8px; padding-right: 8px; }
.tbl-snug table.tbl { font-size: calc(var(--fs-small) * 0.9); }
/* THE HEADER IS A BAND, NOT FOURTEEN LABELS (2026-07-29 polish). Header words
   wrap here — "DESIG-/NATION", "LEAVE/ENCASH", "LAST/PAID" — and they used to
   hang from the TOP of the band, so a two-line label and a one-line label began
   at different heights and the whole strip read as ragged. Bottom-aligned, every
   label sits on one baseline: the line directly above the first row of data.
   The band closes with a stronger rule than the row separators, so the eye
   reads header-then-data instead of fifteen equal stripes. */
.tbl-snug .tbl thead th {
  /* letter-spacing stays 0 (NOT the house .04em, and not even .02em): tracking
     on a header is width, and these headers ARE the width of half the columns —
     .02em across fourteen labels measured ~25px and pushed the last action
     button out past the card edge. */
  padding: 7px 3px 6px; letter-spacing: 0; font-size: var(--fs-micro);
  vertical-align: bottom; color: var(--text-mute);
  border-bottom: 1.5px solid var(--border-strong);
}
/* AIR COMES FROM THE ROW HEIGHT, NOT THE COLUMN WIDTH. The sheet is at its width
   limit — every horizontal pixel spent is a pixel of wrapping somewhere — but
   vertical space is free, and cramped LEADING is most of what made this look
   like a spreadsheet dump. 5px → 7px top and bottom with 1.34 line-height gives
   each row its own block of air; the sides stay at 3px, so not one column moves. */
.tbl-snug .tbl tbody td { padding: 7px 3px; color: var(--text); line-height: 1.34; }
.tbl-snug .tbl td.num, .tbl-snug .tbl th.num { padding-left: 2px; padding-right: 3px; }
/* the identity column keeps normal breathing room — it is the one every row is
   read from, and it is the column that freezes when a narrow window does scroll */
.tbl-snug .tbl thead th:first-child, .tbl-snug .tbl tbody td:first-child { padding-left: 8px; }
/* the id is a reference, not prose — one step down from the row keeps its column
   to the width of the code itself */
.tbl-snug .tbl td .mono { font-size: var(--fs-micro); }
/* Chips may wrap BETWEEN WORDS — "Visa & Immigration" on one unbreakable line
   set the Dept column's minimum width — but never inside one: overflow-wrap
   stays `normal`, because the first attempt at this inherited `anywhere` from
   the cell and rendered the Operations chip as "Op/era/tio/ns". */
.tbl-snug .tbl td .badge { white-space: normal; overflow-wrap: normal; word-break: keep-all; }
.tbl-snug .tbl td .text-mute { color: var(--text-dim); }
/* The width was never in the type — it was in the CAPTIONS. Half these money
   cells carry a figure plus a qualifier ("৳126,523 we owe", "৳8,947 ৳1,491/mo",
   "4.0d Eligible") and td.num is nowrap, so every one of those pairs demanded a
   single unbroken line. Let the CELL wrap; the .num/.mono spans inside it stay
   atomic (base rule above), so a figure still never fractures — the qualifier
   simply sits under it. That alone is worth ~370px across the row. */
.tbl-snug .tbl td.num { white-space: normal; }
/* EVERY span in a money cell is atomic, not just the ones tagged .num — the
   qualifier spans are plain <span class="text-warn">৳52,000</span>, and the
   first cut of this rule let one of them break as "৳ ⏎ 52,000". Wrapping is
   allowed only in the gaps BETWEEN spans. */
.tbl-snug .tbl td.num span { white-space: nowrap; }
/* .nowrap is a td/th modifier in this sheet, not a utility, so a span carrying
   it (the "May 2026" month) was still breaking — "May 202 / 6" */
.tbl-snug .tbl td .nowrap { white-space: nowrap; }
/* NOTHING BREAKS MID-LETTER. The base cell rule is overflow-wrap:anywhere,
   which is right for arbitrary data in a normal-width table but at these column
   widths turned "Chowdhury" into "Chowdhur/y" and "Operations Executive" into
   "Operati/ons Executi/ve". Wrap at spaces only; each column then takes the
   width of its longest single word, which is what makes the row readable at all.
   Only the id keeps the anywhere-break, as a machine token with no words. */
.tbl-snug .tbl tbody td { overflow-wrap: normal; }
.tbl-snug .tbl tbody td .mono { overflow-wrap: anywhere; }
.tbl-snug .tbl td .badge { padding: 2px 6px; gap: 4px; }
/* THE ROW ACTIONS — 45% SMALLER, 30% LESS VISIBLE (owner 2026-07-29, second
   pass on the same column). The first pass answered "the action buttons are not
   visible, pure white" by giving each glyph full opacity, a surface and a
   border — and three little buttons then became the loudest thing in a row of
   figures, in the column you scan last.
   So the BUTTON shrinks 45% (24px → 13.2px) and sits 30% quieter (opacity .7,
   --text-mute, no chrome at rest) — but the TARGET does not move. Each one
   carries a transparent ::after pad that restores a ~25px hit box, and the gap
   is set so neighbouring pads tile edge-to-edge instead of overlapping onto the
   next glyph: less ink on screen, exactly the same thing to click.
   The glyph itself only drops 13px → 11px. Shrinking a button is not the same
   as shrinking its icon — at 45% the icon would be a smudge — so the icon keeps
   its legibility and the button around it does the giving-way.
   Visibility comes back on approach, which is what the first pass really wanted:
   hovering ANYWHERE on the row lifts its actions to full strength, and hovering
   one gives it the accent and its pill back. Scoped to .tbl-snug. */
.tbl-snug .tbl td.dt-actions { padding-left: 2px; padding-right: 0; }
.tbl-snug .dt-actions .row-actions { gap: 6px; }
/* the divider between "things that move money" and "print" keeps its meaning at
   1px of margin — the 6px gaps either side already separate the two groups */
.tbl-snug .dt-actions .row-act-sep { margin: 0 1px; }
.tbl-snug .row-act {
  /* padding:0 is load-bearing: a <button> carries Chrome's UA `padding: 1px 6px`,
     which at 24px merely stole a quarter-pixel but at 13.2px left a 1.2px content
     box — the glyph was pushed clean out of the right of its own button (and the
     last one out of the table, which is where the phantom scrollbar came from). */
  width: 13.2px; height: 13.2px; padding: 0; border-radius: 4px; position: relative;
  opacity: .7; color: var(--text-mute); background: transparent; border-color: transparent;
}
.tbl-snug .row-act i { font-size: 11px; }
/* the pad paints nothing — it only widens what the pointer counts as this
   button. inset -6px against a 6px gap means the two pads meet in the middle of
   the gap and neither reaches into the other's glyph. */
.tbl-snug .row-act::after { content: ''; position: absolute; inset: -6px; border-radius: 7px; }
/* …but it stops at the ends of the group. The pad is a real box: on the LAST
   button it was 6px of scrollable overflow, which gave the card a horizontal
   scrollbar over nothing, and on the FIRST it reached back into the Status cell
   and would have swallowed a click meant for the row. */
.tbl-snug .row-actions .row-act:first-child::after { left: 0; }
.tbl-snug .row-actions .row-act:last-child::after { right: 0; }
.tbl-snug .tbl tbody tr:hover .row-act { opacity: 1; color: var(--text-dim); }
.tbl-snug .row-act:hover { opacity: 1; color: var(--accent); background: var(--surface-3, var(--surface-2)); }

/* DEPT + DESIGNATION, 25% SMALLER (owner 2026-07-29) — the two descriptive
   columns, deliberately below the 11px floor the rest of the sheet respects,
   because they are labels you scan rather than figures you read. */
.tbl-snug .tbl .c-dept, .tbl-snug .tbl .c-desig, .tbl-snug .tbl .c-role { font-size: calc(var(--fs-small) * 0.9 * 0.75); }
.tbl-snug .tbl td.c-dept .badge, .tbl-snug .tbl td.c-role .badge { font-size: inherit; padding: 2px 5px; }
/* THE MERGED ROLE CELL (owner 2026-07-29): designation on the first row, the
   department chip on the second. Two short labels stacked read as one fact about
   the person and take one column's width instead of two. */
.tbl-snug .tbl td.c-role { line-height: 1.3; }
.tbl-snug .tbl td.c-role .c-role-dept { margin-top: 3px; }

/* COLUMN RULES (owner 2026-07-29, drawn on the screenshot — then softened the
   same day: "this table looks so nasty"). The rule STAYS: fourteen money columns
   need something to run the eye down. It just stops shouting. At 1.5px and 75%
   of the foreground it drew a black cage over the sheet — the lines were the
   first thing the eye landed on and the figures the second, which is the exact
   inversion of what a register is for. At 1px and 9% they are the graph paper
   UNDER the numbers: there when you look for a column edge, gone when you are
   reading a row. Still mixed from --text, so the dark theme keeps the same
   relationship rather than a hard-coded grey. */
.tbl-snug .tbl thead th + th,
.tbl-snug .tbl tbody td + td { border-left: 1px solid color-mix(in srgb, var(--text) 9%, transparent); }

/* FROZEN IDENTITY COLUMN (2026-07-29). The rule above is "fit, don't scroll" — but
   it cannot always win: the Salary Register carries 24 columns, and at a narrow
   window or 110%+ zoom it does scroll. When it does, the first column is the one
   cell that makes the other twenty-three readable — every .tbl-dense table leads
   with WHO (salary sheet, salary register) or WHICH MONTH (monthly register), and
   scrolling right used to strand the figures with no idea whose they were.
   Purely progressive: while the sheet still fits, nothing scrolls and this does
   nothing at all. Scoped to .tbl-dense and .tbl-snug, which only Payroll applies
   — Staff Accounts fits at the owner's window but not on a 1280 laptop, and the
   name is exactly the cell that must survive scrolling there too.
   z-index ladder: header corner (3) over frozen body cells (2) over the already
   sticky thead (1). The edge is a box-shadow, not a border, so border-collapse
   layout and the measured column widths are untouched. */
.tbl-dense .tbl thead th:first-child, .tbl-snug .tbl thead th:first-child,
.tbl-dense .tbl tbody td:first-child, .tbl-snug .tbl tbody td:first-child { position: sticky; left: 0; }
.tbl-dense .tbl tbody td:first-child, .tbl-snug .tbl tbody td:first-child {
  z-index: 2; background: var(--surface); box-shadow: 1px 0 0 var(--border);
}
.tbl-dense .tbl thead th:first-child,
.tbl-snug .tbl thead th:first-child { z-index: 3; box-shadow: 1px 0 0 var(--border); }
/* the frozen cell paints its own background, so it would otherwise sit out the
   row-hover the rest of the row gets */
.tbl-dense .tbl tbody tr:hover td:first-child,
.tbl-snug .tbl tbody tr:hover td:first-child { background: var(--surface-2); }

/* FROZEN ACTION COLUMN — the same idea at the other end of the row (2026-07-29).
   The actions cell is `width: 1%`, i.e. shrink-to-fit, so it is the column that
   gets pushed off the edge when the sheet is a few pixels too wide for the card:
   the print button was being sliced in half by the card's own rounded corner,
   with nothing to say why. Pinned right it survives that — WHOEVER the row is
   about, whatever the window, the three things you can DO to a person stay on
   screen. Like the identity column, it is purely progressive: while the sheet
   fits, sticky costs nothing and nothing moves. */
.tbl-snug .tbl thead th.dt-actions, .tbl-snug .tbl tbody td.dt-actions { position: sticky; right: 0; }
.tbl-snug .tbl tbody td.dt-actions { z-index: 2; background: var(--surface); box-shadow: -1px 0 0 var(--border); }
.tbl-snug .tbl thead th.dt-actions { z-index: 3; background: var(--surface-2); box-shadow: -1px 0 0 var(--border); }

/* ============================ SNUG · THE ROW RESPONDS (2026-07-29 polish) ====
   Owner: "make it beautiful, dynamic, clean — without removing any rows and
   columns". Nothing below removes anything; it is all presentation. With the
   cage gone (see the softened column rules above) the sheet needs its structure
   back from somewhere, and on a register the honest source is the ROW: the unit
   you actually read is one person, left to right. So the row — not a border —
   becomes the thing that reacts. A tinted band the width of the table, an accent
   edge on the identity cell that says "this line is live", the person's name
   turning into a link under the pointer, and the row's actions coming up to full
   strength. One 120ms transition, no movement, nothing reflows: the sheet must
   never twitch while you read down a column of money.
   These come AFTER the frozen-column block on purpose — that block paints the
   first cell's background itself, so the hover tint has to be restated here or
   the identity cell sits out the highlight it is supposed to lead. */
.tbl-snug .tbl tbody tr { transition: background var(--t-fast); }
.tbl-snug .tbl tbody tr:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.tbl-snug .tbl tbody tr:hover td:first-child {
  background: color-mix(in srgb, var(--accent) 6%, var(--surface));
  box-shadow: inset 2px 0 0 var(--accent), 1px 0 0 var(--border);
}
/* the pinned action cell paints its own background too — same reason, other end */
.tbl-snug .tbl tbody tr:hover td.dt-actions {
  background: color-mix(in srgb, var(--accent) 6%, var(--surface));
}
/* THE NAME. EPAL.people.linkify hard-codes a dotted underline inline, so every
   name in the sheet carried a permanent dashed line under it — fourteen columns
   of clean figures led by a column that looked like unfinished text. The whole
   row already opens the person's file (onRow), so the underline is not the
   affordance; it is noise at rest and an invitation on approach. !important is
   the only lever against an inline style, and it is scoped to this table. */
.tbl-snug .tbl td .emp-link { text-decoration: none !important; color: var(--text); }
.tbl-snug .tbl tbody tr:hover .emp-link {
  color: var(--accent);
  text-decoration: underline dotted !important; text-underline-offset: 3px;
}
/* THE DEPARTMENT CHIP is a label you scan, not a status you act on, so it drops
   the pill shape and the grey slab for a soft tint of the company accent — the
   same ink as the rest of the sheet, at a weight that lets Status keep the only
   real colour in the row (green/amber/red mean something; a department does not). */
.tbl-snug .tbl td.c-dept .badge, .tbl-snug .tbl td.c-role .badge {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--text-dim); border-radius: 5px; font-weight: 600; letter-spacing: 0;
}
/* Any inline number/money span is atomic too — never break a figure. */
.num, .mono { white-space: nowrap; }

/* ---------------------------------------------------------- Badges / chips */
.badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: var(--r-pill);
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .02em; background: var(--surface-3); color: var(--text-dim); }
.badge.dot::before { content:''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge-good { background: var(--good-soft); color: var(--good); }
.badge-warn { background: var(--warn-soft); color: var(--warn); }
.badge-bad  { background: var(--bad-soft);  color: var(--bad); }
.badge-info { background: var(--info-soft); color: var(--info); }
.badge-accent { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--border); font-size: var(--fs-small); font-weight: 500; cursor: pointer; transition: all var(--t); }
.chip:hover { border-color: var(--border-strong); }
.chip.active { background: var(--accent); color: #fff; border-color: transparent; }

/* --------------------------------------------------------------- Utilities */
.text-muted { color: var(--text-dim); }
.text-mute { color: var(--text-mute); }
.text-good { color: var(--good); } .text-bad { color: var(--bad); } .text-warn { color: var(--warn); }
.text-accent { color: var(--accent); }
.sm { font-size: var(--fs-tiny); } .xs { font-size: var(--fs-micro); } .lg { font-size: var(--fs-lead); }
.fw-700 { font-weight: 700; } .fw-600 { font-weight: 600; }
.flex { display: flex; } .items-center { align-items: center; } .justify-between { justify-content: space-between; }
.gap-1{gap:6px;} .gap-2{gap:12px;} .gap-3{gap:18px;} .gap-4{gap:24px;} .flex-wrap{flex-wrap:wrap;} .flex-1{flex:1;}
.grid { display: grid; } .hidden { display: none !important; }
.mt-1{margin-top:6px;} .mt-2{margin-top:12px;} .mt-3{margin-top:18px;} .mt-4{margin-top:24px;}
.mb-2{margin-bottom:12px;} .mb-3{margin-bottom:18px;}
.uppercase { text-transform: uppercase; letter-spacing: .06em; }
.divider { height: 1px; background: var(--border); margin: 16px 0; }
.spacer { flex: 1; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* RESPONSIVE NAV — NEVER CLIPS (owner screenshot 2026-07-15).
   The previous rule chased "one row at any cost": flex-wrap:nowrap +
   overflow-x:auto + a HIDDEN scrollbar. So when the band ran out of width it
   clipped IN SILENCE — "Loans We Took / Report" and "Constructio[n]" were cut
   off at the window edge with nothing on screen to say more existed, and (the
   scrollbar being hidden) no way to reach them without a trackpad. A nav that
   hides its own destinations is worse than a nav on two rows.

   Graceful degradation as width shrinks — the row DEMOTES, it never truncates:
     1. wide   → both groups share one row (renders exactly as before)
     2. tight  → pills compress via the clamp()s, down to the 11px floor
     3. tighter→ the company switcher drops to its own row (.nav-row wraps)
     4. tiny   → each group wraps internally (phones)
   At no width/zoom does a label clip, and none renders below --fs-micro (11px):
   the clamp minima used to be 9.5px, which bought one-row-ness with type nobody
   could read. Width is negotiable; legibility and reachability are not. */
/* SINGLE-LINE SCROLLABLE NAV (owner 2026-07-19, reverses the earlier wrap
   rule): tab/filter rows stay on ONE line at any zoom/width; when they don't
   fit, the row scrolls sideways. A thin but VISIBLE scrollbar keeps off-screen
   tabs discoverable — the concern that originally motivated wrapping. */
.pill-tab { display:inline-flex; gap:4px; background: var(--surface-2); border:1px solid var(--border);
  border-radius: var(--r-pill); padding:4px; max-width:100%; flex-wrap:wrap; }
/* Type is a fixed scale step; only the PADDING stays fluid. The nav used to size
   its text with clamp(…vw…) to force one row — but that produced off-scale sizes
   (11.5 / 12.5 / 16.4px, part of the 15-size sprawl) AND it still clipped, which
   is the bug in the owner's screenshot. Wrapping does that job properly now, so
   fluid type buys nothing and costs legibility: at every width below 1440 those
   clamps were simply pinned at their 11px floor anyway. */
.pill-tab button { padding:6px clamp(8px, 0.95vw, 14px); border-radius: var(--r-pill);
  font-size: var(--fs-small); font-weight:600; color: var(--text-dim);
  white-space:nowrap; flex:0 0 auto; transition: all var(--t); }
.pill-tab button.active { background: var(--accent); color:#fff; }
/* nav band: two groups share the row while it fits, a hairline separates them */
.nav-row { display:flex; align-items:center; gap:8px 6px; flex-wrap:wrap; }
.nav-row > .pill-tab { flex:0 0 auto; overflow:visible; }   /* natural width; the whole nav-row scrolls, not each group */
.nav-row .vsep { width:1px; height:26px; background:var(--border-strong, var(--border)); flex:0 0 auto; }
.nav-row .co-sw { display:flex; gap:4px; flex-wrap:nowrap; flex:0 0 auto; }
/* inside a shared row both groups compress harder — via padding, not type */
.nav-row .pill-tab button { font-size: var(--fs-tiny); padding: 6px clamp(5px, 0.6vw, 14px); }
.nav-row .btn.btn-sm { font-size: var(--fs-tiny); padding: clamp(3px, 0.4vw, 6px) clamp(5px, 0.65vw, 12px); white-space:nowrap; }
/* THE SWITCHER STAYS ON THE NAV LINE (owner 2026-07-29: "these company switcher
   should be beside sub menu nav, FIXED, in 100% and 90% chrome view").
   Rule 3 above (the switcher demotes to its own row) was firing on every desk
   whose tab band is long — Master Payroll needs ~1248px for 8 tabs + 6 companies
   and has 944 at a 1350px window, so it ALWAYS dropped, at every zoom.
   On a desktop-width row the two groups now share the line and the SWITCHER is
   what gives way. Below 1200px — phones, tablets, heavy zoom — rule 3 still
   applies and the switcher takes its own row.

   ⚠ REVISED the same day, on the owner's next screenshot: yielding by wrapping
   put the TAB BAND on two lines, which reads worse than the problem it solved
   ("this created the new problem … you just decrease the company switcher size,
   20%"). So the switcher is what gives way now — it rides ~20% narrower on a
   shared row (tighter padding + the 11px type floor, never below it) and the tab
   band keeps ONE line. When even that is not enough the band scrolls sideways
   with its visible thin scrollbar rather than growing a second row. */
@media (min-width: 1200px) {
  .nav-row { flex-wrap: nowrap; gap: 6px; }
  .nav-row > .pill-tab { flex: 0 1 auto; min-width: 0; flex-wrap: nowrap; overflow-x: auto; }
  .nav-row .vsep, .nav-row .co-sw { flex: 0 0 auto; }
  .nav-row .co-sw { margin-left: auto; gap: 2px; }
  /* the switcher, 20% narrower: the saving comes from padding + gap, with the
     label at the 11px floor — 20% off 12px type would be 9.6px, which is the
     unreadable caption size the dashboard row was just rescued from. */
  .nav-row .co-sw .btn { font-size: var(--fs-micro); padding: clamp(3px, 0.34vw, 5px) clamp(4px, 0.42vw, 6px); gap: 4px; }
  /* the tab band matches it: same 11px floor, tighter padding — between the two
     the whole band fits without scrolling at 1440 and at 90% zoom on a 1366 */
  .nav-row .pill-tab button { font-size: var(--fs-micro); padding: 6px clamp(4px, 0.42vw, 9px); }
}
/* UNDERLINE TABS — the calm PRIMARY navigation (owner mock 2026-07-15).
   Hierarchy: section nav = quiet underline tabs; switchers/filters below it
   stay as pills; real actions stay as buttons. Less chrome, more premium. */
/* FULL-BLEED: the bar runs sidebar-edge → window-edge with NO side gaps
   (owner mark 2026-07-15) — negative margins cancel .content's padding. */
/* Wraps rather than scroll-clipping, for the same reason as .nav-row above:
   this band is the PRIMARY section nav, so a section that scrolls out of sight
   behind a hidden scrollbar is a section the user cannot find. */
/* WRAP, never scroll (owner premium directive): every section tab stays visible
   on a second line rather than hiding behind a horizontal scrollbar. */
.tab-underline { display:flex; gap:0; align-items:stretch; border-bottom:1px solid var(--border); flex-wrap:wrap;
  margin-left:-30px; margin-right:-30px; padding:0;
  background: color-mix(in srgb, var(--surface) 62%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 6px 18px rgba(7, 12, 30, .10);                            /* ~10% soft lift */ }
@media (max-width: 720px) { .tab-underline { margin-left:-16px; margin-right:-16px; } }
/* flex:1 0 auto = fill the bar when the tabs fit, keep natural width and
   scroll when they don't (never shrink below the label). */
/* --tab-fs / --tab-px are set per-bar by App.fitTabs (app.js): it shrinks the
   tabs just enough to keep the whole band on ONE line at 90–100% zoom, and lets
   it wrap only when it hits the readable floor (110%+ zoom). Unset = natural size. */
.tab-underline button { flex:1 0 auto; min-width:0; text-align:center; padding:12px var(--tab-px, clamp(8px, 1.2vw, 18px)) 13px; border-bottom:3px solid transparent; margin-bottom:-1px;
  font-size: var(--tab-fs, var(--fs-small)); font-weight:700; color:var(--text-dim); white-space:nowrap; letter-spacing:.1px;
  border-radius:0; background:none; transition: color var(--t), border-color var(--t), background var(--t); }
.tab-underline button + button { border-left:1px solid var(--border); }   /* 1px line between sections */
.tab-underline button:hover { color: var(--text); background: color-mix(in srgb, var(--accent) 9%, transparent); }
.tab-underline button.active { color: var(--accent); border-bottom-color: var(--accent); }   /* the blue line */
/* .tabs-dense — for long rows (Consolidated Finance's 13 sections): the same
   look, compressed harder so every label stays on one line before the
   hidden-scrollbar fallback ever engages. */
.tab-underline.tabs-dense button { padding:12px var(--tab-px, clamp(4px, 0.62vw, 14px)) 13px; font-size: var(--tab-fs, var(--fs-tiny)); letter-spacing:0; }

/* .scroll-row — utility: a flex row of chips/buttons that stays on ONE line
   and scrolls sideways instead of wrapping (e.g. the standalone company
   switcher). */
.scroll-row { flex-wrap:wrap !important; gap:6px; }
.scroll-row > * { flex:0 0 auto; }
/* Thin but visible scrollbar for every single-line nav scroller (so
   off-screen tabs stay discoverable — the reason the nav used to wrap). */
.pill-tab::-webkit-scrollbar, .nav-row::-webkit-scrollbar, .tab-underline::-webkit-scrollbar, .scroll-row::-webkit-scrollbar { height:6px; }
.pill-tab::-webkit-scrollbar-thumb, .nav-row::-webkit-scrollbar-thumb, .tab-underline::-webkit-scrollbar-thumb, .scroll-row::-webkit-scrollbar-thumb { background:var(--border-strong); border-radius:3px; }
.pill-tab::-webkit-scrollbar-track, .nav-row::-webkit-scrollbar-track, .tab-underline::-webkit-scrollbar-track, .scroll-row::-webkit-scrollbar-track { background:transparent; }
kbd { font-family: var(--font-mono); font-size: var(--fs-micro); background: var(--surface-3); border: 1px solid var(--border-strong);
  border-bottom-width: 2px; border-radius: 5px; padding: 1px 6px; color: var(--text-dim); }
