/* ═══════════════════════════════════════════════════════════════════════════
   TARTIB UI — the shared component set
   brand/tartib-ui.css → /brand/tartib-ui.css   (needs tartib-tokens.css first)

   ⚠️ Link this ONLY on a page that does not already define .card, .btn or
   .pill. The Assets SPA defines all three with its own meanings and links
   the tokens alone — that is why the two files are separate.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ── frame + sidebar ────────────────────────────────────────────────────── */
/* ══ THE SIDEBAR IS DARK, AND THIS IS THE CORRECTION ════════════════════════
   Hussain, 22 Aug 2026: "yess, lets use this" — the hr.koobs.cafe template.

   ⚠️ IT WAS WHITE HERE AND IT IS TEAL IN THE REFERENCE, AND THAT ONE VALUE IS
   MOST OF WHY MY VERSION READ AS A DIFFERENT PRODUCT. The reference is a dark
   teal SHELL — bar and rail, one continuous colour — wrapping a light page.
   A light rail beside a light page has no shell at all; it is a page with a
   column in it, which is what I built and what Hussain rejected on sight.

   ⚠️ THE ACTIVE ROW IS LIME WITH TEAL INK, NEVER THE MODULE ACCENT. It read
   `background:var(--accent)` — HR teal on HR teal, a row that identified itself
   by being almost invisible. Lime on teal is 10.04:1 and it is the same pairing
   the primary button uses, so "where I am" and "the thing to press" are one
   colour with one meaning. This is also why lime cannot be a text colour
   anywhere: it only works as a plate, and only on this shell. */
.wrap{display:grid;grid-template-columns:260px 1fr;min-height:calc(100vh - 58px)}
/* ⚠️ 18px OF TOP PADDING, NOT 8. Hussain: "the side bar links needs to be
   brought a bit down (its high)" — the first row was sitting hard against
   the bar with the same 8px the rows use between themselves, so the rail
   read as starting mid-word. */
.side{background:var(--chrome);border-right:1px solid var(--chrome-line);padding:18px 12px 32px;
  position:sticky;top:58px;align-self:stretch;min-height:calc(100vh - 58px);
  max-height:calc(100vh - 58px);overflow-y:auto}

/* ── the platform mark, above the first row ────────────────────────────────
   ⚠️ WIDTH AND HEIGHT ON THE <img>, and the same ratio the file has (2000x737).
   Without them the rail reflows the moment the SVG lands and every row below
   jumps — the kind of thing that reads as "the page is slow" rather than as a
   missing attribute.

   ⚠️ AND A FIXED BOX AROUND IT, so a future wordmark of a different shape
   cannot change where Dashboard sits. object-fit does the fitting. */
/* ⚠️ THE BAND'S HEIGHT IS AN ALIGNMENT, NOT A TASTE. Hussain: "dashboard icon
   needs to be aligned top with the page boxes … just try to make the page
   aligned properly." The rail was starting its first row at y=130 while the
   page title sat at 109 and the first card at 176 — level with neither, which
   is exactly what reads as "not quite right" without being nameable.

   So the two columns are locked to each other: this band spans the page's
   HEADER block (breadcrumb + title), and the first nav row starts level with
   the first card. Rail header ↔ page header, rail nav ↔ page content.

   87 + 16 puts .nav's top at 179 against the card's 178.8. hr-chart-check
   asserts it within 4px, because a number that arrived by measurement will
   drift the first time the header's margins change and nothing would say so.

   ⚠️ IT ALREADY HAS, ONCE, AND THE ASSERTION IS WHY THIS LINE READS 87 AND NOT
   84. Raising the type scale one step grew the page header by 2.8px and left
   the rail behind; the check reported nav 176.0 vs card 178.8 while still
   passing on tolerance, which is exactly the early warning it was for.

   ⚠️ THE MARK SITS INSIDE THE BAND; THE BAND DOES NOT FOLLOW IT. 28px, after
   60 -> 39 -> 33 -> 28, leaves the 87px band and therefore .nav's alignment
   with the first card exactly where they were. Shrinking the band to hug the mark each
   time would have walked the whole rail up out of alignment in three steps.
   Past about 79px the band has to grow and .nav stops lining up with the first
   card — which the assertion will say out loud rather than letting it drift.

   ⚠️ AND 60px IS NOW 60px OF INK. The mark's viewBox was tightened to its own
   bounding box (see brand/tartib-logo-mono.svg): the original carried 11.6%
   empty width and 42% empty height, so a nominal 70px drew about 40px of
   letters starting 22px in from the element's edge. Height and left edge both
   mean what they say now, which is also why this number came DOWN while the
   mark on screen got bigger. */
/* ⚠️ THE SAME PHYSICAL PADDING AS .nav, NOT A LOGICAL PAIR. Hussain: "align it
   left to the buttons below it (dashboard…) like Foodics." The mark's left edge
   has to be the nav icons' left edge, and .nav is written `0 12px 0 26px` —
   physical, with only text-align flipped for RTL. Writing padding-inline here
   would put the mark on the opposite side from the icons the moment somebody
   switches to Arabic. Match the rule that exists, not the rule it should be. */
.side-brand{display:flex;align-items:center;justify-content:flex-start;
  height:87px;margin:0 0 16px;padding:0 12px 0 26px;
  /* a rule under the mark, so the rail reads as brand-then-navigation rather
     than as a logo that happens to be the first row */
  border-bottom:1px solid var(--chrome-line)}
/* ⚠️ margin-bottom ON A CENTRED FLEX ITEM, NOT padding OR A SHORTER BAND.
   Hussain: "take it a little up, just a little." The margin BOX is what
   gets centred, so 16px under the mark lifts it 8px and leaves the band's
   84px — and therefore .nav's alignment with the first card — untouched.
   Trimming the band would have moved every row below it. */
/* WARN: NO margin-bottom. It was 24px to lift the mark inside an 87px band;
   the band is the bar's 58px now and the ask is to CENTRE the mark in it, so
   a bottom margin is exactly the thing that stops that happening — align-items
   centres the MARGIN box, not the image. */
.side-brand img{height:30px;width:auto;max-width:100%;object-fit:contain;display:block}
/* the wordmark's letters are a fixed grey; on a dark rail they sink into
   the plate, so the whole mark is lifted to white there */
:root[data-theme="dark"] .side-brand img{filter:brightness(0) invert(1);opacity:.92}
.grp{font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  /* ⚠️ .55, NOT THE REFERENCE'S .45. White at 45% over #0D3B4A composites to
     rgb(122,147,155) — 3.72:1, under AA, on every section heading in the rail.
     Same call as the status pills: the design is right and the number was not.
     At .55 it measures 4.80:1 and is still visibly quieter than the .65 nav
     rows above it, which is the whole job of a section label. */
  color:var(--chrome-mute);padding:0 10px;font-weight:700;margin-top:14px;
  height:30px;display:flex;align-items:center;border-top:0}

/* ── a section heading that folds ──────────────────────────────────────────
   Hussain, 02 Sep: "the side bar needs to be folded with arrow to unfold …
   the side bar is now too crowded and confusing."

   ⚠️ THE HEADING IS THE CONTROL, so it is a <button> — reachable by keyboard,
   announcing aria-expanded — rather than a div wearing an onclick.

   ⚠️ AND THE SECTION HOLDING THE OPEN SCREEN IS NEVER FOLDED ON RENDER, which
   is why there is no "you are in here" marker on a shut heading: there cannot
   be one. Walk into a folded section and it opens. */
/* ⚠️ font-family, NOT THE `font` SHORTHAND. `font:inherit` here reset
   font-size and font-weight to the body's 14px/400 and beat .grp's own
   10.5px/700 on order, so every section heading was rendering a third larger
   than the rule that was supposed to define it. Nothing in the markup shows
   this; the measured value in the contrast pass is what caught it. */
button.grp{width:100%;background:none;border:0;cursor:pointer;
  font-family:inherit;font-size:11.5px;font-weight:700;color:var(--chrome-mute);
  border-radius:8px;justify-content:space-between;gap:8px;text-align:left;
  transition:background .12s,color .12s}
button.grp:hover{background:var(--chrome-fill);color:var(--chrome-ink)}
.grp.fold .ic{opacity:.75}
.grp.fold .ic svg{width:14px;height:14px;transition:transform .15s}
.grp.fold.open .ic svg{transform:rotate(90deg)}
html[dir="rtl"] .grp.fold .ic svg{transform:scaleX(-1)}
html[dir="rtl"] .grp.fold.open .ic svg{transform:scaleX(-1) rotate(-90deg)}
.navgrp[hidden]{display:none}
.side .grp:first-child{margin-top:0;padding-top:0}
/* ⚠️ 13.5px, NOT 12.5, AND 38px HIGH. Hussain: "text needs to be little
   bigger". The rail was set a full step under the page it navigates, which
   made it read as a caption rather than as the product's spine. */
.nav{display:flex;align-items:center;gap:10px;width:100%;text-align:left;font:inherit;
  font-size:14px;font-weight:500;color:var(--chrome-mute);background:none;border:0;
  border-radius:9px;padding:0 12px 0 26px;height:38px;transition:background .12s,color .12s}
/* ⚠️ THE ACTIVE ROW IS SOLID TEAL, NOT LIME, AND ONLY BECAUSE THE RAIL IS
   LIGHT. On the dark rail lime was the obvious "you are here" — it is the one
   colour that pops off #0D3B4A. On white it glares, and worse it would make
   lime mean two different things on one screen: the thing you are looking at,
   and the thing to press. The reference's own answer for a selected item on a
   LIGHT tray is white-on-tint with teal text (see .tabs and the employee tab
   strip), and for the strongest possible "this one" on light it uses solid
   teal — which is exactly .btn-primary. So: lime stays the ACTION colour and
   the bar accent, teal marks position. One colour, one job, each. */
.nav:hover{background:var(--chrome-fill);color:var(--chrome-ink)}
/* ⚠️ --accent-ink, NOT --lime. On the paper rail lime is 1.2:1 — unreadable —
   and this row's own comment above already worked out the answer for a light
   tray: solid accent, white text, position marked without borrowing the
   action colour. Each module marks its place in its own hue. */
.nav.on{background:var(--accent-ink);color:#fff;font-weight:700}
.nav .ic{opacity:.8}
.nav:hover .ic,.nav.on .ic{opacity:1}
.nav .ic{width:17px;height:17px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.nav .ic svg{width:16px;height:16px}
/* ⚠️ NO SECOND LANGUAGE ON A NAV ROW. The label used to carry its own
   translation on the right, which put two scripts on every line and pushed
   "Renewal Calendar" and "Government Fees" onto two. One language, chosen by
   the switch, and the row never wraps. */
.nav span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
/* ⚠️ FULL WIDTH (Hussain, 23 Aug 2026): "lets have the pages browser width on
   all pages and adaptable to browser size." The 1360px cap is gone; the grids
   inside are auto-fill and the tables scroll, so the page spends whatever the
   window gives it. */
.main{padding:20px 24px 60px;min-width:0;background:var(--bg)}

/* ══ THE RAIL RUNS FULL HEIGHT, AND THE BAR STARTS BESIDE IT ═════════════════
   Hussain, with the reference: "we need to make the side bar extend to the top
   of the page and then the top bar starts after the side bar ... and the side
   bar can be closed with the button like in the sample".

   Before: one bar across the whole window, rail underneath it.
       [============ bar ============]
       [ rail ][ main ]
   Now:  the rail owns the left edge from y=0, and the bar is the CONTENT
   column's header rather than the window's.
       [ rail ][===== bar =====]
       [ rail ][     main      ]

   ⚠️ position:fixed ON THE RAIL, AND .wrap KEEPS ITS 260px COLUMN. The bar and
   the rail live in different DOM subtrees — chrome() fills #chrome, shell()
   fills #app — so no single grid can contain both. Fixing the rail lets it
   escape #app and reach y=0; the grid column it leaves behind is the gutter
   that keeps .main out from under it. Delete that column and the content slides
   beneath the rail with nothing to say why.

   ⚠️ DESKTOP ONLY. Below 761px the rail is already a drawer that starts
   off-screen and slides over the page, and every rule here would fight it —
   which is why they are scoped rather than written as defaults.

   WARN: AND IT LIVES DOWN HERE, AFTER .side's BASE RULE, ON PURPOSE. Written
   above it these declarations lost every collision on source order — same
   specificity, earlier in the file — so the rail kept `position:sticky;top:58px`
   and started under the bar exactly as before. The check read "rail top 58"
   while the markup and the class were both correct. */
@media(min-width:761px){
  .side{position:fixed;inset:0 auto auto 0;height:100vh;max-height:none;
    width:260px;z-index:45;padding-top:0}
  /* WARN: THE BRAND BAND IS THE BAR'S HEIGHT NOW, AND THAT IS THE NEW
     ALIGNMENT. It used to be 87px, chosen so the first nav row sat level with
     the first card — an invariant that made sense while the rail started
     BELOW the bar. With the rail running to y=0 the two headers are side by
     side, so the thing worth aligning is the rail's header with the page's:
     58px, ending on the same line as the bar, with a rule across both. The
     old measurement did not become wrong so much as obsolete, and the check
     that guarded it now guards this instead. */
  .side-brand{height:58px;margin:0 0 12px;
    border-bottom:1px solid var(--chrome-line)}
  html[dir="rtl"] .side{inset:0 0 auto auto;border-right:0;
    border-left:1px solid var(--chrome-line)}
  /* WARN: A position:fixed GRID ITEM STOPS OCCUPYING ITS TRACK. .wrap was
     `grid-template-columns:260px 1fr` with the rail in column 1; fixing the
     rail took it out of flow, auto-placement moved .main into column 1, and
     the whole page rendered 260px wide UNDERNEATH the rail. It still looked
     plausible in a screenshot of the top of the page — the org chart was the
     thing that gave it away, its chips landing at x=130 inside the rail.

     So the grid goes. A block .wrap and a margin on .main say the same thing
     with nothing to auto-place, and the margin is also what animates when the
     rail folds. */
  .wrap{display:block}
  .wrap > .main{margin-inline-start:260px;transition:margin-inline-start .2s ease}
  /* WARN: THE OFFSET IS CONDITIONAL ON THERE BEING A RAIL, and it was not.
     Written flat, `.top{margin-inline-start:260px}` indented the bar on EVERY
     page — including the Workspace dashboard, which deliberately has no rail.
     The result was 260px of blank paper to the left of the bar and no mark
     anywhere on the page, because the mark lives in the rail header that page
     does not have. Hussain: "top bar look cut because there is no side bar on
     this page, we need to find a better way."

     The better way is one rule instead of two assumptions: the bar is inset
     only while a rail is actually on screen, and the mark appears in the bar
     whenever it is not. Both read the same condition, so they cannot disagree
     — a page with no rail, and a page whose rail is folded away, are the same
     case and neither needs to know about the other. */
  body:has(.side):not(.rail-off) .top{margin-inline-start:260px}
  .top{transition:margin-inline-start .2s ease}

  /* ── collapsed ──────────────────────────────────────────────────────────
     The rail slides out; the bar and the content take the width back. */
  body.rail-off .side{transform:translateX(-100%)}
  html[dir="rtl"] body.rail-off .side{transform:translateX(100%)}
  body.rail-off .wrap > .main{margin-inline-start:0}
}
/* ── the mark, in the bar, when there is no rail to hold it ─────────────────
   One condition, read twice: a rail that is present AND not folded owns the
   mark; in every other case — no rail at all, a folded rail, a phone before
   the drawer is opened — the bar shows it. That is why this is display:none on
   a state rather than display:block on a page: pages come and go, the state is
   the thing that is true. */
/* 22px, asked for on 9 Sep 2026 looking at dopa.tartib.app. Was 30px.
   ⚠️ THE PHONE OVERRIDE IS GONE RATHER THAN LOWERED. It existed to bring 30px
   down to 26px on a cramped bar; 22px is already smaller than the size that
   override was reaching for, so keeping it would have made the mark BIGGER on
   a phone than on a desktop — the rule inverting the moment the base changed. */
.top-mark{height:22px;width:auto;display:block;flex:none;margin-inline-end:2px}
body:has(.side):not(.rail-off) .top-mark{display:none}

/* the toggle sits with the mark, as the reference draws it */
.rail-btn{width:30px;height:30px;flex:none;display:flex;align-items:center;justify-content:center;
  margin-inline-start:auto;border:0;background:none;border-radius:7px;cursor:pointer;
  color:var(--chrome-mute)}
.rail-btn:hover{background:var(--chrome-fill);color:var(--chrome-ink)}
.rail-btn .ic svg{width:17px;height:17px;filter:none}
/* ⚠️ THE ONE THAT RE-OPENS IT LIVES IN THE BAR, because the rail's own button
   goes with the rail. A collapse with no visible way back is a dead end. */
.top .rail-btn{margin-inline-start:0;order:-1}
body:not(.rail-off) .top .rail-btn{display:none}
@media(max-width:760px){ .rail-btn{display:none} .top .rail-btn{display:none!important} }


/* ══ THE PHONE LAYOUT ════════════════════════════════════════════════════════
   Hussain: "we need a proper version HR page for mobile, with proper logo size
   and app style design."

   ⚠️ WHAT WAS HERE BEFORE WAS NOT A PHONE LAYOUT, IT WAS THE RAIL FOLDED FLAT.
   .side went `display:flex;flex-wrap:wrap` and stacked ABOVE the content, so a
   390px screen opened on 460px of navigation — the logo at 60px filling the
   width, four rows, three headings — and you scrolled past all of it to reach
   the word "Dashboard". On a desktop the rail is free: it sits in a column
   nothing else wants. On a phone that column does not exist, so the same rail
   costs the entire first screen.

   An app puts navigation in two places instead: what you use constantly goes in
   a fixed bar at the bottom under your thumb, and everything else goes behind
   one control. That is what this does — the same four pinned rows become the
   tab bar, and the whole rail becomes a drawer.

   ⚠️ 760px, NOT 900. At 900 a landscape tablet — which has room for the rail
   and no thumb reach to the bottom of the screen — was getting the phone
   layout. The rail earns its keep down to about 760.
   ═══════════════════════════════════════════════════════════════════════════ */
@media(max-width:760px){
  .wrap{grid-template-columns:1fr}

  /* ── the rail becomes a drawer ─────────────────────────────────────────
     ⚠️ 100dvh, NOT 100vh. On iOS Safari 100vh is the height with the URL bar
     HIDDEN, so a drawer sized in vh is taller than the visible window and its
     last rows sit under the browser chrome — unreachable, with no scrollbar to
     tell you they are there. dvh tracks the visible viewport. */
  .side{position:fixed;inset:0 auto 0 0;z-index:70;
    width:min(82vw,320px);height:100dvh;max-height:none;min-height:0;
    padding:14px 12px calc(18px + env(safe-area-inset-bottom));
    border-right:1px solid var(--chrome-line);overflow-y:auto;
    transform:translateX(-100%);transition:transform .22s ease;
    box-shadow:0 0 0 rgba(0,0,0,0)}
  html[dir="rtl"] .side{inset:0 0 0 auto;transform:translateX(100%);
    border-right:0;border-left:1px solid var(--chrome-line)}
  .side.open{transform:none;box-shadow:0 18px 50px rgba(16,24,40,.28)}
  /* ⚠️ THE SCRIM IS INERT UNTIL THE DRAWER OPENS. A fixed full-screen element
     at opacity 0 still swallows every tap on the page under it. */
  .side-scrim{position:fixed;inset:0;z-index:69;background:rgba(16,24,40,.45);
    opacity:0;pointer-events:none;transition:opacity .22s ease}
  .side-scrim.open{opacity:1;pointer-events:auto}
  /* ⚠️ THE MARK STAYS, AT A PHONE SIZE. Hussain asked for "proper logo size" —
     the old layout drew it at 60px across a 390px screen, a third of the first
     view spent on a wordmark. In the drawer at 26px it is a header, which is
     what a logo is for here. The 26px left padding is .nav's, so it stays on
     the icons' axis exactly as it does on the desktop rail. */
  .side-brand{height:auto;justify-content:flex-start;
    padding:2px 12px 14px 26px;margin:0 0 8px}
  .side-brand img{height:26px;margin-bottom:0}

  /* ⚠️ 46px ROWS, NOT 38. A row you hit with a thumb, not a cursor: 44px is the
     floor both Apple and Google publish and the rail was three under it. */
  .nav{height:46px;font-size:15px;border-radius:10px}
  .grp,button.grp{height:38px;font-size:11.5px}

  /* ⚠️ `.wrap .main`, NOT `.main`. A dashboard's own sheet is linked AFTER this
     one and HR sets a plain `.main{padding:18px 22px}`; equal specificity means
     later wins, and a media query adds none — so the bottom reserve this rule
     exists to make was being thrown away by a rule that never mentioned
     phones. The extra class is the whole fix. Measured: 18px of a 57px bar. */
  .wrap .main{padding:14px 14px calc(78px + env(safe-area-inset-bottom))}

  /* ── the bottom bar ────────────────────────────────────────────────────
     ⚠️ env(safe-area-inset-bottom) OR THE LAST 34px ARE UNDER THE HOME BAR on
     every notched iPhone — which is exactly where the labels are. */
  .tabbar{position:fixed;left:0;right:0;bottom:0;z-index:60;display:grid;
    grid-auto-flow:column;grid-auto-columns:1fr;
    background:var(--chrome);border-top:1px solid var(--chrome-line);
    padding-bottom:env(safe-area-inset-bottom);
    box-shadow:0 -1px 0 rgba(16,24,40,.03)}
  .tab{display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:3px;min-height:56px;padding:7px 2px 8px;font:inherit;font-size:10.5px;
    font-weight:600;color:var(--chrome-mute);background:none;border:0;
    -webkit-tap-highlight-color:transparent}
  .tab .ic svg{width:21px;height:21px;filter:none}
  .tab span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* ⚠️ COLOUR AND WEIGHT, NOT A PILL. The rail marks position with a solid
     plate because it has the width for one; a 74px-wide tab does not, and a
     plate that tight reads as a button somebody is holding down. */
  .tab.on{color:var(--accent-ink);font-weight:800}
  .tab.on .ic svg{stroke-width:2}

  /* wide content scrolls inside its own box rather than the page */
  .scroll,.dash-org{overflow-x:auto;-webkit-overflow-scrolling:touch}
}
/* ⚠️ THE MENU BUTTON IS HIDDEN BY DEFAULT, NOT BY A DESKTOP MEDIA QUERY, and
   this is a regression I shipped. TartibUI.topbar() renders it on EVERY page,
   and the rule that hid it above 760px lived here — in a sheet the Workspace
   hub does not link. So the hub grew a hamburger at full desktop width that
   opened nothing, because the hub has no .side for it to open.

   Default hidden, shown only where a drawer exists to be opened. A page that
   does not link this file now gets no button rather than a dead one. */
.menu-btn{display:none}
/* the drawer and its bar exist only on the phone layout */
@media(min-width:761px){ .tabbar,.side-scrim{display:none} }
@media(max-width:760px){ .wrap ~ * .menu-btn,body:has(.side) .menu-btn{display:flex} }

@media(max-width:900px) and (min-width:761px){
  .main{padding:18px 14px 60px}
}

/* ── type ───────────────────────────────────────────────────────────────── */
/* ⚠️ DENSITY PASS, 23 Aug 2026 (Hussain: "boxes and buttons are very bulky…
   need to look super clean and a little more compact"). Every size below the
   Koobs-literal copy was taken down one notch — smaller headings, tighter card
   padding, shorter rows, slimmer buttons. The PROPORTIONS and the colour system
   are untouched; only the air came out. */
/* ══ TYPE SCALE, ONE STEP UP — 02 Sep 2026 ═══════════════════════════════════
   Hussain, twice: "text needs to be little bigger", then "increase the fonts a
   bit also". The whole scale was set while the shell was dark and dense, and a
   dark chrome flatters small type in a way paper does not — 10.5px labels that
   read as crisp on #0D3B4A read as squinting on #FFFFFF.

   ⚠️ ONE STEP, EVERYWHERE, NOT A NEW SCALE. Every size moves by roughly one
   notch and the RATIOS between them are unchanged, so nothing re-flows into a
   different hierarchy: the stat value is still about twice its label, the table
   is still a step under the body, the section heading is still the quietest
   thing in the rail. Bumping a few sizes and not the rest is how a type scale
   turns into a list of exceptions.

   ⚠️ AND .grp/.stat .k/th STAY THE SMALLEST THINGS ON THE PAGE ON PURPOSE.
   They are labels for the thing under them; a label that competes with its own
   value is noise. They moved 10.5 -> 11.5, not to 13. */
h1{font-size:21px;color:var(--ink);margin:0 0 2px;letter-spacing:-.3px}
h2{font-size:16.5px;color:var(--ink);margin:0 0 3px}
.lede{font-size:12.5px;color:var(--mute);margin-bottom:14px}

/* ── cards ────────────────────────────────────────────────────────── */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  padding:14px 16px;margin-bottom:12px;box-shadow:var(--shadow)}
/* Koobs-literal 15px, taken to 13.5px in the 23 Aug density pass. */
.card-t{font-size:14.5px;font-weight:700;color:var(--ink);margin-bottom:10px;
  display:flex;align-items:center;justify-content:space-between;gap:10px}
.card-t a,.card-t .lnk{font-size:12.5px;font-weight:600;color:var(--accent-deep);cursor:pointer}

/* ── stat cards — Koobs-literal since 23 Aug 2026 ────────────────────
   ⚠️ THE SEVERITY TOP-BORDER IS GONE, approved on the sample. The reference's
   stat card is a plain white card: emoji icon floated right, uppercase muted
   label, 30px/800 value. Severity lives in the number and the pills, not the
   frame. .bad/.warn/.ok/.info/.brand stay ACCEPTED so existing markup keeps
   rendering — they simply no longer paint anything. */
/* ⚠️ auto-FIT, AND THE EMOJI RIDES INSIDE THE FLEX LABEL ROW (23 Aug 2026,
   "bleeding boxes" on the live dashboard). auto-fill keeps invisible EMPTY
   tracks on a wide window, and a floated icon is a layout that owes nobody an
   explanation — .k is already space-between, so the icon is simply its
   right-hand item and nothing can escape the card. */
/* Density pass 23 Aug 2026: 30px/18px cards read as billboards on a screen of
   six; 22px on 12px padding says the same number in half the height. */
/* ⚠️ 182, NOT 170, SINCE THE TYPE WENT UP A STEP. At 170 a 1440px screen
   fits all six cards at ~176px and the Companies card's second line —
   "11 branches between them" — runs into .stat's overflow:hidden and gets
   cut mid-word. Six cramped cards are worse than five roomy ones and a
   sixth below; the min-width is what chooses between those. */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(182px,1fr));gap:12px;margin-bottom:14px}
.stat{background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r);padding:12px 14px;box-shadow:var(--shadow);min-width:0;overflow:hidden}
.stat .ic{font-size:16px;line-height:1;display:inline-flex;align-items:center}
.stat .k{font-size:11.5px;font-weight:600;letter-spacing:.55px;text-transform:uppercase;
  color:var(--mute);display:flex;align-items:center;justify-content:space-between;gap:6px}
.stat .k>span:first-child{min-width:0}
.stat .v{font-size:24px;font-weight:800;color:var(--ink);line-height:1.15;margin:4px 0 0;letter-spacing:-.5px}
.u{margin-inline-start:3px}
.stat .v .u{font-size:12px;font-weight:700;color:var(--mute)}
.stat .v .dec{font-size:14px;font-weight:700}
.stat .s{font-size:12px;color:var(--mute);margin-top:2px;line-height:1.4}
.stat .s em{font-style:normal;color:var(--ink);font-weight:700}

/* ── engraved icons ─────────────────────────────────────────────────────────
   ico() draws `<span class="ic"><svg viewBox="0 0 24 24" stroke="currentColor">`.
   The sidebar sized those under `.nav .ic svg` and NOWHERE ELSE, which was fine
   while the sidebar was the only caller. The moment the stat cards started
   asking for one, an unsized <svg> fell back to its intrinsic 300x150 and blew
   the card apart. So the size lives here, on `.ic svg`, and `.nav` keeps its
   own 16px override.

   ⚠️ THE LIFT IS ONE PIXEL OF WHITE UNDERNEATH, NOT A SHADOW ABOVE. A dark
   drop-shadow on a 1.9px stroke turns it into a smudge at this size; a white
   edge below a muted stroke is the letterpress trick and it survives. It is
   also why it must be switched off on the chrome: on the teal bar the lift
   would draw a white outline around every glyph.

   ⚠️ currentColor IS THE POINT — never paint these. A .stat.bad colours its own
   text and the icon follows it down to the same red; give the icon a hue of its
   own and the card is now saying two things at once. That is exactly what the
   colour emoji did, and why they are gone. */
.ic{display:inline-flex;align-items:center;justify-content:center;flex:none}
.ic svg{width:16px;height:16px;display:block;
  filter:drop-shadow(0 1px 0 rgba(255,255,255,.9))}
.stat .ic{color:var(--mute)}
.stat.bad .ic,.stat.warn .ic,.stat.ok .ic,.stat.brand .ic,.stat.info .ic{color:inherit;opacity:.9}
/* ⚠️ THE SEVERITY CLASSES PAINT AGAIN (29 Sep 2026). The 23 Aug retirement
   kept .bad/.warn/.ok/.info "accepted" but left nothing that paints them, so a
   dashboard with three expired papers drew the same grey card as one with
   none — the reference (hr.koobs.cafe) colours the frame, the label and the
   number. A card is painted ONLY when its caller passes the class, and the
   callers pass it only when the count means something (> 0): a tile that is
   always red says nothing. .brand stays unpainted on purpose (it is emphasis,
   not severity). .hold/.teal/.violet/.cyan are the attendance and budget
   hues, from the token set — never a literal colour here. */
.stat.bad,.stat.warn,.stat.ok,.stat.info,.stat.hold,.stat.teal,.stat.violet,.stat.cyan{border-top:3px solid var(--line)}
.stat.bad{border-top-color:var(--bad-line)}   .stat.bad .k,.stat.bad .v{color:var(--bad)}
.stat.warn{border-top-color:var(--warn-line)} .stat.warn .k,.stat.warn .v{color:var(--warn)}
.stat.ok{border-top-color:var(--ok-line)}     .stat.ok .k,.stat.ok .v{color:var(--ok)}
.stat.info{border-top-color:var(--info-line)} .stat.info .k,.stat.info .v{color:var(--info)}
.stat.hold{border-top-color:var(--hold-line)} .stat.hold .k,.stat.hold .v{color:var(--hold)}
.stat.teal{border-top-color:var(--cat-7)}     .stat.teal .k,.stat.teal .v{color:var(--cat-7)}
.stat.violet{border-top-color:var(--cat-2)}   .stat.violet .k,.stat.violet .v{color:var(--cat-2)}
.stat.cyan{border-top-color:var(--cat-8)}     .stat.cyan .k,.stat.cyan .v{color:var(--cat-8)}
.stat.hold .ic,.stat.teal .ic,.stat.violet .ic,.stat.cyan .ic{color:inherit;opacity:.9}
/* a card that opens something lifts on hover, like the reference's */
.stat.go{cursor:pointer;transition:box-shadow .15s}
.stat.go:hover{box-shadow:var(--shadow-md)}
h1 .ic svg,.pgh h1 .ic svg{width:19px;height:19px}
h1 .ic,h2 .ic,h3 .ic,.card-t .ic,.pgh .ic{color:var(--mute)}
/* the lift is right on the paper shell; only the dark theme outlines it */
:root[data-theme="dark"] .topbar .ic svg,
:root[data-theme="dark"] .side .ic svg{filter:none}
/* inline beside text in a pill or a cell */
.pill .ic,td .ic{margin-inline-end:5px;vertical-align:middle}
.pill .ic svg,td .ic svg{width:13px;height:13px}

/* ── tables ─────────────────────────────────────────────────────────────── */
.scroll{overflow-x:auto}
/* ⚠️ 13px AND A 2px HEADER RULE, from the reference. Ours was 13.5px text on a
   1px hairline with a mono header — lighter, but it let the header float into
   the first row. A heavier rule under the header and a lighter one between rows
   is what makes a long register scannable. */
table{width:100%;border-collapse:collapse;font-size:13.5px}
th{font-size:11.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  color:var(--mute);text-align:left;padding:7px 10px;border-bottom:2px solid var(--line);
  white-space:nowrap}
td{padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:middle}
tbody tr:hover{background:#F8FAFB}
.table td b{color:var(--ink)}
tr.click{cursor:pointer}
tr.click:hover td{background:var(--surface-2)}
/* ⚠️ START-ALIGNED SINCE 23 Aug 2026 (Hussain: "all tables need to align text
   properly like Ref. no."). Most .num cells are IDENTIFIERS — references,
   dates, employee numbers — and a right-aligned id under a left-aligned header
   reads as a mistake. MONEY still wants right alignment so magnitudes line up:
   that is .num-r, applied to the few cells that hold amounts. */
.num{font-family:var(--mono);font-size:12.5px;text-align:start;white-space:nowrap}
.num-r{text-align:end}

/* ── pills: the semantic four (+hold), and nothing else ──────────────── */
/* ⚠️ NO BORDER AND NO MONO — Koobs-literal since 23 Aug 2026, approved on the
   side-by-side sample. The reference's pills are a wash and a colour at
   11.5px/600; the ring our first copy added was visible furniture. The
   AA-corrected inks STAY — they are the invisible half of the copy. The
   classless pill is the reference's grey one. */
.pill{display:inline-block;white-space:nowrap;font-size:12px;font-weight:600;
  border:0;border-radius:var(--r-pill);padding:3px 10px;background:#EEF1F3;color:var(--mute)}
.pill.ok{color:var(--ok);background:var(--ok-bg)}
.pill.warn{color:var(--warn);background:var(--warn-bg)}
.pill.bad{color:var(--bad);background:var(--bad-bg)}
.pill.hold{color:var(--hold);background:var(--hold-bg)}
.pill.info{color:var(--info);background:var(--info-bg)}
/* the two attendance hues the reference keeps apart from amber and grey:
   leave is teal (not "late"), a public holiday is violet (not a day off) */
.pill.teal{color:var(--cat-7);background:var(--cat-7-bg)}
.pill.violet{color:var(--cat-2);background:var(--cat-2-bg)}

/* ── controls ───────────────────────────────────────────────────────────── */
/* Koobs-literal inputs: white field, 1px hairline, teal focus; muted 600 label. */
label{display:block;font-size:11.5px;font-weight:600;color:var(--mute);margin:8px 0 3px}
input,select,textarea{width:100%;font:inherit;font-size:13px;color:var(--ink);
  background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:7px 10px;
  transition:border-color .15s}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent)}
/* ══ BUTTONS ════════════════════════════════════════════════════════════════
   ⚠️ TAKEN FROM THE REFERENCE STYLESHEET, NOT REDRAWN FROM A SCREENSHOT.
   Hussain, 22 Aug 2026: "Tartib looks bulkey and the buttons are not good like
   hr.koobs". He was right and the cause was specific: these carried
   font-weight 800, a 1.5px border on the ghost variant and a shadow, at 13.5px
   on a 12px radius. The reference's are FLAT — 600 weight, no border, no
   shadow, 8px radius, 11px/16px padding — and that difference is most of why
   one reads as a product and the other as a wireframe of one.

   ⚠️ FOUR VARIANTS, AND EACH ONE MEANS SOMETHING:
     .btn          teal    — the primary action on a screen. One per screen.
     .btn.lime     lime    — the CREATE action, and the only lime on the page
                             body. Its ink is teal; lime is never a text colour.
     .btn.ghost    outline — everything secondary.
     .btn.danger   red     — destructive, and washed rather than solid so it
                             does not out-shout the primary action beside it. */
/* Density pass 23 Aug 2026: the reference's 14px/11px×16px pad, one notch down. */
/* ⚠️ --accent-ink, NOT --chrome. The button borrowed the bar's fill, which
   was fine while the bar was teal and is white-on-white now. */
.btn{font:inherit;font-size:13px;font-weight:600;background:var(--accent-ink);color:#fff;
  border:0;border-radius:8px;padding:8px 13px;transition:background .15s,opacity .15s}
.btn:hover{background:var(--accent-deep)}
.btn:active{transform:translateY(1px)}
.btn[disabled]{opacity:.5;cursor:not-allowed}
.btn.lime{background:var(--lime);color:var(--lime-ink)}
.btn.lime:hover{background:var(--lime-hi)}
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
.btn.ghost:hover{background:#F0F3F4}
.btn.danger{background:var(--bad-bg);color:var(--bad);border:0}
.btn.sm{padding:4px 10px;font-size:11.5px}
.btn:focus-visible{outline:2px solid var(--lime);outline-offset:2px}
.msg{border-radius:var(--r-sm);padding:8px 12px;font-size:12.5px;margin:8px 0;display:none}
.msg.ok{display:block;background:var(--ok-bg);color:var(--ok)}
.msg.err{display:block;background:var(--bad-bg);color:#8C3A1D}
/* a note (blue) and a caution (amber) — the reference's "no upcoming leaves" and
   "confirm against your invoice" lines, in the token hues */
.msg.info{display:block;background:var(--info-bg);color:var(--info)}
.msg.warn{display:block;background:var(--warn-bg);color:var(--warn)}
.spin{width:22px;height:22px;border:2.5px solid var(--line);border-top-color:var(--accent);
  /* ⚠️ `t-sp`, not `sp`. This said animation:sp against @keyframes t-sp, so
     every loading spinner in HR has been a static ring since it shipped —
     silent, because an unknown animation name is not a CSS error. */
  border-radius:50%;animation:t-sp .7s linear infinite;margin:30px auto}
@keyframes t-sp{to{transform:rotate(360deg)}}
.row{display:flex;gap:9px;align-items:center;flex-wrap:wrap}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:0 15px}
.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:0 15px}
@media(max-width:760px){.grid2,.grid3{grid-template-columns:1fr}}


/* ── a location's pin ───────────────────────────────────────────────────── */
.map{width:100%;height:260px;border:1px solid var(--line);border-radius:var(--r-sm);display:block}
.mapless{border:1px dashed var(--line);border-radius:var(--r-sm);padding:28px 18px;text-align:center;background:var(--surface-2)}

