@import url("theme.css");

/* =========================================================================
   HR-only additions to the Glisco console theme.

   The palette itself is theme.css, imported above: the phone page at /self
   needs the same six colours and none of the console rules in this file.

   app.css in this folder is a BYTE-IDENTICAL copy of
   glisco-ops/public/app.css. Nothing HR-specific goes in it, so `diff` on the
   two files is the drift check — and when HR moves into that console, the
   shared half needs no merge at all.

   Everything below is either a component the ops console has no use for yet
   (severity stripes, the approval queue) or a page-level layout that will
   travel with its page.
   ========================================================================= */

/* --------------------------------------------------------- ONE COLUMN ONLY --
   The shell wraps every page in .wrap: 1180px, centred, with its margins.
   Anything inside it that ALSO sets a width or a padding gives that page a
   second, narrower column and different margins from its neighbours — which
   is why some pages started 50px lower than others and ran 80px narrower.

   Six names for the same mistake, collected from the pages: .page-wrap
   (payslips, open positions, reviews, notices), .container (ID cards),
   .container-fluid (employees, master data, users, archived), .wrapper and
   .main-card (exit papers, increment letters), .workspace.

   app-legacy.css neutralised three of them — but eighteen rail pages
   deliberately do not load app-legacy, so on those it did nothing. This file
   every rail page loads.

   A page that genuinely wants a narrower measure uses .wrap--narrow on the
   shell's own wrap. */
/* THE LEGACY CONTAINERS ADOPT THE SHELL'S MEASURE — they do not lose it.
   Zeroing them was half a fix. It stopped them being a second, narrower
   column inside .wrap, and it left the eleven pages that have no .wrap at all
   running full-bleed to the window edge — so walking from Dashboard to
   Reviews moved the left margin and changed the measure, which is exactly
   what "the layout changes between pages" is.

   `.wrap` is `max-width:1180px; margin:0 auto; padding:1.5rem 1.1rem 4rem`.
   A legacy container that is NOT inside a .wrap now gets that same measure,
   so every page in the console has one width and one top edge. One inside a
   .wrap still collapses to nothing, because its parent is already providing
   it. */
.main-content .page-wrap,
.main-content .wrapper,
.main-content .workspace,
.main-content .main-card {
  max-width: 1180px; width: auto; margin: 0 auto;
  padding: 1.5rem 1.1rem 4rem;
  background: none; border: 0; border-radius: 0; box-shadow: none;
}

/* Nested inside the shell's own wrap, they are redundant. */
.main-content .wrap .page-wrap,
.main-content .wrap .wrapper,
.main-content .wrap .workspace,
.main-content .wrap .main-card {
  max-width: none; margin: 0; padding: 0;
}

/* Bootstrap's two containers lose their width and their vertical padding, but
   KEEP their 12px horizontal padding. That padding is not decoration: it is
   what absorbs the -12px margins on .row, and zeroing it — which
   app-legacy.css did for .container-fluid.py-4 — pushes the first and last
   column of every Bootstrap grid 12px outside the page. On Employees that put
   the Export CSV button half off the right edge. */
.main-content .container,
.main-content .container-fluid {
  max-width: 1180px; width: auto; margin: 0 auto;
  padding-top: 1.5rem; padding-bottom: 4rem;
}
.main-content .wrap .container,
.main-content .wrap .container-fluid {
  max-width: none; margin: 0;
  padding-top: 0; padding-bottom: 0;
}

/* The generated document, below the form that generates it.
   Hidden until there is one — the JS on both pages sets style.display when it
   has something to show. Increment Letters had no definition for this at all
   after the retheme deleted its stylesheet, so the page opened with a
   "Download PDF" button under an empty preview and nothing to download. */
.preview-area { display: none; margin-top: 1.4rem; }

/* ---------------------------------------------- the employee-lookup panel --
   Search for somebody, get a list, load one. Increment Letters and Payslips
   both carry this markup — .db-panel / .db-panel-header / .db-panel-title,
   an .emp-search-bar and an #emp-results-box — and it was defined on the
   payslip page only. The retheme deleted Increment's own stylesheet and
   nothing replaced it, so on that page the panel had no ground, no border and
   no heading: "Employee Database" and "70 employees loaded" rendered as two
   lines of unstyled text above a bare input.

   Payslips still has its own copy, which is more specific in that document
   and wins there. This is the one every other page gets. */
.db-panel {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0.9rem 1rem; margin-bottom: 1rem;
}
.db-panel-header {
  display: flex; align-items: baseline; gap: 0.8rem;
  flex-wrap: wrap; margin-bottom: 0.7rem;
}
.db-panel-title { font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.db-panel-header #db-status,
.db-panel .db-stats { margin-left: auto; font-size: 0.8rem; color: var(--ink-3); }
.emp-search-bar { display: flex; gap: 0.5rem; }
.emp-search-bar input { flex: 1 1 auto; min-width: 0; }

/* ------------------------------------------------------------ page header -- */
.page-head {
  display: flex; align-items: flex-start; gap: 1rem;
  flex-wrap: wrap; margin-bottom: 1.2rem;
}
.page-head h1 { font-size: 1.5rem; letter-spacing: -0.02em; }
.page-head p { color: var(--ink-3); font-size: 0.9rem; margin-top: 0.2rem; max-width: 62ch; }
.page-head__actions { margin-left: auto; display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* ONE HEADING TREATMENT, whichever convention a page was built with.
   Three grew up here: .page-head with a bare <h1> on the pages written after
   the theme landed, .page-title inside a .topbar on the ones retrofitted into
   it, and a naked <h1> on the oldest. They rendered at three different sizes
   and weights, which is most of why every page looked like a different
   product.
   Rather than rewrite twenty-one files and risk what the payslip rebuild
   already taught, the selectors converge on the same declarations. New pages
   should use .page-head; these two keep the rest looking right meanwhile. */
/* Every page heading, wherever the page happens to keep it.
   An earlier attempt listed the containers — .wrap > h1, .page-wrap > h1 and so
   on — and missed Increment, whose <h1> sits two levels deeper inside a
   .main-card. Enumerating each page's nesting is a game you lose one page at a
   time, so the rule stops caring: a top-level heading inside the console looks
   like a top-level heading. .page-head h1 above already matches this and says
   the same thing. */
.main-content h1,
.page-title {
  font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em;
  color: var(--ink);
  /* `margin-top: 0` with !important, because several of these pages put a
     Bootstrap `mt-2` on their heading — and a utility class is !important, so
     without this the first heading sits 8px lower on those pages than on the
     rest. Two pages differing by 8px at the top of the screen is the sort of
     thing nobody can name and everybody notices. */
  margin: 0 0 1.1rem !important;
}
/* Whatever the page put straight after the heading is its standfirst. */
.main-content h1 + p,
.main-content h1 + div:not([class]),
.page-title + p,
.page-title + div:not([class]) {
  color: var(--ink-3); font-size: 0.9rem; margin: -0.85rem 0 1.2rem;
  max-width: 62ch;
}
/* Inside .page-head the flex parent owns the spacing, so the heading gives it
   back. */
.page-head h1 { margin: 0 !important; }
.page-head h1 + p { margin: 0.2rem 0 0; }

/* ---------------------------------------------------------------- panels -- */
/* The box almost every page puts its content in.
   This lived only in app-legacy.css, which the pages written after the theme
   deliberately do not load — so on those pages .panel had no background, no
   border and no margin, and two panels' contents simply overlapped. It is a
   core primitive, not a transitional shim, so it belongs here. app-legacy keeps
   the .card-box / .box aliases for the older markup. */
.panel {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.1rem 1.2rem; margin-bottom: 1rem;
}
.panel:last-child { margin-bottom: 0; }
.panel > h2:first-child, .panel > h3:first-child { margin-top: 0; }

/* ------------------------------------------------------------- tool cards -- */
/* The dashboard. A card here is a door to a tool, so it gets the hover state
   and the arrow; nothing else in the console does. */
.tool {
  display: flex; flex-direction: column; gap: 0.45rem;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.05rem 1.15rem;
  text-decoration: none; color: inherit;
  border-top: 3px solid var(--accent, var(--teal));
  transition: box-shadow 0.15s, transform 0.15s;
}
.tool:hover { box-shadow: 0 6px 18px -12px rgba(19,26,28,0.35); transform: translateY(-1px); }
.tool__icon {
  width: 34px; height: 34px; border-radius: 8px;
  display: grid; place-items: center; font-size: 1.05rem;
  background: var(--teal-pale); color: var(--teal-dark);
}
.tool h2 { font-size: 1rem; display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.tool p { font-size: 0.85rem; color: var(--ink-3); flex: 1; }
.tool__go { font-size: 0.78rem; font-weight: 600; color: var(--teal-dark); }

/* ------------------------------------------------------------- severities -- */
/* Data Health. Semantic, and deliberately not the brand teal: "critical" must
   never be able to read as "emphasis". */
.sev-critical { --sev: var(--err);  --sev-bg: #FBE9E7; }
.sev-warning  { --sev: var(--warn); --sev-bg: #FBF1DF; }
.sev-info     { --sev: var(--ink-3); --sev-bg: #EDEFF0; }

.finding {
  background: var(--card); border: 1px solid var(--line);
  border-left: 3px solid var(--sev, var(--line));
  border-radius: var(--radius); margin-bottom: 0.8rem; overflow: hidden;
}
.finding__head {
  display: flex; align-items: flex-start; gap: 0.8rem;
  padding: 0.95rem 1.1rem; cursor: pointer; width: 100%;
  background: none; border: 0; font: inherit; text-align: left; color: inherit;
}
.finding__head:hover { background: #FBFCFC; }
.finding__head h2 { font-size: 1rem; }
.finding__why { color: var(--ink-2); font-size: 0.88rem; margin-top: 0.35rem; max-width: 74ch; }
.finding__fix { font-size: 0.84rem; margin-top: 0.45rem; color: var(--ink); }
.finding__fix i { color: var(--ok); }
.finding__count {
  margin-left: auto; background: var(--sev-bg, #EDEFF0); color: var(--sev, var(--ink-3));
  font-size: 0.78rem; font-weight: 700; border-radius: 100px;
  padding: 0.1rem 0.6rem; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.finding__rows { border-top: 1px solid var(--line); }
.finding__row {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.5rem 1.1rem; border-bottom: 1px solid var(--line); font-size: 0.87rem;
}
.finding__row:last-child { border-bottom: 0; }
.finding__row .note { margin-left: auto; color: var(--ink-3); font-size: 0.82rem; text-align: right; }

/* ---------------------------------------------------------------- empty -- */
.empty { background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 3rem 1.5rem; text-align: center; }
.empty i { font-size: 2rem; color: var(--ok); }
.empty h2 { font-size: 1.05rem; margin-top: 0.7rem; }
.empty p { color: var(--ink-3); font-size: 0.87rem; margin-top: 0.2rem; }

/* --------------------------------------------------------- approval queue -- */
/* Accounts LDAP created that nobody has approved. Warm, because it is a task
   somebody has to do rather than a fault. */
.queue {
  background: #FBF1DF; border: 1px solid #EBD8AE;
  border-radius: var(--radius); padding: 1rem 1.1rem; margin-bottom: 1.2rem;
}
.queue__head { display: flex; gap: 0.6rem; align-items: flex-start; }
.queue__head i { color: var(--warn); font-size: 1.1rem; }
.queue__head strong { display: block; }
.queue__head span { font-size: 0.85rem; color: #7A5410; }
.queue table { margin-top: 0.8rem; background: #fff; border-radius: 8px; }
.queue th { background: #FDF8EF; }

/* --------------------------------------------------------------- sign in -- */
.login-sub { color: var(--ink-3); font-size: 0.88rem; margin-bottom: 1.3rem; }
.or {
  display: flex; align-items: center; gap: 0.75rem; margin: 1.1rem 0;
  color: var(--ink-3); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.12em;
}
.or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.sso-btn {
  display: flex; align-items: center; justify-content: center; gap: 0.6rem;
  width: 100%; padding: 0.7rem 1rem; border-radius: 8px; border: 1px solid var(--ink);
  background: var(--ink); color: #fff; font: inherit; font-weight: 600;
  text-decoration: none; cursor: pointer;
}
.sso-btn:hover { background: #000; }
/* STACKED fields only, which means the login card — the one place in the
   product where fields sit one under the next with nothing between them.

   It used to be unscoped, as `.field + .field`, and that put 11px of
   margin-top on the SECOND field of every horizontal row of fields in the
   console: .field-row on the two offer generators, the incentive plans
   annexure and open positions, and .calc-pair on the salary calculator. Two
   inputs side by side, the right one sitting eleven pixels lower than the
   left. It reads as sloppiness rather than as a bug, which is why it survived
   this long — you notice it and cannot say what is wrong. */
.login-card .field + .field { margin-top: 0.7rem; }

/* --------------------------------------------------------------- utility -- */
.stack { display: grid; gap: 1rem; }
/* `.right` means two things, because it was written for one and used for the
   other.

   It arrived as a FLEX utility — push me to the end of the row — and that is
   what `margin-left: auto` does. It is then used 68 times across fourteen
   pages, and most of those are <th> and <td> in a figures table, where
   margin-left does nothing whatsoever. So every number column in Payroll,
   Off Payroll, Expense Claims and Reconcile was left-aligned under a heading
   that had asked to be right-aligned, and the digits did not line up.

   Renaming it in fourteen files is the tidier answer and the worse one: the
   flex use is correct and there is no reason a page author should have to
   know which of the two they are in. So the class does the right thing in
   both places. app.css's `td.num` stays as it is; this is the one people
   actually reach for. */
.right { margin-left: auto; }
th.right, td.right { text-align: right; margin-left: 0; }
.nowrap { white-space: nowrap; }
.tnum { font-variant-numeric: tabular-nums; }
.stamp { color: var(--ink-3); font-size: 0.78rem; margin-top: 1.2rem; }

/* ====================================================================== *
   Sidebar scrolling.

   THIS BELONGS UPSTREAM in glisco-ops/public/app.css. It is here so that
   app.css stays byte-identical to ops' copy and `diff` remains the drift
   check — but ops has the same bug waiting for it, and it should go across
   when HR merges. It is a fix, not an HR preference.

   The bug: .sidebar is `position: fixed` with `min-height: 100vh`, a column
   flex container, and no overflow rule. Ops has fifteen nav items and just
   fits on a laptop; HR has sixteen plus four section headings, so the
   sidebar grows past the viewport, and because it is fixed there is nothing
   to scroll — the user panel, Sign out and Collapse are simply unreachable.

   Three things make it work, and all three are load-bearing:

     height, not min-height   a flex container has to be bounded before its
                              children can be told to scroll inside it
     min-height: 0 on .nav    without it a flex item refuses to shrink below
                              its content, so overflow-y never engages. This
                              is the one everybody misses
     flex: 0 0 auto           on brand, foot and rail, so only the nav gives

   100dvh rather than 100vh because on mobile browsers 100vh includes the
   chrome that is about to slide away, which puts the sign-out button just
   off-screen.
 * ====================================================================== */
.sidebar {
  height: 100dvh;
  overflow: hidden;
}
.sidebar-brand,
.sidebar-foot,
.rail-btn { flex: 0 0 auto; }

.sidebar .nav {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Vertical only. A long menu label pushes the list wider than the rail and
     puts a horizontal scrollbar along the bottom of the sidebar — which reads
     as a broken layout, not as a hint that a word is cut off. */
  overflow-x: hidden;
  overscroll-behavior: contain;
}

/* And the labels themselves stop rather than push. */
.sidebar .nav-link { min-width: 0; }
.sidebar .nav-link span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* margin-top:auto pushed the footer down when nothing scrolled. Now the nav
   takes the slack, and leaving it in fights the flex sizing. */
.sidebar-foot { margin-top: 0; }

/* A visible scrollbar on a dark sidebar looks like a seam, and an invisible
   one hides that there is more below. Thin and dim: present when you look. */
.sidebar .nav { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.22) transparent; }
.sidebar .nav::-webkit-scrollbar { width: 6px; }
.sidebar .nav::-webkit-scrollbar-track { background: transparent; }
.sidebar .nav::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.18); border-radius: 3px;
}
.sidebar .nav:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,.3); }

/* ====================================================================== *
   Collapsible nav sections.

   ALSO BELONGS UPSTREAM in ops' app.css — an admin there will carry every
   section of both consoles in one rail, which is the case this is for.

   The first section never gets a header and never folds: it is the everyday
   work, and a console whose main list can be hidden is a console people
   lose. Sections default to OPEN, because collapsed-by-default hides from a
   new user that the rest of the tool exists.
 * ====================================================================== */
.nav-group { display: block; }

.sidebar-heading--btn {
  display: flex; align-items: center; gap: 0.4rem; width: 100%;
  background: none; border: 0; font: inherit; cursor: pointer;
  /* Matches .sidebar-heading exactly, so the button is invisible as a control
     until you hover it — the words have to sit where they always did. */
  padding: 1.1rem 1.25rem 0.35rem; font-size: 0.62rem; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: rgba(255,255,255,0.32);
  text-align: left;
}
.sidebar-heading--btn:hover { color: rgba(255,255,255,0.6); }
.sidebar-heading--btn span { flex: 1; }
.sidebar-heading--btn i {
  font-size: 0.7rem; transition: transform 0.18s; flex: 0 0 auto;
  transform: rotate(-90deg);
}
.nav-group.open > .sidebar-heading--btn i { transform: rotate(0deg); }
.sidebar-heading--btn:focus-visible {
  outline: 2px solid var(--teal); outline-offset: -2px;
}

.nav-group__items { display: none; }
.nav-group.open > .nav-group__items { display: block; }

/* Collapsed to a rail there are no words to fold, so every section shows its
   icons and the headers disappear with them — otherwise a fold would hide
   icons behind a chevron the rail is too narrow to label. */
body.rail-collapsed .sidebar-heading--btn { display: none; }
body.rail-collapsed .nav-group__items { display: block; }

@media (max-width: 991.98px) {
  body.rail-collapsed .sidebar-heading--btn { display: flex; }
  body.rail-collapsed .nav-group:not(.open) > .nav-group__items { display: none; }
}

/* ====================================================================== *
   Screenshot watermark.

   ALSO FOR UPSTREAM in ops' app.css — same console, same exposure, and the
   ops side shows customer addresses.

   The viewer's name and today's date, tiled diagonally at 20% black, so a
   photograph of a screen or a forwarded screenshot carries who was looking
   and when. A leak with a name on it is a conversation; an anonymous one is
   an investigation.

   Four properties are load-bearing:

     pointer-events: none   it must not swallow a click. Without this the
                            whole console becomes unusable
     user-select: none      so dragging to select text does not select forty
                            copies of somebody's name
     position: fixed        it stays over the viewport as the page scrolls,
                            rather than only over the first screenful
     z-index                above content, BELOW modals and the sidebar. A
                            watermark that lands on top of a dialog obscures
                            the thing somebody is trying to read, and an
                            obscured dialog is how a feature gets switched off

   Hidden in print on purpose: the payslip, ID card, offer letter and exit
   paper pages produce documents that are GIVEN to people, and an ID card
   with an HR user's name across it is broken output.
 * ====================================================================== */
.watermark {
  position: fixed;
  inset: 0;
  z-index: 1020;                 /* sidebar is 1040, mobilebar 1030, modals above */
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  background-repeat: repeat;
  background-position: 0 0;
}

@media print {
  .watermark { display: none !important; }
}

/* Where somebody has asked for a quieter console — a projector, a screen share
   with a client — the mark can be dimmed but not removed. Halving it keeps it
   legible in a photograph while taking it out of the way on a wall. */
body.watermark-quiet .watermark { opacity: 0.5; }

/* ====================================================================== *
   Employee search.

   The pattern the increment and transfer letters already use: type a name or
   a code, pick from a short list, and the record loads. Lifted out of those
   pages' own stylesheets so Advances uses the same component rather than a
   second one that behaves almost the same.

   Search by NAME as well as code, deliberately. Nobody in HR knows that
   Divakar Sharma is 30151; they know his name.
 * ====================================================================== */
.emp-search { position: relative; }
.emp-search input { padding-left: 2.1rem; }
.emp-search__icon {
  position: absolute; left: 0.7rem; top: 50%; transform: translateY(-50%);
  color: var(--ink-3); pointer-events: none;
}
/* Whatever holds the dropdown becomes its anchor.
   .emp-results is absolutely positioned, so without this it measures itself
   against the nearest positioned ancestor — and on the Increment page there was
   none, so the results laid out 2015px wide and landed off the right of the
   screen. The search worked perfectly; it just drew itself somewhere nobody
   could see, which reads exactly like a broken search.
   Written with :has() so any container that holds a result list anchors it,
   rather than each page having to remember to. */
:has(> .emp-results) { position: relative; }
.emp-results {
  position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 4px);
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: 0 12px 28px -18px rgba(19,26,28,.4);
  max-height: 17rem; overflow-y: auto;
}
.emp-result,
.emp-result-row {
  display: flex; align-items: baseline; gap: 0.7rem;
  padding: 0.55rem 0.75rem; cursor: pointer; border-bottom: 1px solid var(--line);
  width: 100%; background: none; border-left: 0; border-right: 0; border-top: 0;
  font: inherit; text-align: left; color: inherit;
}
.emp-result:last-child, .emp-result-row:last-child { border-bottom: 0; }
.emp-result:hover, .emp-result:focus-visible,
.emp-result-row:hover, .emp-result-row:focus-visible { background: var(--teal-pale); }
.emp-result strong, .emp-result-row strong { color: var(--ink); }
.emp-result .code, .emp-result-row .code { font-family: var(--mono); font-size: 0.78rem; color: var(--ink-3); }
.emp-result .meta, .emp-result-row .meta { margin-left: auto; font-size: 0.8rem; color: var(--ink-3); text-align: right; }
.emp-results-empty { padding: 0.7rem 0.75rem; color: var(--ink-3); font-size: 0.88rem; }

/* The loaded record. Deliberately shows salary and joining date: an advance is
   a credit decision and those are the two things somebody assessing it asks
   for. */
.emp-loaded {
  display: grid; gap: 0.75rem 1.4rem;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0.9rem 1rem; margin-top: 0.8rem;
}
.emp-loaded dt {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--ink-3); font-weight: 600;
}
.emp-loaded dd { margin: 0.15rem 0 0; font-weight: 600; color: var(--ink); }
.emp-loaded dd.warn { color: var(--warn); }

/* ====================================================================== *
   Fixes found by opening the pages.
 * ====================================================================== */

/* Buttons were breaking onto two lines — "Add / Employee", "Add / New",
   "Export / CSV". A button is a single target and a two-line one reads as a
   layout fault. It stops shrinking instead. */
.btn, .btn-primary, .btn-secondary, .btn-success, .btn-danger, .btn-warning,
.btn-light, .btn-dark, .btn-outline-primary, .btn-outline-secondary,
.btn-action, .btn-add, .btn-cancel, .emp-load-btn {
  white-space: nowrap;
  flex: 0 0 auto;
}

/* A toolbar sits on one line and wraps as whole controls, not mid-word. */
/* .toolbar's display:flex came from app-legacy.css, which the themed pages do
   not load — so on those pages a toolbar's controls stacked vertically. Same
   mistake as .panel: a layout primitive parked in the transitional shim. */
.toolbar {
  display: flex; align-items: flex-start; gap: 0.6rem; flex-wrap: wrap;
  margin-bottom: 0.9rem;
}
.toolbar, .btn-row, .page-head__actions { align-items: center; }
.toolbar > *, .btn-row > * { flex: 0 0 auto; }
/* …except the search field, which takes the slack. */
.toolbar input[type=text], .toolbar input[type=search] { flex: 1 1 14rem; min-width: 0; }
/* A select in a toolbar sizes to its longest option plus room for the arrow.
   Without this it inherits width:100% from the form rules, which in a flex row
   with flex:0 0 auto collapses it to something narrower than its own text —
   Master Data's "Sort A–Z" had the chevron sitting on top of the Z. */
.toolbar select, .page-head__actions select, .btn-row select {
  width: auto; min-width: 9rem; padding-right: 2rem;
}

/* Running prose stays readable. A max-width on the paragraph alone was being
   ignored where a flex parent had already stretched its child. */
.page-head > div:first-child { min-width: 0; max-width: 72ch; }

/* An empty result area with nothing in it reads as a page that failed to load
   rather than one waiting for input. */
#results:empty::after {
  content: "Search by name or employee code to open a record.";
  display: block; padding: 3rem 1rem; text-align: center;
  color: var(--ink-3); font-size: 0.9rem;
}

/* ====================================================================== *
   Utilities the retheme took away.

   Each page used to define its own handful of one-line utilities at the
   bottom of its own stylesheet, and deleting those stylesheets took them
   with it. `.hidden` is the one that mattered: the status matrix and the
   payslip page hide their modals with `class="hidden"`, so losing it left
   two dialogs rendered open and unstyled on top of the page.

   Found by opening the pages, which is the only way this kind of thing is
   ever found. It is exactly the failure a class census misses — `hidden` was
   on my list of framework names to skip.
 * ====================================================================== */
.hidden { display: none !important; }

/* The bespoke modal these two pages use — not Bootstrap's. Styled to match
   .modal-content so a page can carry either while it waits to be converted. */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 1050;
  background: rgba(19,26,28,.55);
  display: grid; place-items: center; padding: 1.5rem;
  opacity: 1;                        /* Bootstrap sets .modal-backdrop to 0.5 */
}
.modal-card {
  background: var(--card); border-radius: 14px; border: 1px solid var(--line);
  width: min(560px, 100%); max-height: 90vh; overflow-y: auto;
  box-shadow: 0 24px 60px -30px rgba(19,26,28,.5);
}
.modal-head {
  padding: 1rem 1.2rem; border-bottom: 1px solid var(--line);
  font-weight: 700; font-size: 1.05rem;
}
.modal-foot {
  padding: 0.9rem 1.2rem; border-top: 1px solid var(--line);
  display: flex; gap: 0.5rem; justify-content: flex-end; flex-wrap: wrap;
}

/* ====================================================================== *
   Bare <button> elements.

   app.css styles `.btn`. The pages' own stylesheets styled the ELEMENT —
   `button { ... }` — so deleting them left every unclassed button rendering
   as a raw grey browser control. That is 57 buttons across eleven pages,
   including "Approve & Update Employee" on the increment letter.

   `:not([class])` is what makes this safe: a button that already carries a
   class keeps whatever that class says, so nothing here can fight .btn or
   the Bootstrap variants above.
 * ====================================================================== */
button:not([class]),
input[type="submit"]:not([class]),
input[type="button"]:not([class]) {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.5rem 0.95rem; border-radius: 8px;
  border: 1px solid var(--line); background: #fff; color: var(--ink-2);
  font: inherit; font-size: 0.88rem; font-weight: 600;
  cursor: pointer; white-space: nowrap;
}
button:not([class]):hover,
input[type="submit"]:not([class]):hover { background: #F1F3F3; color: var(--ink); }

button:not([class]):disabled { opacity: 0.5; cursor: not-allowed; }

/* A bare button that submits or confirms still reads as the primary action,
   which the pages used to express through their own colours. Named by intent
   so the markup can say which is which. */
button:not([class])[data-primary],
button.primary {
  background: var(--teal); border-color: var(--teal); color: #fff;
}
button:not([class])[data-primary]:hover, button.primary:hover {
  background: var(--teal-dark); border-color: var(--teal-dark); color: #fff;
}

/* Bare form controls, for the same reason. */
input:not([class]):not([type="checkbox"]):not([type="radio"]),
select:not([class]),
textarea:not([class]) {
  width: 100%; padding: 0.55rem 0.65rem; font: inherit; font-size: 0.92rem;
  border: 1px solid var(--line); border-radius: 8px;
  background: #fff; color: var(--ink);
}

/* ====================================================================== *
   Bespoke button classes the pages invented.

   Every page named its buttons differently — .primary-btn, .green-btn,
   .rep-btn, .load-btn, .pdf-btn — and defined each in its own stylesheet.
   Deleting those left "Approve & Update Employee" rendering as a raw browser
   control on the increment letter.

   THE MAPPING USED TO BE HERE AND ALSO 250 LINES BELOW.

   Two blocks, overlapping selectors, different opinions: this one made
   .green-btn a filled green and the other made it an unfilled teal; this one
   put .pdf-btn in quiet and so did that one, in slightly different greys. The
   lower block won on every shared name because it comes later, which made
   everything here either dead or — worse — half-live, applying to the names
   the other block happened not to list.

   Two copies of a mapping is the same fault as two copies of a palette, and
   it is the one that produced "the look and feel is all over the page": you
   cannot fix a button by reading one of them.

   So there is now ONE block, under "BESPOKE BUTTONS, MAPPED" further down.
   Everything button-shaped goes there. What stays here is the handful of
   things in this neighbourhood that are not buttons.
 * ====================================================================== */

/* A close glyph on the bespoke popups. */
.popup-close, .calc-card-close {
  background: none; border: 0; cursor: pointer; line-height: 1;
  font-size: 1.35rem; color: var(--ink-3); padding: 0 0.3rem;
}
.popup-close:hover, .calc-card-close:hover { color: var(--ink); }

/* A small labelled token — used as a filter and as a tag. */
.chip {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.2rem 0.6rem; border-radius: 100px;
  background: var(--teal-pale); color: var(--teal-dark);
  font-size: 0.78rem; font-weight: 600; border: 1px solid transparent;
  cursor: pointer; text-decoration: none;
}
.chip:hover { border-color: var(--teal); }
.chip.active, .chip[aria-pressed="true"] { background: var(--teal); color: #fff; }

.slot-code-input {
  padding: 0.35rem 0.5rem; border: 1px solid var(--line); border-radius: 6px;
  font: inherit; font-size: 0.85rem; width: 100%;
}
.hiring-link { color: var(--teal-dark); font-weight: 600; }

/* The bespoke popup shell these pages use, alongside .modal-card above.
   The head/body/foot parts were styled but the overlay and the panel itself
   never were, so a dialog appended to <body> rendered as a block at the very
   bottom of the document — present, focusable, and completely invisible unless
   you scrolled past the footer to find it. */
.popup-overlay {
  position: fixed; inset: 0; z-index: 1080;
  background: rgba(19, 26, 28, 0.45);
  display: flex; align-items: center; justify-content: center;
  padding: 1.5rem; overflow-y: auto;
}
.popup {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); width: 100%; max-width: 30rem;
  box-shadow: 0 18px 48px rgba(19, 26, 28, 0.22);
  max-height: 100%; display: flex; flex-direction: column;
}
.popup .popup-body { overflow-y: auto; }
/* A wider dialog, for one that holds a table rather than a form.
   This lived in public/admin/claims/index.html, so `.popup--wide` worked on
   that page and silently did nothing everywhere else — the same class name
   meaning two different things depending which page you were on. A dialog
   that holds a table and is 30rem wide is not obviously broken; it just
   scrolls sideways for no reason anybody can see. */
.popup--wide .popup { max-width: 46rem; }
.popup-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.7rem; padding: 1rem 1.2rem; border-bottom: 1px solid var(--line);
}
.popup-head h3 { font-size: 1.05rem; margin: 0; }
.popup-body { padding: 1.1rem 1.2rem; }
.popup-foot {
  padding: 0.9rem 1.2rem; border-top: 1px solid var(--line);
  display: flex; gap: 0.5rem; justify-content: flex-end;
}

/* =========================================================================
   THE EMPLOYEE RECORD
   One person, opened from search or straight from a Data Health link. HR works
   this screen next to the person whose record it is, so it reads top to bottom:
   who they are, what we hold about them, what has been changed, what was
   reviewed.

   These classes had styling inside the page until the page's own CSS block was
   removed in the Bootstrap migration, and nothing replaced it — the label/value
   pairs collapsed into one unstyled column and the record lost its surface.
   They live here rather than in app-legacy.css because this screen replaces
   ops' Team page and travels with it.
   ========================================================================= */
.employee-card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.3rem 1.4rem; margin-bottom: 1rem;
}
.employee-card > div:first-child { gap: 1rem; flex-wrap: wrap; }
.employee-card h2 {
  font-size: 1.35rem; font-weight: 700; letter-spacing: -0.02em;
  color: var(--ink); margin: 0; display: flex; align-items: center;
  gap: 0.5rem; flex-wrap: wrap;
}

/* A definition list, not a paragraph. auto-fit means the same markup gives
   four columns on a desktop and one on a phone with no breakpoint to maintain. */
.employee-grid {
  display: grid; gap: 1rem 1.6rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  padding-top: 1.2rem; margin-top: 0.2rem; border-top: 1px solid var(--line);
}
.employee-grid .label {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 0.2rem;
}
.employee-grid .value {
  font-size: 0.97rem; font-weight: 500; color: var(--ink);
  font-variant-numeric: tabular-nums; word-break: break-word;
}
/* A field we hold nothing for says so. Before this, an empty value rendered as
   nothing at all and left its label hanging over the next field's answer —
   which is how you read a blank cuisine as a blank salary. */
.employee-grid .value:empty::after,
.value--empty {
  content: "Not recorded"; color: var(--ink-3);
  font-weight: 400; font-style: italic; font-size: 0.9rem;
}
.value--empty { font-style: italic; }

/* --------------------------------------------------------- the action row -- */
/* Five buttons in five colours was five things all shouting. Hierarchy instead:
   the routine action is filled, everything else is quiet until you reach for
   it, and only the two that end someone's record carry a warning colour. */
.emp-actions { display: flex; gap: 0.45rem; flex-wrap: wrap; }
/* ONE BUTTON SIZE.
 *
 * .emp-load-btn was 0.55rem/0.85rem padding at 0.85rem type while .btn was
 * 0.5rem/0.95rem at 0.88rem. Two pixels of height and half a point of type —
 * invisible alone, obvious the moment the two sit in one toolbar, which they
 * do on Payroll Runs, Off Payroll, Reconcile, Expense Claims, Register
 * Clean-up and API Access. A row of buttons at two heights reads as a layout
 * fault rather than a design.
 *
 * The families now share one geometry and differ only in colour:
 *   .btn           teal — the action being encouraged
 *   .btn--quiet    white, grey border — everything else
 *   .btn--danger   white, red border — a red slab invites the click it should
 *                  be discouraging
 *   .emp-load-btn  the same as .btn--quiet, under its older name
 *
 * New markup uses .btn and its modifiers. The rest are aliases, kept so that
 * thirty pages did not have to be rewritten at once. */
.emp-load-btn {
  font: inherit; font-size: 0.88rem; font-weight: 600; line-height: 1;
  padding: 0.5rem 0.95rem; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--ink-2);
  display: inline-flex; align-items: center; gap: 0.4rem; white-space: nowrap;
  text-decoration: none;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.emp-load-btn:hover { background: var(--bg); border-color: var(--ink-3); color: var(--ink); }
.emp-load-btn:focus-visible {
  outline: 2px solid var(--teal); outline-offset: 2px;
}
.emp-load-btn--primary {
  background: var(--teal); border-color: var(--teal); color: #fff;
}
.emp-load-btn--primary:hover {
  background: var(--teal-dark); border-color: var(--teal-dark); color: #fff;
}
/* Reaching for these is deliberate: quiet until hover, then unmistakable. */
.emp-load-btn--warn { color: var(--warn); border-color: #E8DCC3; }
.emp-load-btn--warn:hover { background: #FDF6E7; border-color: var(--warn); color: #8A5D12; }
.emp-load-btn--danger { color: var(--err); border-color: #EFD3D0; }
.emp-load-btn--danger:hover { background: #FCEFEE; border-color: var(--err); color: #8F281F; }

/* ------------------------------------------------------------- the status -- */
.status-pill {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 0.24rem 0.55rem; border-radius: 999px;
  background: var(--teal-pale); color: var(--teal-dark);
  border: 1px solid #CDE9EF; vertical-align: middle;
}
.status-date { font-weight: 500; letter-spacing: 0; text-transform: none; opacity: 0.85; }

/* ----------------------------------------------------- history and reviews -- */
.history-box {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.1rem 1.2rem; margin-bottom: 1rem;
}
.history-title {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3); margin: 0 0 0.8rem;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.history-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.history-table th {
  text-align: left; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
  padding: 0 0.7rem 0.5rem; border-bottom: 1px solid var(--line);
}
.history-table td {
  padding: 0.6rem 0.7rem; border-bottom: 1px solid var(--line);
  color: var(--ink-2); vertical-align: top;
}
.history-table tr:last-child td { border-bottom: 0; }
.history-when { white-space: nowrap; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.history-by { white-space: nowrap; color: var(--ink-3); }
.history-changes { color: var(--ink); }
.history-changes strong { font-weight: 600; }
.history-empty, .notes-text, .muted { color: var(--ink-3); font-size: 0.9rem; }
.history-empty { padding: 0.3rem 0; }

/* ------------------------------------------------------------ search rows -- */
.emp-result-wrap { overflow-x: auto; }
.emp-result-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.emp-result-table th {
  text-align: left; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
  padding: 0 0.7rem 0.5rem; border-bottom: 1px solid var(--line);
}
.emp-result-table td {
  padding: 0.65rem 0.7rem; border-bottom: 1px solid var(--line); color: var(--ink-2);
}
.emp-name { font-weight: 600; color: var(--ink); }

/* THE FACE, IN A SEARCH RESULT.

   A round crop because the stored photograph is an ID-card rectangle: a
   circle takes the middle of it, which is the face, and drops the shoulders
   and the wall behind them — the parts that differ most between one phone
   picture and the next. A row of circles reads as a set; a row of rectangles
   reads as seven differently-lit photographs.

   The INITIALS stand in where there is none, rather than a silhouette. The
   register has a photograph for nobody today, so the silhouette would be the
   whole column — and two initials at least tell one row from another while
   that is true. */
.emp-face {
  width: 34px; height: 34px; border-radius: 50%;
  object-fit: cover; display: block;
  background: var(--paper-2, #f1f1ee);
  border: 1px solid var(--line);
}
.emp-face--none {
  display: flex; align-items: center; justify-content: center;
  font-size: 0.72rem; font-weight: 700; letter-spacing: .02em;
  color: var(--ink-3); text-transform: uppercase;
}
.emp-result-table td.emp-face-cell { width: 46px; padding-right: 0; }

/* A leaver in a live search. Quieter, not hidden — the row is there because
   somebody asked for it, and a difference somebody has to hunt for is one
   they will miss on the row that matters. */
.emp-row--ex { opacity: 0.72; }
.emp-row--ex .emp-name { font-weight: 500; }
.emp-ex-tag {
  font-size: 0.66rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--err, #b4322a);
  margin-top: 0.1rem;
}
.emp-id { font-variant-numeric: tabular-nums; color: var(--ink-3); }
.emp-loc { color: var(--ink-3); }

.empty-state {
  text-align: center; padding: 2.6rem 1rem; color: var(--ink-3);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
}
.empty-state i { font-size: 1.9rem; display: block; margin-bottom: 0.6rem; opacity: 0.5; }


/* =========================================================================
   BESPOKE BUTTONS, MAPPED
   Every page that predates the theme brought its own button class and, in
   several cases, an inline background colour. The result was one console with
   four greens in it. These map onto the same two shapes everything else uses:
   filled for the action a page exists to perform, quiet for the rest.
   Inline `style="background:#..."` beats a class, so those are removed from the
   markup rather than fought here.
   ========================================================================= */
/* ONE GEOMETRY.
   The shape below is app.css's .btn, to the character: 0.5rem 0.95rem,
   0.88rem, 8px, 600. It used to be 0.6rem/1rem here, which meant a page
   carrying an .add-btn beside a .btn had two buttons of visibly different
   height in the same toolbar — the same fault .emp-load-btn had. There is one
   button geometry in this product and this is it.

   .btn itself is deliberately NOT in this list. It is already this shape in
   app.css, and naming it here would put a teal fill after .btn--quiet and
   .btn--ghost in the cascade — repainting every quiet button in the console
   solid. */
.primary-btn, .btn-save, .add-btn, .btn-add, .load-btn, .rep-btn,
.btn-primary, .btn-primary-modal, .btn-act, .btn-pine, .btn-success,
.emp-search-btn, .action-btn, .btn-mini,
.btn-cancel, .preview-btn, .pdf-btn, .btn-secondary, .btn-quiet,
.btn-light, .btn-outline-secondary, .btn-help,
.btn-warning, .btn-info, .btn-dark,
.btn-outline, .btn-outline-primary, .btn-line, .btn-ghost, .green-btn,
.btn-danger, .btn-danger-modal, .red-btn, .btn-remove,
.btn-outline-danger, .slot-remove-btn, .danger {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.4rem;
  font: inherit; font-size: 0.88rem; font-weight: 600; line-height: 1.2;
  padding: 0.5rem 0.95rem; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--teal); background: var(--teal); color: #fff;
  text-decoration: none; white-space: nowrap;
}

/* FILLED — the one thing a page exists to do. */
.primary-btn:hover, .btn-save:hover, .add-btn:hover, .btn-add:hover,
.load-btn:hover, .rep-btn:hover, .btn-primary:hover,
.btn-primary-modal:hover, .btn-act:hover, .btn-pine:hover,
.btn-success:hover, .emp-search-btn:hover,
.action-btn:hover, .btn-mini:hover {
  background: var(--teal-dark); border-color: var(--teal-dark);
}

/* GHOST — the accent, unfilled. A second action of the same kind. */
.btn-outline, .btn-outline-primary, .btn-line, .btn-ghost, .green-btn {
  background: var(--card); color: var(--teal-dark); border-color: var(--teal);
}
.btn-outline:hover, .btn-outline-primary:hover, .btn-line:hover,
.btn-ghost:hover, .green-btn:hover {
  background: var(--teal-pale);
}

/* QUIET — everything else. Cancel, reset, download, open, go back. */
.btn-cancel, .preview-btn, .pdf-btn, .btn-secondary, .btn-quiet,
.btn-light, .btn-outline-secondary, .btn-help,
.btn-warning, .btn-info, .btn-dark {
  border-color: var(--line); background: var(--card); color: var(--ink-2);
}
.btn-cancel:hover, .preview-btn:hover, .pdf-btn:hover,
.btn-secondary:hover, .btn-quiet:hover, .btn-light:hover,
.btn-outline-secondary:hover, .btn-help:hover,
.btn-warning:hover, .btn-info:hover, .btn-dark:hover {
  background: var(--bg); border-color: var(--ink-3); color: var(--ink);
}

/* DANGER — never filled. A red rectangle is easy to press by accident, and
   every one of these removes something. */
.btn-danger, .btn-danger-modal, .red-btn, .btn-remove,
.btn-outline-danger, .slot-remove-btn, .danger {
  border-color: #EFD3D0; color: var(--err); background: var(--card);
}
.btn-danger:hover, .btn-danger-modal:hover, .red-btn:hover,
.btn-remove:hover, .btn-outline-danger:hover, .slot-remove-btn:hover,
.danger:hover {
  background: #FCEFEE; border-color: var(--err);
}

/* SIZE. Bootstrap's .btn-sm is loaded before this file, so the geometry above
   was silently making every "small" button full-size — a 9-page problem that
   only shows as a toolbar sitting a few pixels taller than it should. These
   come last so a size modifier still modifies. */
.btn-sm, .btn--sm, .btn-mini {
  padding: 0.28rem 0.6rem; font-size: 0.78rem;
}
.btn-lg {
  padding: 0.65rem 1.2rem; font-size: 0.95rem;
}

/* Disabled says so once, for all of them. Three pages had their own opacity
   and one of them kept the hover lift while disabled. */
.btn:disabled, .btn[disabled],
.primary-btn:disabled, .btn-save:disabled, .add-btn:disabled,
.btn-add:disabled, .btn-primary:disabled, .btn-act:disabled,
.btn-outline:disabled, .btn-ghost:disabled, .btn-line:disabled,
.btn-cancel:disabled, .pdf-btn:disabled, .btn-secondary:disabled {
  opacity: 0.45; cursor: not-allowed; transform: none;
}

.btn:focus-visible,
.primary-btn:focus-visible, .btn-cancel:focus-visible,
.btn-save:focus-visible, .add-btn:focus-visible, .btn-add:focus-visible,
.load-btn:focus-visible, .preview-btn:focus-visible, .pdf-btn:focus-visible,
.btn-remove:focus-visible, .btn-primary:focus-visible,
.btn-outline:focus-visible, .btn-ghost:focus-visible,
.btn-line:focus-visible, .btn-act:focus-visible, .btn-pine:focus-visible,
.btn-secondary:focus-visible, .btn-quiet:focus-visible,
.btn-danger:focus-visible, .red-btn:focus-visible {
  outline: 2px solid var(--teal); outline-offset: 2px;
}

/* =========================================================================
   TIMESHEETS
   A register of who was seen and when. Deliberately NOT a report of hours
   worked: this terminal reports every punch as the same event type, so there
   is no in/out flag, and a bar drawn from first to last punch is a span, not a
   shift. The design has to make that distinction impossible to misread —
   which is why a single punch is a MARK rather than a zero-width bar, and why
   the span column says "—" rather than "0h 00m".

   One scale for the whole page (06:00–24:00), so two rows can be compared.
   One hue for the bars; unmatched rows carry a status colour, not a second
   series colour, because "we don't know who this is" is a state and not a
   category.
   ========================================================================= */
.ts-note {
  display: flex; gap: 0.7rem; align-items: flex-start;
  background: var(--teal-pale); border: 1px solid #CDE9EF;
  border-radius: var(--radius); padding: 0.85rem 1rem; margin-bottom: 1rem;
  color: var(--ink-2); font-size: 0.88rem;
}
.ts-note i { color: var(--teal-dark); font-size: 1rem; margin-top: 0.1rem; flex: 0 0 auto; }
.ts-note p { margin: 0 0 0.35rem; max-width: 88ch; }
.ts-note p:last-child { margin-bottom: 0; }

/* -------------------------------------------------- FOLDED PAGE NOTES --
   These were three and four paragraphs of reasoning at the top of every
   page. Every word is worth having ONCE — why a missing punch is not
   evidence of absence, why the weekly summary arrives even when it is empty
   — and none of it is worth reading on the four hundredth visit. Standing
   between somebody and the table they came for, it stops being an
   explanation and becomes furniture.

   So shell.js folds every .ts-note behind one small button on every page,
   and remembers per page whether it was left open. The text is unchanged and
   one click away; it is the default that changed.

   Written as a modifier the script adds rather than as the base style, so a
   page with JavaScript off still shows its note in full — losing the
   explanation entirely is worse than showing it always.            */
.ts-note--fold {
  display: block; padding: 0; background: none; border: 0;
  margin-bottom: 0.9rem;
}
/* THE i BUTTON.
   It was a 0.78rem pill in pale teal on a pale teal ground — legible if you
   already knew it was there, and reported as hard to read. The fix is
   contrast and size rather than colour: the label at body size, the ink at
   full strength, a solid ground, and a target tall enough to be obviously a
   button. It is the way into everything a page has to explain, so it cannot
   be the faintest thing on the screen. */
.ts-note__toggle {
  display: inline-flex; align-items: center; gap: 0.5rem;
  background: #fff; border: 1px solid #9FD4E0;
  border-radius: 100px; padding: 0.42rem 0.95rem 0.42rem 0.7rem;
  font: inherit; font-size: 0.9rem; font-weight: 600;
  color: var(--teal-dark); cursor: pointer;
}
.ts-note__toggle:hover {
  border-color: var(--teal); background: var(--teal-pale);
}
.ts-note__toggle:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.ts-note__toggle i { font-size: 1.05rem; margin: 0; color: var(--teal); }
.ts-note__toggle .ts-note__chev { font-size: 0.7rem; opacity: 0.9; color: inherit; }

.ts-note__body {
  display: flex; gap: 0.7rem; align-items: flex-start;
  background: var(--teal-pale); border: 1px solid #CDE9EF;
  border-radius: var(--radius); padding: 0.85rem 1rem;
  margin-top: 0.5rem; color: var(--ink-2); font-size: 0.88rem;
}
.ts-note__body > i { color: var(--teal-dark); font-size: 1rem; margin-top: 0.1rem; flex: 0 0 auto; }

/* Printed, it is documentation rather than furniture — so it prints open.
   Pages that carry `no-print` on the note keep that; this only affects the
   fold. */
@media print {
  .ts-note__toggle { display: none !important; }
  .ts-note__body { display: flex !important; }
}

.ts-stats { display: flex; flex-wrap: wrap; gap: 1.6rem 2.4rem; }
.ts-stat { display: flex; flex-direction: column; gap: 0.1rem; }
.ts-stat strong {
  font-size: 1.7rem; font-weight: 700; letter-spacing: -0.02em;
  line-height: 1.1; color: var(--ink); font-variant-numeric: tabular-nums;
}
.ts-stat span {
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
}

.panel--attention { border-left: 3px solid var(--warn); }
/* .ts-head is THE section header, on ten pages: Timesheets, Reconcile, API
   Access, Register Clean-up, Payroll Runs, Off Payroll, Expense Claims,
   Devices, Employees and Blockers. The name is wrong — "ts" is timesheets,
   where it was written — and renaming it means touching ten files, so it is
   noted here rather than done badly. A page with a heading and a line of
   explanation above a panel wants this. */
.ts-head h2 {
  font-size: 1.02rem; font-weight: 700; letter-spacing: -0.01em;
  color: var(--ink); margin: 0 0 0.3rem;
  display: flex; align-items: center; gap: 0.45rem;
}
/* A section-heading icon takes the heading's colour, not a status colour.
   This was amber, which on Timesheets read as "careful, these are spans not
   shifts" — a real point, made once, in prose. Register Clean-up borrows
   .ts-head for its three numbered steps, so it inherited three amber icons on
   a page where nothing is being warned about, and amber stopped meaning
   anything on either page. */
.ts-head h2 i { color: var(--ink-3); }
.ts-head p { margin: 0 0 1rem; max-width: 80ch; font-size: 0.88rem; }

.ts-toggle {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.85rem; color: var(--ink-2); cursor: pointer;
  margin-left: auto; white-space: nowrap;
}

/* ------------------------------------------------------------------ a day -- */
.ts-day { margin-bottom: 1.8rem; }
.ts-day:last-child { margin-bottom: 0; }
.ts-day__head {
  display: flex; align-items: baseline; gap: 0.7rem;
  padding-bottom: 0.45rem; border-bottom: 1px solid var(--line);
  margin-bottom: 0.2rem;
}
.ts-day__head h3 {
  font-size: 0.95rem; font-weight: 700; color: var(--ink); margin: 0;
}

/* The scale, drawn once per day and shared by every bar under it. Recessive:
   it orients the eye and then gets out of the way. */
.ts-scale {
  position: relative; height: 1.15rem;
  margin: 0.35rem 0 0.2rem;
  margin-left: 13rem; margin-right: 11.5rem;
}
.ts-scale span {
  position: absolute; transform: translateX(-50%);
  font-size: 0.66rem; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.ts-row {
  display: grid;
  grid-template-columns: 13rem 1fr 6rem 5.5rem;
  align-items: center; gap: 0 0;
  padding: 0.3rem 0; border-bottom: 1px solid var(--line);
}
.ts-row:last-child { border-bottom: 0; }
.ts-who { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; padding-right: 0.8rem; }
.ts-who strong {
  font-size: 0.9rem; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ts-who .emp-id { font-size: 0.74rem; }
.ts-flag {
  font-size: 0.66rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--warn);
}

/* "Off payroll" is a FACT about somebody, not a problem with their row. The
   amber of .ts-flag says "somebody deal with this", and a guard who punched
   in and out correctly needs nobody to deal with anything — so the same shape
   in a neutral ink, which reads as a label rather than as a warning. */
.ts-flag--off { color: var(--ink-3); }

/* A COUNT that needs attention, at the size of the counts beside it.
   .ts-flag above is the style of a WORD — "MISSED PUNCH", uppercase and
   small. Off Payroll was using it for a number in a numeric column, so
   Nirmal Chander's 2 days with one punch rendered two thirds the size of the
   9 in the column to its left: the figure that mattered was the smallest
   thing in the row, and the colour read as decoration rather than as a
   signal. Same colour, same size as its neighbours, and it is a figure
   again. */
.ts-count-warn {
  color: var(--warn); font-weight: 700;
  border-bottom: 1px dotted currentColor; cursor: help;
}

/* Off Payroll's list. A name is the column a person reads DOWN, so it does
   not wrap: "Ramvir Singh Chaudhary" on three lines made every row in the
   table three lines tall, and the table already scrolls sideways inside
   .table-scroll if it needs to. */
.emp-result-table .emp-name { white-space: nowrap; }
/* The line under a name — credential, branch, agency. It wraps rather than
   widening the column, because a nine-column table has no spare width and
   this is the least important thing in the row. */
.emp-result-table .meta { max-width: 13rem; }

/* A second line in a table heading. Used where a label genuinely needs two
   words and the column is narrow; the better answer is usually one word and a
   title attribute, which is what Off Payroll ended up with. */
.th-sub {
  display: block; font-weight: 600; opacity: 0.72;
}

/* The track. A faint ground so an empty stretch still reads as time. */
.ts-track {
  position: relative; height: 1.35rem;
  background: linear-gradient(var(--bg), var(--bg)) no-repeat center/100% 0.5rem;
  border-radius: 4px;
}
.ts-bar {
  position: absolute; top: 50%; transform: translateY(-50%);
  height: 0.5rem; min-width: 3px; border-radius: 4px;
  background: var(--teal);
}
/* One punch is a moment, not a span. Drawn as a mark so it can never be read
   as a shift of zero length. */
.ts-mark {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  width: 0.55rem; height: 0.55rem; border-radius: 50%;
  background: var(--card); border: 2px solid var(--teal);
}
/* Every individual punch, over the bar — so a day with four punches looks
   different from a day with two spanning the same hours. */
.ts-tick {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  width: 2px; height: 0.85rem; border-radius: 1px;
  background: var(--ink); opacity: 0.55;
}

.ts-times, .ts-span {
  font-size: 0.82rem; color: var(--ink-2);
  font-variant-numeric: tabular-nums; text-align: right;
  padding-left: 0.7rem; white-space: nowrap;
}
.ts-span { font-weight: 600; color: var(--ink); }

.ts-notes {
  grid-column: 2 / -1; display: flex; flex-direction: column; gap: 0.1rem;
  padding: 0.1rem 0 0.3rem;
}
.ts-notes span { font-size: 0.78rem; color: var(--ink-3); }

/* A row we cannot attribute. The warning colour is on the identity, not the
   bar — the times are just as true, it is the person that is unknown. */
.ts-row--unmatched .ts-bar { background: var(--warn); }
.ts-row--unmatched .ts-mark { border-color: var(--warn); }
.ts-row--before { opacity: 0.55; }

@media (max-width: 900px) {
  .ts-row { grid-template-columns: 10rem 1fr; }
  .ts-times, .ts-span { grid-column: 2; text-align: left; padding-left: 0; }
  .ts-scale { margin-left: 10rem; margin-right: 0; }
  .ts-notes { grid-column: 1 / -1; }
}

/* =========================================================================
   HELP, COLLAPSED
   The Devices page carries the whole runbook — new machine, machine coming
   back, admin PIN, RFID, what to do when it goes quiet. That is a lot of prose
   to sit above the list of machines you actually came to look at, so it lives
   in <details>: open the one you need, and the page stays a device list the
   rest of the time.

   Native <details> deliberately, not a JS accordion. It survives Ctrl-F (the
   browser opens a closed section to reveal a match), it prints expanded, and
   it needs no script to work — which matters for a page somebody reads while
   standing in front of a machine that is not working.
   ========================================================================= */
.help {
  border-top: 1px solid var(--line);
}
.help:first-of-type { border-top: 0; }
.help > summary {
  cursor: pointer; padding: 0.75rem 0.2rem;
  font-size: 0.92rem; color: var(--ink-2);
  display: flex; align-items: center; gap: 0.5rem;
  list-style: none;
}
.help > summary::-webkit-details-marker { display: none; }
.help > summary::before {
  content: ""; flex: 0 0 auto;
  width: 0.42rem; height: 0.42rem;
  border-right: 2px solid var(--ink-3); border-bottom: 2px solid var(--ink-3);
  transform: rotate(-45deg); transition: transform 0.15s;
}
.help[open] > summary::before { transform: rotate(45deg); }
.help > summary:hover { color: var(--ink); }
.help > summary strong { font-weight: 600; color: var(--ink); }
.help > summary:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

.help__body {
  padding: 0.1rem 0 1.1rem 1rem;
  border-left: 2px solid var(--line); margin-left: 0.15rem;
}
.help__body p {
  font-size: 0.88rem; color: var(--ink-2); margin: 0 0 0.7rem; max-width: 78ch;
}
.help__body p:last-child { margin-bottom: 0; }
.help__body strong { color: var(--ink); font-weight: 600; }
.help__body table { font-size: 0.86rem; }

/* A command to type. Selectable, scrollable, and visibly not prose. */
.help__code {
  font-family: var(--mono); font-size: 0.8rem; line-height: 1.6;
  background: var(--bg); border: 1px solid var(--line); border-radius: 6px;
  padding: 0.6rem 0.75rem; margin: 0 0 0.8rem;
  overflow-x: auto; white-space: pre; color: var(--ink);
}

/* The things that cost data if ignored. Amber, because they are warnings about
   consequences rather than errors that have happened. */
.help__warn {
  display: flex; gap: 0.5rem; align-items: flex-start;
  background: #FDF6E7; border: 1px solid #E8DCC3; border-radius: 6px;
  padding: 0.6rem 0.75rem; margin: 0.9rem 0 0 !important;
  font-size: 0.85rem !important; color: #6B4A0F !important;
}
.help__warn i { color: var(--warn); margin-top: 0.1rem; flex: 0 0 auto; }

/* ------------------------------------------------------------ device admins -- */
/* Who can open a machine's menu. The most important thing on a device card:
   an admin can add and remove users at that branch, and a machine with none
   has an open menu. */
.admin-list { margin: 0 0 1rem; }
.admin-list h3 {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3); margin: 0 0 0.2rem;
}
.admin-list > p { margin: 0 0 0.6rem; font-size: 0.85rem; }
.admin-list--none {
  display: flex; gap: 0.6rem; align-items: flex-start;
  background: #FDF6E7; border: 1px solid #E8DCC3; border-radius: var(--radius);
  padding: 0.75rem 0.9rem; font-size: 0.88rem; color: #6B4A0F;
}
.admin-list--none i { color: var(--warn); margin-top: 0.1rem; flex: 0 0 auto; }
.admin-list--none strong { color: #4A320A; }

/* ------------------------------------------------- your own machine access -- */
/* The one panel on the dashboard that is about the person looking at it. Their
   PIN is public (it is their employee ID); the password is not, so it is set
   apart rather than sitting in a row of facts. */
.da-title {
  font-size: 0.95rem; font-weight: 700; color: var(--ink);
  margin: 0 0 0.3rem; display: flex; align-items: center; gap: 0.45rem;
}
.da-title i { color: var(--teal-dark); }
.da-card {
  border: 1px solid var(--line); border-radius: 8px;
  padding: 0.8rem 0.95rem; margin-bottom: 0.7rem; background: var(--bg);
}
.da-card:last-child { margin-bottom: 0; }
.da-card h4 {
  font-size: 0.86rem; font-weight: 600; color: var(--ink); margin: 0 0 0.6rem;
}
.da-rows { display: flex; flex-wrap: wrap; gap: 0.6rem 2.4rem; }
.da-rows > div { display: flex; flex-direction: column; gap: 0.05rem; }
.da-rows span {
  font-size: 0.66rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
}
.da-rows strong {
  font-size: 1.15rem; font-weight: 700; color: var(--ink); line-height: 1.2;
}
.da-rows em { font-size: 0.76rem; color: var(--ink-3); font-style: normal; }
.da-secret {
  font-family: var(--mono); letter-spacing: 0.06em;
  background: #FDF6E7; border: 1px solid #E8DCC3; border-radius: 5px;
  padding: 0.05rem 0.4rem; align-self: flex-start;
}
.da-quicker { margin: 0.7rem 0 0 !important; font-size: 0.82rem !important; }

/* One person's timesheet. Read down a column — were the days consistent, which
   are short, which have a punch missing — so no rule between rows. */
.ts-person-summary {
  display: flex; flex-wrap: wrap; gap: 1.2rem 2.4rem;
  padding-bottom: 1rem; margin-bottom: 0.3rem;
  border-bottom: 1px solid var(--line);
}

/* --------------------------------------------------------- period and totals -- */
.ts-range { align-items: center; gap: 0.8rem 1.1rem; }
.ts-presets { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.ts-dates { display: flex; align-items: center; gap: 0.45rem; }
.ts-dates input { max-width: 10.5rem; }
.ts-toggle { margin-left: auto; }

/* Hours against expectation.
   The excess sits beside the total rather than under it, because the two are
   read together — a big number on its own says nothing without the shift it is
   being measured against. Tone is carried by a rule at the top rather than by
   colouring the figure: an amber number reads as an error, and somebody
   working over their days has not made one. */
/* THE HIGHLIGHTED FIGURE, and it is now a shared component.
   This tile was invented on Timesheets and asked for everywhere else — the
   figure large and tabular, the label small and uppercase above nothing, the
   context in an `em` underneath, and the TONE carried by the 3px rule along
   the top rather than by the colour of the number.
   `.figure` is the name to use from here on. `.ts-total` is the same rule,
   kept because Timesheets is built on it; one definition, two names, so the
   two cannot drift into looking almost alike. */
.figures,
.ts-totals {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}
.figure,
.ts-total {
  display: flex; flex-direction: column; gap: 0.1rem;
  padding: 0.75rem 0.9rem;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: 8px; border-top: 3px solid var(--line);
}
.figure strong,
.ts-total strong {
  font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em;
  line-height: 1.15; color: var(--ink); font-variant-numeric: tabular-nums;
}
.figure span,
.ts-total span {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--ink-3);
}
.figure em,
.ts-total em {
  font-style: normal; font-size: 0.78rem; color: var(--ink-3);
  margin-top: 0.25rem; line-height: 1.35;
}
/* The unit that follows a figure — "days", "of 21". Part of the number, so it
   sits on the same line at the weight of the label rather than becoming a
   second figure competing with the first. */
.figure strong small {
  font-size: 0.78rem; font-weight: 600; color: var(--ink-3);
  letter-spacing: 0; margin-left: 0.25rem;
}
.ts-total--over { border-top-color: var(--warn); }
.ts-total--under { border-top-color: var(--teal); }
/* Tone along the top, never on the digits. "3 days" is not an error.
   --now    it falls due today
   --soon   this week
   --late   the date has passed */
.figure--now  { border-top-color: var(--warn); }
.figure--soon { border-top-color: var(--teal); }
.figure--late { border-top-color: var(--err); }
/* A figure that is a link to the page that acts on it. The whole tile, not a
   word inside it — a 2rem number is the thing the hand goes for. */
a.figure { text-decoration: none; color: inherit; transition: border-color .12s, background .12s; }
a.figure:hover { background: var(--card); border-color: #C9D4D6; }
a.figure:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
/* Nothing to count, because nothing counts it yet. Dashes rather than a zero,
   and the tile says why in its own `em`. */
.figure--untracked strong { color: var(--ink-3); font-weight: 600; }


/* ------------------------------------------------------------- the machines -- */
/* A LIST, not a stack of cards. Six terminals meant scrolling past four to
   reach the fifth and there will be nine, so each one is a single line and
   everything else opens when the line is pressed. The line carries what a
   person scans for: which site, what it is called, whether it is answering,
   how much it has sent, when it last spoke. */
.dev {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); margin-bottom: 0.5rem; overflow: hidden;
}
.dev__row {
  display: flex; align-items: center; gap: 0.8rem; width: 100%;
  padding: 0.7rem 0.9rem; background: none; border: 0; cursor: pointer;
  font: inherit; color: var(--ink); text-align: left;
}
.dev__row:hover { background: var(--bg); }
.dev__row:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }
/* The site code, which is the first thing anybody looks for. Fixed width so
   the names below it line up into a column. */
.dev__site {
  flex: 0 0 4.2rem; font-size: 0.75rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--teal-dark);
}
.dev__site--unset { color: var(--warn); }
.dev__name { flex: 1 1 auto; font-weight: 600; min-width: 8rem; }
.dev__name .muted { font-weight: 400; font-size: 0.85rem; }
.dev__n { flex: 0 0 auto; font-size: 0.82rem; color: var(--ink-2); }
.dev__seen { flex: 0 0 auto; font-size: 0.8rem; }
.dev__sn { flex: 0 0 auto; font-size: 0.74rem; }
.dev__chev { flex: 0 0 auto; font-size: 0.7rem; color: var(--ink-3); }
.dev__body { padding: 0 0.9rem 0.9rem; }
@media (max-width: 820px) {
  /* The serial is the first thing to go: it is how the machine identifies
     itself to us, not how a person identifies it. */
  .dev__sn { display: none; }
}
@media (max-width: 620px) {
  .dev__row { flex-wrap: wrap; gap: 0.4rem 0.7rem; }
  .dev__n, .dev__seen { font-size: 0.76rem; }
}

/* A button counting down to something it will then ask about again. Amber,
   because it is armed and not yet done. */
.btn--waiting {
  background: #FDF6E7 !important; border-color: #E8DCC3 !important;
  color: var(--warn) !important; font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------- who is on site now -- */
/* A pill per site, and the count in it is EMPLOYEES only.
   Off-payroll people punch on the same terminals and are deliberately left
   out of the number — they are not employed, and a headcount that includes
   the guards makes every per-head figure downstream wrong. They are shown
   beside it instead, small, because somebody standing at the door can see
   them and a figure that disagrees with the door stops being believed.

   Fixed reading order, never sorted by count: a row of pills that reorders
   itself through the day is a row where the pill you are looking for has
   moved. */
/* The three groups. The label is a word beside the row rather than a heading
   above it: three headings for nine pills is more structure than nine pills
   deserve, and the eye needs only to know which row it is reading. */
.sitegroup {
  display: flex; align-items: baseline; gap: 0.7rem;
  flex-wrap: wrap; margin-bottom: 0.5rem;
}
.sitegroup:last-child { margin-bottom: 0; }
.sitegroup__label {
  flex: 0 0 7.5rem;
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--ink-3);
  cursor: help;
}
@media (max-width: 700px) {
  /* Stacked, and then the label does want to be a heading. */
  .sitegroup { display: block; }
  .sitegroup__label { display: block; margin-bottom: 0.35rem; }
}
.sites { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.site {
  display: inline-flex; align-items: baseline; gap: 0.45rem;
  padding: 0.4rem 0.85rem;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 100px; cursor: pointer; text-align: left;
  font: inherit; color: var(--ink);
}
.site:hover { border-color: #C9D4D6; background: var(--bg); }
.site:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.site__code {
  font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3);
}
.site__n {
  font-size: 1.1rem; font-weight: 700; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.site__of { font-size: 0.74rem; color: var(--ink-3); }
/* The state dot. Three states and they are not decoration: a 0 from a live
   terminal means nobody is here, and a 0 from a terminal that stopped
   answering at lunch means we cannot see the place. */
.site__dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok); flex: 0 0 auto; align-self: center;
}
.site--live  .site__dot { background: var(--ok); }
.site--stale .site__dot { background: var(--warn); }
.site--none  .site__dot { background: var(--line); }
.site--none  { color: var(--ink-3); }
.site--none .site__n { color: var(--ink-3); font-weight: 600; }
/* Somebody is actually there. The one pill in the row that should catch an
   eye scanning it. */
.site--has { border-color: #BFDECB; background: #F3FAF6; }
.site[aria-expanded="true"] { border-color: var(--teal); background: var(--teal-pale); }
/* The off-roll count, when there is one. Same colour as the label, never the
   colour of the employee count it sits next to. */
.site__off { font-size: 0.72rem; color: var(--ink-3); }

/* WHERE IS ONE PERSON.
   Above the pills, because it answers a question somebody arrives with rather
   than one the pills provoke. The hint sits beside the box and not under the
   results: it is a caveat about what the answer means, and a caveat read
   after the answer is a caveat that has already been misread. */
.whereis {
  display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
  margin-bottom: 0.9rem;
}
.whereis input { max-width: 20rem; }
.whereis .muted { font-size: 0.78rem; }
.whereis-out { margin-bottom: 0.9rem; }
.whereis-none { margin: 0 0 0.9rem; font-size: 0.85rem; }
.whereis-note {
  margin: 0.6rem 0 0; font-size: 0.78rem; line-height: 1.4;
  max-width: 70ch;
}
/* The in/out pill is the first thing in the row and it is the answer, so it
   keeps its width whatever the name beside it does. */
.whereis-out .site-person > .pill { flex: 0 0 2.6rem; text-align: center; }

/* Who is in the place, once a pill is pressed. */
.site-people { margin-top: 0.8rem; }
.site-people__head {
  display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.site-people__head h3 { margin: 0; font-size: 1rem; }
.site-person {
  display: flex; align-items: baseline; gap: 0.6rem;
  padding: 0.35rem 0; border-bottom: 1px solid var(--line);
  font-size: 0.88rem;
}
.site-person:last-child { border-bottom: 0; }
.site-person__for { margin-left: auto; color: var(--ink-3); font-size: 0.8rem; }
.ts-totals__ot { margin: 0.9rem 0 0 !important; font-size: 0.83rem !important; }

/* ------------------------------------------------ who this timesheet is for -- */
/* The facts that give the hours their units. Chips rather than a table row:
   they are read together and at a glance, and a table makes them look like
   data to be scanned rather than context to be absorbed. */
.subj-facts {
  display: flex; flex-wrap: wrap; gap: 0.4rem;
  margin: 0 0 1rem;
}
/* Each pill is a statement, not a label and a value. "Type: Corporate" spent a
   word saying what the next word already said. The rate lives IN the pill,
   because "eligible" without a rate is half an answer, and the reason lives in
   the title so hovering or focusing explains it. */
.subj-pill {
  display: inline-flex; align-items: center;
  font-size: 0.82rem; font-weight: 600; color: var(--ink);
  background: var(--card); border: 1px solid var(--line);
  border-radius: 100px; padding: 0.32rem 0.8rem;
  cursor: default;
}
.subj-pill[tabindex]:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
/* Eligible / not, in green and red as asked. These are the theme's semantic
   colours and this is a legitimate use of them: an entitlement either exists
   or it does not, and which one it is changes what every figure below means. */
.subj-pill--yes {
  background: #EAF6EF; border-color: #BFDECB; color: #17703F;
}
.subj-pill--no {
  background: #FCEFEE; border-color: #EFD3D0; color: #8F281F;
}

/* The caveats, folded away.
   Six paragraphs above the data is a wall, and a wall gets skipped — which is
   worse than one line, because the reader then has none of it. */
.ts-caveats {
  background: var(--teal-pale); border: 1px solid #CDE9EF;
  border-radius: var(--radius); margin-bottom: 1rem;
}
.ts-caveats > summary {
  cursor: pointer; list-style: none;
  padding: 0.6rem 0.9rem; display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.85rem; color: var(--teal-dark); font-weight: 600;
}
.ts-caveats > summary::-webkit-details-marker { display: none; }
.ts-caveats > summary::after {
  content: ""; margin-left: auto; flex: 0 0 auto;
  width: 0.38rem; height: 0.38rem;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); transition: transform 0.15s;
}
.ts-caveats[open] > summary::after { transform: rotate(-135deg); }
.ts-caveats > summary:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.ts-caveats__body { padding: 0 0.9rem 0.8rem 2.05rem; }
.ts-caveats__body p {
  margin: 0 0 0.5rem; font-size: 0.85rem; color: var(--ink-2); max-width: 88ch;
}
.ts-caveats__body p:last-child { margin-bottom: 0; }

/* -------------------------------------------------------- presence overview -- */
.pres-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 0.9rem;
}
.pres-head .da-title { margin: 0; }

/* One terminal, collapsed. The count is the answer most of the time; the names
   matter when it is wrong. */
.pres-machine {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); margin-bottom: 0.6rem;
}
.pres-machine > summary {
  cursor: pointer; list-style: none;
  padding: 0.75rem 0.95rem;
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
}
.pres-machine > summary::-webkit-details-marker { display: none; }
.pres-machine > summary strong { font-size: 0.95rem; color: var(--ink); }
.pres-machine > summary:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.pres-count {
  font-size: 0.85rem; font-weight: 600; color: var(--teal-dark);
  background: var(--teal-pale); border-radius: 100px; padding: 0.15rem 0.6rem;
}
.pres-meta { margin-left: auto; font-size: 0.8rem; }
.pres-body { padding: 0 0.95rem 0.9rem; }

/* A terminal's health in three states, not two: "quiet" is the one to look at
   before it becomes "offline". A dot rather than a word, because the word is
   already in the meta line and two of them is noise. */
.pres-dot {
  width: 0.5rem; height: 0.5rem; border-radius: 50%; flex: 0 0 auto;
}
.pres-dot--ok { background: var(--ok); }
.pres-dot--warn { background: var(--warn); }
.pres-dot--err { background: var(--err); }

/* -------------------------------------------------------- the two questions -- */
.ts-choose {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}
.ts-choose__card {
  border: 1px solid var(--line); border-radius: 8px;
  padding: 0.95rem 1.05rem; background: var(--bg);
}
.ts-choose__card h4 {
  font-size: 0.92rem; font-weight: 600; color: var(--ink); margin: 0 0 0.25rem;
}
.ts-choose__card > p { margin: 0 0 0.8rem; font-size: 0.85rem; }

/* -------------------------------------------------------- the timesheet list -- */
/* A list, not a chart. The question here is who was in, from when to when, and
   for how long — four columns answer it and fit on a phone. The timeline that
   used to be here answered "were the days consistent", which nobody was
   asking. */
.ts-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.ts-table th {
  text-align: left; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
  padding: 0 0.7rem 0.5rem; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.ts-table td {
  padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--line);
  color: var(--ink-2);
}
.ts-table tr:last-child td { border-bottom: 0; }
.ts-table .num { text-align: right; }
.ts-table a { color: var(--ink); font-weight: 600; text-decoration: none; }
.ts-table a:hover { color: var(--teal-dark); text-decoration: underline; }
.ts-table .emp-id { font-size: 0.78rem; }
/* Hours line up under each other, which is the whole reason for a column of
   figures. */
.tnum { font-variant-numeric: tabular-nums; }

/* A missing out-time. Named rather than blank: an empty cell reads as an
   oversight in the page rather than a fact about the day. */
.ts-missing {
  font-size: 0.76rem; font-weight: 600; color: var(--warn);
  background: #FDF6E7; border: 1px solid #E8DCC3;
  border-radius: 4px; padding: 0.05rem 0.35rem; white-space: nowrap;
}
/* STILL IN, which is not the same as a missed punch. A day in progress is an
   ordinary state of affairs — teal like the rest of the "this is fine"
   vocabulary, not the amber of something to chase. It becomes .ts-missing
   after fourteen hours, and that one IS amber. */
.ts-open {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--teal-dark);
  white-space: nowrap;
}
/* Before the payroll cut-off. Dimmed, and said once in the notes. */
.ts-tr--before td { opacity: 0.6; }

/* =========================================================================
   LETTERS
   A warning is not a document, it is a step in a sequence — and each one is
   evidence that the one before it happened. So the page leads with where
   somebody stands, then what can be issued next, then the letter itself on a
   sheet that prints as the real thing.
   ========================================================================= */
.esc {
  display: flex; gap: 1.1rem; align-items: flex-start;
  border-radius: var(--radius); padding: 0.9rem 1rem;
  border: 1px solid var(--line); background: var(--bg);
}
.esc__count { text-align: center; flex: 0 0 auto; min-width: 4.5rem; }
.esc__count strong {
  display: block; font-size: 1.9rem; font-weight: 700; line-height: 1;
  letter-spacing: -0.02em; color: var(--ink); font-variant-numeric: tabular-nums;
}
.esc__count span {
  font-size: 0.64rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3);
}
.esc p { margin: 0 0 0.3rem; font-size: 0.9rem; color: var(--ink-2); max-width: 74ch; }
.esc p:last-child { margin-bottom: 0; }
.esc--ok { border-left: 3px solid var(--ok); }
.esc--warn { border-left: 3px solid var(--warn); background: #FDF6E7; }
.esc--err { border-left: 3px solid var(--err); background: #FCEFEE; }

/* The kinds of letter. A kind the escalation does not allow is shown disabled
   with the reason rather than hidden: a missing button is a mystery, a
   disabled one with a sentence is an explanation. */
.kind-grid {
  display: grid; gap: 0.6rem; margin-bottom: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
.kind {
  display: flex; flex-direction: column; gap: 0.2rem; text-align: left;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 8px; padding: 0.7rem 0.85rem; cursor: pointer; font: inherit;
}
.kind:hover { border-color: var(--teal); background: var(--teal-pale); }
.kind:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.kind strong { font-size: 0.9rem; color: var(--ink); }
.kind span { font-size: 0.78rem; color: var(--ink-3); line-height: 1.4; }
.kind--off { opacity: 0.55; cursor: not-allowed; }
.kind--off:hover { border-color: var(--line); background: var(--card); }

.letter-form { max-width: 44rem; }
.letter-form h4 { font-size: 1rem; font-weight: 600; margin: 0 0 0.8rem; color: var(--ink); }
.letter-form .form-label { margin-top: 0.9rem; }
.inc {
  display: grid; grid-template-columns: 1.6rem 1fr 2rem;
  gap: 0.5rem; align-items: start; margin-bottom: 0.6rem;
}
.inc__n {
  font-size: 0.85rem; font-weight: 700; color: var(--ink-3);
  padding-top: 0.55rem; text-align: right;
}
.inc textarea { margin-top: 0.35rem; }
.obj {
  display: grid; grid-template-columns: 1fr 2rem; gap: 0.5rem;
  margin-bottom: 0.5rem;
}

/* ---------------------------------------------------------------- the sheet -- */
/* Deliberately a sheet of paper on screen too. Somebody about to put their
   name to a letter should see it as the person receiving it will, not as a
   form. */
.sheet {
  background: #fff; border: 1px solid var(--line);
  border-radius: 4px; padding: 3rem 3.2rem;
  max-width: 48rem; margin: 0 auto 1rem;
  font-size: 0.95rem; line-height: 1.65; color: #111;
  box-shadow: 0 8px 28px -20px rgba(19,26,28,0.4);
}
.sheet__head {
  text-align: right; font-size: 0.78rem; line-height: 1.5; color: #333;
  margin-bottom: 2.2rem;
}
.sheet__head strong { display: block; font-size: 0.95rem; color: #000; }
.sheet__title {
  text-align: center; font-size: 1rem; font-weight: 700;
  text-decoration: underline; margin: 0 0 1.6rem; letter-spacing: 0.01em;
}
.sheet__date { text-align: right; margin: 0 0 1.6rem; }
.sheet__to { margin: 0 0 1.4rem; }
.sheet p { margin: 0 0 1rem; }
.sheet__list { margin: 0 0 1rem; padding-left: 1.6rem; }
.sheet__list li { margin-bottom: 0.6rem; }
.sheet__table {
  width: 100%; border-collapse: collapse; margin: 0 0 1rem;
  font-size: 0.9rem;
}
.sheet__table th, .sheet__table td {
  border: 1px solid #ccc; padding: 0.45rem 0.6rem; text-align: left;
}
.sheet__table th { width: 12rem; background: #f6f6f6; font-weight: 600; }
.sheet__note { font-size: 0.82rem; color: #444; font-style: italic; }
.sheet__sign { margin-top: 2.4rem; }
.sheet__space { height: 3.2rem; }
.sheet__ack {
  margin-top: 2.6rem; padding-top: 1rem; border-top: 1px dashed #bbb;
  font-size: 0.88rem;
}
.sheet__ack p { margin: 0 0 0.8rem; }

/* Printing. The sheet is the page; everything else is scaffolding. */
@media print {
  .no-print, .rail, .main-content > .wrap > .page-head,
  .panel, #pickPanel, #escalation, #compose, #historyPanel { display: none !important; }
  .main-content { margin-left: 0 !important; }
  .wrap { max-width: none !important; padding: 0 !important; }
  .sheet {
    border: 0; box-shadow: none; padding: 0; max-width: none;
    border-radius: 0; font-size: 11pt;
  }
  @page { margin: 18mm 16mm; }
}

/* ══════════════════════════════════════════════════════════════════════════
   ONE PILL.

   A small word saying what state something is in: draft, final, revoked,
   queried, expiring, blocking. It appears on nine pages and had been
   reinvented on five of them — .cl-state on Expense Claims, .bl-tag on
   Blockers, .tok-state on API Access, .rq-cost on Reconcile, .rc-status on
   Register Clean-up — each with its own padding, its own radius and its own
   idea of what green means.

   Six meanings, and no more. A palette that grows a colour per page is a
   palette that stops telling the reader anything:

     .pill            neutral — a fact, no judgement
     .pill--ok        it happened, or it is fine
     .pill--warn      it needs somebody
     .pill--bad       it is wrong, or it was refused
     .pill--info      in progress, waiting
     .pill--quiet     over, superseded, archived

   Geometry is .status-pill's, which was already correct: a 999px lozenge at
   0.7rem. This adds the colours it was missing and one name for all of them.
   My first attempt at this used a 4px radius and would have been a sixth
   variant rather than a unification.
   ══════════════════════════════════════════════════════════════════════════ */

.pill {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; white-space: nowrap;
  padding: 0.24rem 0.55rem; border-radius: 999px;
  border: 1px solid var(--line);
  background: #F2F4F4; color: var(--ink-2);
  vertical-align: middle;
}

.pill--ok    { background: #EAF6EF; color: #1A7A4A; border-color: #BFDECB; }
.pill--warn  { background: #FDF6E7; color: var(--warn); border-color: #E8DCC3; }
.pill--bad,
/* --err is app.css's name for the same meaning, and the dashboard, Data
   Health, Devices and the approval queue all write it. Adding .pill above
   without it silently repainted every one of those grey: the base rule sets
   a neutral ground and sits LATER in the cascade than app.css's .pill--err,
   so the severity colour lost. The dashboard's "Critical" rows going the
   same grey as everything else is how it was noticed. */
.pill--err   { background: #FCEFEE; color: var(--err); border-color: #EFD3D0; }
.pill--info  { background: var(--teal-pale); color: var(--teal-dark); border-color: #CFE9EF; }
.pill--quiet,
/* Same again: app.css calls the neutral one --muted. */
.pill--muted { background: #F2F4F4; color: var(--ink-3); border-color: var(--line); }

/* The old names, aliased rather than hunted down across nine pages. */
/* .status-pill was already the right shape — a 999px lozenge at 0.7rem —
   so .pill above was made to match IT rather than the other way round. What
   it lacked was a border and a neutral ground, which is what made the
   per-page reinventions look different even when the geometry agreed. */
.status-pill {
  border: 1px solid var(--line);
  background: #F2F4F4;
  vertical-align: middle;
}
.cl-state, .tok-state, .bl-tag, .rc-status {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; white-space: nowrap;
  padding: 0.24rem 0.55rem; border-radius: 999px;
  border: 1px solid var(--line);
  background: #F2F4F4; color: var(--ink-2);
  vertical-align: middle;
}

/* State names as they are written in the data, mapped onto the six meanings
   above — so a page can print its own vocabulary and still look like the rest
   of the product. */
/* Blockers writes TWO classes per item and they are not interchangeable:
   .bl--blocking on the whole card, and .bl-tag--blocking on the little word
   inside it. These rules named the card, so `color: var(--err)` cascaded into
   every paragraph of a blocking item — the title, the prose, "I need" and
   "unblocks" were all red, which is unreadable and stops the colour meaning
   anything. It is the tag that carries the state. */
.cl-filed,    .tok-active,   .bl-tag--waiting  { background: var(--teal-pale); color: var(--teal-dark); border-color: #CFE9EF; }
.cl-approved, .cl-paid,      .tok-expiring     { background: #EAF6EF; color: #1A7A4A; border-color: #BFDECB; }
.cl-queried,  .bl-tag--degraded                { background: #FDF6E7; color: var(--warn); border-color: #E8DCC3; }
.cl-declined, .tok-revoked,  .bl-tag--blocking { background: #FCEFEE; color: var(--err); border-color: #EFD3D0; }
.tok-expired                                    { background: #F2F4F4; color: var(--ink-3); border-color: var(--line); }


/* =========================================================================
   THE SHELL BAR
   Who is signed in, and what needs attention. Fixed at the top right on
   every console page.

   WHY IT IS NOT PART OF THE PAGE. Every page would otherwise decide for
   itself whether to show the bell, where to put it and what to call the
   person — which is how a console ends up with the user's name in three
   places and the alert count in none. shell.js draws it once.

   It floats over the content rather than pushing it down: the page heads
   were all written against a top edge, and inserting a 48px band above
   them would move every heading on every page by 48px. So it is
   position:fixed with a transparent ground, and the pages keep their
   measure. The two controls carry their own surfaces so they stay legible
   over whatever is behind them.
   ========================================================================= */
.shellbar {
  position: fixed; top: 0; right: 0; z-index: 1035;
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.7rem 1.1rem;
  pointer-events: none;            /* the gap between the two is not a target */
}
.shellbar > * { pointer-events: auto; }

/* A BUTTON, because pressing your own name is how you sign out. It was a
   div, and a div carrying the only route to Sign out is unreachable by
   keyboard. */
.shellbar__who {
  display: flex; align-items: center; gap: 0.5rem;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 100px; padding: 0.25rem 0.6rem 0.25rem 0.3rem;
  box-shadow: 0 1px 2px rgba(19,26,28,0.05);
  font-size: 0.82rem; line-height: 1.25;
  font: inherit; font-size: 0.82rem; cursor: pointer; text-align: left;
}
.shellbar__who:hover { border-color: var(--teal); }
/* The name-and-role stack. NOT `> span`: that also matched the avatar next
   to it, and at one class plus one type it beat `.shellbar__avatar`, so the
   avatar's `display:grid; place-items:center` was overridden to `block` and
   the initials sat 5px up and left of their circle's centre. */
.shellbar__id { display: block; }
.shellbar__chev { font-size: 0.62rem; color: var(--ink-3); }

/* The account menu. Narrower than the alert panel — it holds two rows, not a
   list of findings. */
.shellbar__menu { width: min(17rem, calc(100vw - 2rem)); }
.shellbar__item {
  display: flex; align-items: center; gap: 0.55rem; width: 100%;
  padding: 0.65rem 1rem; border: 0; background: none;
  border-bottom: 1px solid var(--line);
  font: inherit; font-size: 0.88rem; color: var(--ink-2);
  text-align: left; text-decoration: none; cursor: pointer;
}
.shellbar__item:last-child { border-bottom: 0; }
.shellbar__item:hover { background: var(--bg); color: var(--ink); }
.shellbar__item--danger:hover { color: var(--err); }
.shellbar__avatar {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--teal); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.02em;
  /* Belt and braces on the vertical: with a line-height of 1 the glyph box
     is the cap height, so flex centring has nothing extra to fight. Two
     capitals have no descenders, which is why initials are the one case
     where this is exactly right rather than approximately. */
  line-height: 1;
  flex: 0 0 auto; overflow: hidden;
}
.shellbar__name {
  font-weight: 600; color: var(--ink); white-space: nowrap; display: block;
}
.shellbar__role {
  color: var(--ink-3); font-size: 0.7rem; text-transform: uppercase;
  letter-spacing: 0.06em; white-space: nowrap; display: block;
}

.shellbar__bell {
  position: relative;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 50%; width: 36px; height: 36px;
  display: grid; place-items: center; cursor: pointer;
  color: var(--ink-2); font-size: 1rem;
  box-shadow: 0 1px 2px rgba(19,26,28,0.05);
}
.shellbar__bell:hover { border-color: var(--teal); color: var(--teal-dark); }

/* The count. Absent rather than zero: a badge showing 0 is a badge that
   trains people to ignore badges. */
.shellbar__count {
  position: absolute; top: -3px; right: -3px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 100px; background: var(--err); color: #fff;
  font-size: 0.62rem; font-weight: 700; line-height: 17px;
  text-align: center; border: 2px solid var(--card);
}
.shellbar__count--look { background: var(--warn); }

.shellbar__panel {
  position: absolute; top: calc(100% - 0.3rem); right: 1.1rem;
  width: min(26rem, calc(100vw - 2rem));
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 18px 48px -18px rgba(19,26,28,0.4);
  max-height: min(30rem, 80vh); overflow-y: auto; overscroll-behavior: contain;
}
.shellbar__panel-head {
  padding: 0.8rem 1rem 0.6rem; border-bottom: 1px solid var(--line);
}
.shellbar__panel-head strong { font-size: 0.95rem; }
.shellbar__panel-head p {
  margin: 0.2rem 0 0; font-size: 0.8rem; color: var(--ink-3);
}
.shellbar__alert {
  display: block; padding: 0.7rem 1rem; border-bottom: 1px solid var(--line);
  border-left: 3px solid var(--line);
  text-decoration: none; color: inherit;
}
.shellbar__alert:last-child { border-bottom: 0; }
.shellbar__alert:hover { background: var(--bg); }
.shellbar__alert--act { border-left-color: var(--err); }
.shellbar__alert--look { border-left-color: var(--warn); }
.shellbar__alert-what {
  font-weight: 600; font-size: 0.87rem; color: var(--ink);
  display: flex; gap: 0.45rem; align-items: baseline;
}
.shellbar__alert-why {
  margin: 0.25rem 0 0; font-size: 0.8rem; color: var(--ink-3);
}
.shellbar__empty {
  padding: 1.2rem 1rem; font-size: 0.87rem; color: var(--ok);
  font-weight: 600;
}

/* ROOM FOR THE BAR, ON EVERY PAGE.
   This used to be `padding-right` on `.page-head:first-child` inside `.wrap`
   — which reserved space only on the pages that happen to have both, and
   silently did nothing on the eleven that have neither. Those pages put
   their own first heading straight under a fixed bar, which is what "I can't
   see the top right headers" was.

   So the reserve is on `.main-content` itself: a top padding the height of
   the bar. Nothing can slide under it, whatever a page is built out of, and
   it costs one rule instead of a per-page opt-in that pages forget.

   The bar is 36px of control plus 0.7rem of padding each side, so 3.4rem
   clears it with a little air. Only on wide screens — below 992px the bar
   sits alongside the mobile bar and the pages scroll normally. */
@media (min-width: 992px) {
  .main-content { padding-top: 3.4rem; }
  /* The page's own first block then starts flush, so its top margin goes —
     otherwise the reserve and the margin stack into a gap. */
  .main-content > .wrap { padding-top: 0.4rem; }
  /* `!important`, and not lightly.
     Several of these pages carry Bootstrap's `py-4`, which IS `!important`,
     so an ordinary declaration loses and their first heading landed 32px
     lower than every .wrap page's. Walking from Dashboard to Reviews moved
     the heading, which is half of what "the layout changes between pages"
     means. There is no way to win against a utility class's !important
     except in kind. */
  .main-content .page-wrap,
  .main-content .wrapper,
  .main-content .workspace,
  .main-content .main-card,
  .main-content .container,
  .main-content .container-fluid { padding-top: 0.4rem !important; }
}
@media (max-width: 991.98px) {
  .shellbar { padding: 0.45rem 0.6rem; }
  .shellbar__name, .shellbar__role, .shellbar__chev { display: none !important; }
  .shellbar__who { padding: 0.25rem; border-radius: 50%; }
}
@media print {
  .shellbar { display: none !important; }
}


/* ------------------------------------------------------------------ timesheet
   WHERE, AND WHETHER IT FINISHED TOMORROW.

   Both sit under or beside the time and are deliberately quieter than it: the
   time is what gets read, these are what gets checked. `.ts-place` appears on
   the OUT column only when the branch differs from the IN — which is how a day
   somebody spent crossing Delhi stops looking like an hour and a half at work.
   `.ts-next` is the airline's +1 for a shift that ended the next morning. */
.ts-place{
  display:block; font-size:.72rem; color:var(--ink-3);
  font-weight:400; letter-spacing:0; white-space:nowrap;
}
.ts-next{
  font-size:.7rem; font-weight:700; color:var(--ink-3);
  vertical-align:super; margin-left:.1rem; cursor:help;
}


/* A question that does not apply to this person, which is not the same as a
   value nobody has filled in. "Not recorded" sends somebody looking for it;
   this says there is nothing to look for. */
.value--na{ color:var(--ink-3); font-style:italic; cursor:help; }


/* ------------------------------------------------------- count tiles
   HOW MANY, PER THING, ACROSS THE TOP.

   Used by Missing Details and HR To-do. They were defined in the first page's
   own <style> block and the second page reused the class names — so its tiles
   rendered as a bare vertical list of numbers, which is what an unstyled
   <div> is. Defined once here, where both pages can see them. */
.dp-fields{ display:flex; flex-wrap:wrap; gap:.6rem; margin:0 0 1.25rem; }
.dp-field{
  border:1px solid var(--line); border-radius:10px; padding:.6rem .85rem;
  min-width:9.5rem; background:var(--card);
}
.dp-field__n{ font-size:1.5rem; font-weight:700; line-height:1.1; }
/* Zero is a good number here and should not shout. */
.dp-field__n--none{ color:var(--ink-3); font-weight:600; }
.dp-field__label{ font-size:.8rem; color:var(--ink-3); }


/* ------------------------------------------------------- email and login
   USUALLY EMPTY, AND THAT IS CORRECT. Most of the register has neither an
   address nor a login — a commi has a PIN on a terminal and nothing else — so
   this panel is quiet by default and never says "missing". */
.acct{
  border:1px solid var(--line); border-radius:var(--radius);
  background:var(--card); padding:.85rem 1rem; margin:1rem 0 0;
}
.acct:empty{ display:none; }
.acct__head{ display:flex; flex-wrap:wrap; align-items:baseline; gap:.5rem;
  margin:0 0 .6rem; }
.acct__head h3{ font-size:.95rem; margin:0; }
.acct__head .muted{ font-size:.78rem; color:var(--ink-3); }
.acct__row{ display:flex; flex-wrap:wrap; align-items:center; gap:.6rem;
  padding:.35rem 0; }
.acct__row .label{ min-width:11rem; margin:0; }
.acct__ask{ display:flex; flex-wrap:wrap; gap:.4rem; align-items:center;
  flex:1 1 22rem; }
.acct__ask input{ flex:1 1 16rem; max-width:26rem; }
/* "on file" against "created" is worth distinguishing: one was recorded,
   the other was made by us. */
.acct__tag{ font-size:.68rem; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; color:var(--ink-3); }
.acct__tag--wait{ color:var(--warn); }
/* The date under the address. Quiet, because it is the answer to a question
   somebody asks once — "when was this made, and did I make it?" — and not
   something to read past every time the record is opened. */
.acct__when{ font-size:.72rem; color:var(--ink-3); font-family:inherit;
  margin-top:.15rem; }
.acct__err{ flex:1 1 100%; font-size:.82rem; color:var(--err);
  margin:.25rem 0 0; }
/* THE ONE TIME THE PASSWORD IS ON A SCREEN.
   Mail Central generates it and keeps it nowhere; we do not store it either. So
   this block is the only copy in the world until somebody writes it down, and
   it is built to look like that rather than like a success message: the warm
   border, the password in mono at a size that can be read across a desk, and a
   dismiss that says what dismissing costs. */
.acct__secret{ flex:1 1 100%; margin:.6rem 0 0; padding:.75rem .9rem;
  border:2px solid var(--warn); border-radius:var(--radius);
  background:var(--bg); }
.acct__secret h4{ font-size:.85rem; margin:0 0 .35rem; }
.acct__secret .pw{ font-family:var(--mono); font-size:1.15rem;
  font-weight:700; letter-spacing:.04em; word-break:break-all;
  background:var(--card); border:1px solid var(--line);
  border-radius:var(--radius); padding:.4rem .6rem; display:inline-block; }
.acct__secret p{ font-size:.8rem; color:var(--ink-2); margin:.5rem 0 0; }
.acct__secret .acct__secret-acts{ display:flex; flex-wrap:wrap; gap:.4rem;
  align-items:center; margin:.55rem 0 0; }
.acct__why{ flex:1 1 100%; font-size:.78rem; color:var(--ink-3);
  margin:.2rem 0 0; }
/* "Not applicable" is an ANSWER, not a blank. The kitchen team has no address
   and no login and never will, and a row that renders as an empty box invites
   somebody to fill it in. */
.acct__na{ color:var(--ink-3); font-weight:600; }

/* THE STATUS CODE ON A TIMESHEET ROW.

   MP, P/D, A, WO — Timelabs' own letters, so nothing has to be learned. Three
   tones carry severity and a fourth carries "not a state at all": a rest day
   and a holiday are not mild problems, and giving them a place on the scale
   is how a screen ends up amber for somebody's Sunday. */
.ts-code {
  display: inline-block; min-width: 2.6rem; text-align: center;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em;
  padding: 0.12rem 0.4rem; border-radius: 100px; border: 1px solid transparent;
}
.ts-code--ok    { background: #E6F4EC; color: #1F6E45; border-color: #C6E4D3; }
.ts-code--warn  { background: #FBF1DF; color: #8A6100; border-color: #EBD9A8; }
.ts-code--bad   { background: #FBE9E7; color: #8E2820; border-color: #F0C8C3; }
/* A day somebody typed in. Warm rather than red: it is not an error, it is a
   different kind of evidence — and a red badge on twenty legitimate days in a
   month when a terminal was down would train people to ignore it. */
/* A SORTABLE HEADING LOOKS CLICKABLE OR NOBODY CLICKS IT. The arrow appears
   only on the column in force — one on every heading is decoration, and an
   arrow that never changes teaches people it means nothing. */
/* THE CHANGE HISTORY. Collapsed by default because most of it is uninteresting
   most of the time, and one line per change is what makes it scannable — the
   question is nearly always "when did this last move", not "what were all the
   values". */
.chg-list{ border:1px solid var(--line); border-radius:8px; overflow:hidden; }
.chg{ border-bottom:1px solid var(--line); }
.chg:last-child{ border-bottom:0; }
.chg > summary{ cursor:pointer; padding:.45rem .7rem; display:flex;
  gap:.7rem; align-items:baseline; flex-wrap:wrap; font-size:.85rem; }
.chg > summary:hover{ background:var(--paper-2,#f6f8f8); }
.chg[open] > summary{ background:var(--paper-2,#f6f8f8); font-weight:600; }
.chg__when{ color:var(--ink-3); flex:0 0 5.5rem; }
.chg__kind{ font-size:.66rem; text-transform:uppercase; letter-spacing:.05em;
  color:var(--ink-3); flex:0 0 5rem; }
.chg__gist{ flex:1 1 14rem; min-width:0; }
.chg__who{ color:var(--ink-3); font-size:.78rem; }
.chg__table{ width:100%; border-collapse:collapse; font-size:.82rem;
  background:#fff; }
.chg__table td{ padding:.3rem .7rem; border-top:1px dotted var(--line);
  vertical-align:top; }
.chg__f{ width:12rem; color:var(--ink-3); }
.chg__from{ color:var(--ink-3); text-decoration:line-through; }
.chg__note{ padding:.4rem .7rem; margin:0; font-size:.8rem;
  color:var(--ink-3); background:#fff; }

.ts-th[data-sort]{ cursor:pointer; user-select:none; white-space:nowrap; }
.ts-th[data-sort]:hover{ text-decoration:underline; }
.ts-th[data-sort]::after{ content:""; }
.ts-th[data-dir="asc"]::after{ content:" \2191"; opacity:.75; }
.ts-th[data-dir="desc"]::after{ content:" \2193"; opacity:.75; }

.ts-code--manual{ background:#FDF3E3; color:#8A6100; border-color:#E6CFA3;
  font-size:.66rem; letter-spacing:.02em; margin-left:.25rem; }
.ts-code--muted { background: var(--paper-2, #f1f1ee); color: var(--ink-3);
                  border-color: var(--line); }

/* A tick beside the code: somebody RECORDED this rather than the punches
   saying it. Same letter, different fact — the second can be argued with. */
.ts-code__by { margin-left: 0.25rem; color: var(--ink-3); font-size: 0.7rem; }

/* A decided day says so in words rather than with a tick. It changes what
   somebody is paid, and a ✓ explained only by a hover title is not a thing
   people read. Pressing it opens the row. */
.ts-decided { border: 1px solid var(--line); background: var(--bg);
  border-radius: 99px; font-size: 0.68rem; padding: 0.05rem 0.45rem;
  cursor: pointer; color: var(--ink-2); letter-spacing: 0.02em; }
.ts-decided:hover { border-color: var(--ink-3); }
.ts-decided__row td { background: var(--bg); font-size: 0.84rem;
  line-height: 1.55; }

/* The decision form, opened into the row it decides. In the row rather than a
   modal because the punches being judged are two inches above the box, and a
   dialog that covers them makes somebody decide from memory. */
.ts-form__row td { background: var(--bg); }
.ts-form { padding: 0.5rem 0.2rem; max-width: 46rem; }
.ts-form__line { display: flex; align-items: center; gap: 0.5rem;
  margin-bottom: 0.4rem; font-size: 0.88rem; }
.ts-form__line input[type="checkbox"] { width: 1rem; height: 1rem; }
.ts-form__line span { min-width: 4rem; }
.ts-form__line .form-control { max-width: 9rem; }

/* A time somebody typed, beside one a machine recorded. */
.ts-stated { font-size: 0.62rem; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink-3); border: 1px solid var(--line);
  border-radius: 3px; padding: 0 0.22rem; margin-left: 0.28rem;
  vertical-align: middle; }

/* Marked on the person's own phone. Teal rather than grey, because unlike
   "Modified" this is not a correction — it is the normal way in at the four
   branches with no reader, and a warning colour would read as a problem on
   every row of their month. The number rides inside the chip so a two-punch
   day is one glance rather than two. */
.ts-self { font-size: 0.62rem; color: var(--teal-dark);
  border: 1px solid var(--teal); border-radius: 3px; padding: 0 0.22rem;
  margin-left: 0.28rem; vertical-align: middle; white-space: nowrap; }
.ts-self .bi { font-size: 0.66rem; }
.ts-self .bi + * { margin-left: 0.12rem; }

/* The four row actions. Quiet until hovered: they sit on every row of a
   month and a column of coloured buttons reads as a column of problems. */
.ts-act { white-space: nowrap; width: 1%; padding-right: 0.4rem; }
.ts-ico {
  background: none; border: 0; cursor: pointer; padding: 0.15rem 0.22rem;
  color: var(--ink-3); border-radius: 4px; line-height: 1;
}
.ts-ico:hover { background: var(--paper-2, #f1f1ee); color: var(--ink); }
.ts-ico--off { opacity: 0.25; cursor: default; }
.ts-ico--off:hover { background: none; color: var(--ink-3); }

.sidebar { background: var(--rail); }
.mobilebar { background: var(--rail); }

/* The one rail rule that does not follow from the background: app.css marks
   the current page with var(--teal), which is now a pink too close to the
   rail's own to be seen. */
.sidebar .nav-link.active { border-left-color: var(--rail-mark); }

/* THE MENU SEARCH, WHICH THIS FILE HAD BEEN BREAKING.
   `input:not([class])` below dresses bare form controls as white boxes with
   ink text — right for a page, wrong for the one input that lives ON the
   rail, which app.css styles as a translucent well with white text. The rail
   search carries no class, so it was losing: a white slab with a placeholder
   written in white on white. Two classes beat one, and it is stated here
   rather than by giving the input a class, because shell.js draws that markup
   to match the ops console's. */
/* The :not() chain below scores 0,3,1 — every [class] inside a :not()
   counts — so a plain `.sidebar .nav-search input` at 0,2,1 LOSES to it.
   Matched here and then some, rather than left to the cascade. */
.sidebar .nav-search input:not([class]):not([type="checkbox"]):not([type="radio"]) {
  padding: 0.42rem 1.8rem; font-size: 0.82rem; border-radius: 6px;
  color: #fff; background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
}
.sidebar .nav-search input:not([class])::placeholder { color: rgba(255,255,255,0.55); }
.sidebar .nav-search input:not([class]):focus {
  border-color: var(--rail-mark); background: rgba(255,255,255,0.16);
}

/* The brand mark is drawn as a white outline for a dark rail, which is what
   this is again — so it needs no filter. Stated because the pastel version
   inverted it, and a stale filter here would black it out. */
.sidebar-brand img { filter: none; }

/* ---------------------------------------------------------- THE TOP BAR --
   One solid band, the shape the SproutBox admin uses — the owner's call,
   20 September 2026.

   IT IS STICKY, NOT FIXED, and that is the substance of the change rather
   than the colour. Floating pills sat on top of whatever the page put in its
   top-right corner: on the wide tables they covered the last column's
   heading, and the only route to Sign out was hidden behind a click on your
   own name. The band takes its own 53px, so nothing is underneath it.

   INSET BY THE RAIL so the hairline begins where the content begins, and it
   follows the rail when it collapses. */
.shellbar {
  position: sticky; top: 0; z-index: 1035;
  margin-left: var(--sidebar-w);
  display: flex; align-items: center; gap: 1rem;
  padding: 0.7rem 1.4rem;
  background: var(--card); border-bottom: 1px solid var(--line);
  pointer-events: auto;
}
body.rail-collapsed .shellbar { margin-left: var(--sidebar-rail); }

/* The title takes the slack, which is what pushes everything else right. */
.shellbar__title {
  flex: 1 1 auto; min-width: 0;
  font-weight: 600; font-size: 0.95rem; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.shellbar__menu {
  background: none; border: 1px solid var(--line); border-radius: 8px;
  color: var(--ink-2); padding: 0.15rem 0.45rem; font-size: 1rem; line-height: 1;
}

/* Name and role, flat: no button, no dropdown, nothing hidden behind it. */
.shellbar__who {
  display: flex; align-items: center; gap: 0.5rem;
  background: none; border: 0; box-shadow: none; padding: 0;
}
.shellbar__role {
  display: inline-block; background: var(--teal-pale); color: var(--teal-dark);
  border-radius: 100px; padding: 0.02rem 0.45rem;
  font-size: 0.64rem; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
}

/* The bell and its neighbours are one size and one shape, so the right-hand
   end reads as a set rather than as three things that happened to land
   together. */
.shellbar__bell,
.shellbar__act {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  height: 32px; min-width: 32px; padding: 0 0.5rem;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  color: var(--ink-2); font-size: 0.86rem; text-decoration: none; cursor: pointer;
  position: relative;
}
.shellbar__bell:hover,
.shellbar__act:hover { border-color: var(--teal); color: var(--teal-dark); }
.shellbar__out { padding: 0 0.7rem; font-size: 0.8rem; }
.shellbar__out:hover { border-color: var(--err); color: var(--err); }

/* The panel hangs from the bar's bottom edge, not from the top of the page. */
.shellbar__panel { top: calc(100% + 0.35rem); right: 1.4rem; }

@media (max-width: 991.98px) {
  .shellbar { margin-left: 0; padding: 0.55rem 0.8rem; gap: 0.6rem; }
  .shellbar__avatar, .shellbar__role { display: none; }
  .shellbar__outword { display: none; }
  .shellbar__panel { right: 0.8rem; }
}


/* --------------------------------------------------- THE LEAVING BOARD --
   On the dashboard, under People. One row per person serving notice,
   ordered by how little time is left.

   The countdown is the loudest thing in the row because it is the only part
   that changes between one day and the next — and it turns red at a week,
   which is the point where somebody has to act today rather than soon. A
   missing last working day is amber rather than red: it is not urgent, it is
   unplannable, and those want different reactions. */
.nb { margin-top: 1rem; border-top: 1px solid var(--line); padding-top: 0.9rem; }
.nb__head {
  font-size: 0.8rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-3); margin-bottom: 0.5rem;
}
.nb__warn { color: var(--err); text-transform: none; letter-spacing: 0; font-weight: 500; }
.nb__row {
  display: grid; grid-template-columns: 1fr 1.4fr auto; gap: 0.8rem;
  align-items: baseline; padding: 0.5rem 0; border-bottom: 1px solid var(--line);
}
.nb__row:last-child { border-bottom: 0; }
.nb__code { color: var(--ink-3); font-size: 0.74rem; margin-left: 0.4rem; }
.nb__job { font-size: 0.86rem; color: var(--ink-2); }
.nb__cuisine { color: var(--ink-3); }
.nb__kitchen { display: block; color: var(--ink-3); font-size: 0.76rem; }
/* URGENT, on a posting. Small and red rather than large and red: the
   countdown is still the thing being scanned. */
.nb__flag {
  display: inline-block; margin-left: .4rem; padding: .05rem .35rem;
  background: #FDE7E7; color: #8A1F1F; border-radius: 4px;
  font-size: .64rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; vertical-align: middle;
}
/* THE ACCENT IN THIS THEME IS `--teal`, and it is pink (#AD1457) — the
   variable kept its name through the colour change. There is no `--accent`,
   no `--ink-1` and no `--paper-2`; `background: var(--accent)` silently
   resolves to nothing, which is how the "Waiting on us" tile came to be
   white text on a white card. Check theme.css before reaching for a name
   that sounds right. */

/* THE (i) BESIDE A CARD TITLE, and the panel it opens.
   Deliberately quiet: it explains a number somebody already trusts, so it
   should not compete with the number. */
.why {
  flex: 0 0 auto; width: 1.05rem; height: 1.05rem; padding: 0;
  border: 1px solid var(--line); border-radius: 50%;
  background: transparent; color: var(--ink-3);
  font: 700 0.68rem/1 Georgia, serif; cursor: pointer;
}
.why:hover { border-color: var(--teal); color: var(--teal); }
.why[aria-expanded="true"] {
  background: var(--teal); border-color: var(--teal); color: #fff;
}
.why__panel {
  margin: 0 0 0.6rem; padding: 0.6rem 0.7rem;
  background: var(--paper-2, #F7F2F5);
  border: 1px solid var(--line); border-radius: 8px;
  font-size: 0.8rem;
}
.why__row { margin-bottom: 0.4rem; color: var(--ink-2); }
.why__row strong { color: var(--ink-2); }
.why__note { display: block; color: var(--ink-3); font-size: 0.74rem; }
.why__foot {
  margin-top: 0.55rem; padding-top: 0.5rem;
  border-top: 1px solid var(--line);
  color: var(--ink-3); font-size: 0.74rem;
}
.nb__left { text-align: right; font-size: 0.9rem; white-space: nowrap; }
.nb__when { display: block; color: var(--ink-3); font-size: 0.74rem; }
.nb__row--soon .nb__left strong,
.nb__row--today .nb__left strong { color: var(--err); }
.nb__row--no-date .nb__left strong,
.nb__row--gone .nb__left strong { color: var(--warn); }

@media (max-width: 640px) {
  .nb__row { grid-template-columns: 1fr auto; }
  .nb__job { grid-column: 1 / -1; }
}


/* ======================================================================== *
   THE DASHBOARD, TIGHTENED.

   The owner, 22 September 2026: "the dashboard is all over the place, we need
   to make it more compact". It had grown to six stacked cards, each with the
   padding of a page that holds one thing, so the two boards he actually wants
   read first — who is leaving, what is open — pushed everything else below
   the fold on a laptop.

   SCOPED TO `body.dash`, not applied to the console. Every other page here is
   a form or a table somebody works in for ten minutes and wants room to
   breathe; the dashboard is glanced at, and density is what a glance wants.
   Nothing in app.css is touched, because that file is the ops console's and
   is diffed against it.
 * ======================================================================== */

/* THE TWO THAT MATTER, SIDE BY SIDE on anything wider than a tablet. That
   alone takes a screen-and-a-half of stacked cards down to one screen. */
body.dash .dash__pair {
  display: grid; gap: 0.7rem;
  grid-template-columns: repeat(auto-fit, minmax(23rem, 1fr));
  align-items: start;
}
body.dash .dash__pair > .card { margin-bottom: 0; }
body.dash .dash__pair:has(.card[hidden]) { display: block; }

body.dash .card { padding: 0.8rem 0.9rem; margin-bottom: 0.7rem; }
body.dash .card__head { margin-bottom: 0.55rem; gap: 0.6rem; }
body.dash .card__head h2 { font-size: 1.02rem; }
/* The count sits at the end of the heading row rather than wrapping under it
   — at half width "5 heads across 3 postings · 1 past the fill-by date" is
   longer than what it qualifies. */
body.dash .card__head .right { font-size: 0.76rem; line-height: 1.3; text-align: right; }
body.dash .page-head { margin-bottom: 0.8rem; }

/* The tiles: same information, less air. Four across instead of three on a
   14-inch screen, which is the difference between scrolling and not. */
body.dash .figures { gap: 0.55rem; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }
body.dash .figure { padding: 0.5rem 0.6rem; }
body.dash .figure strong { font-size: 1.25rem; }
body.dash .figure em { font-size: 0.72rem; margin-top: 0.15rem; }

/* The boards. A row was 0.5rem top and bottom with a 1rem gap between
   columns; at five rows that is most of a card's height spent on nothing. */
body.dash .nb { margin-top: 0.6rem; padding-top: 0.6rem; }
body.dash .nb__head { margin-bottom: 0.35rem; font-size: 0.74rem; }
body.dash .nb__row { padding: 0.35rem 0; gap: 0.6rem; }
/* A NAME ON ONE LINE. In a half-width card the who column was narrow enough
   to break "Nandan Kumar" in two, which makes three rows look like six. The
   code drops under the name instead of sitting beside it. */
body.dash .nb__who { min-width: 0; }
body.dash .nb__who strong { display: block; }
body.dash .nb__code { margin-left: 0; display: block; }
body.dash .nb__job { font-size: 0.82rem; }
body.dash .nb__kitchen, body.dash .nb__when, body.dash .nb__code { font-size: 0.72rem; }
body.dash .nb__left { font-size: 0.85rem; }

/* The search box and its hint, which took four lines to say one thing. */
body.dash .whereis { margin-bottom: 0.5rem; }
body.dash #whereisHint { font-size: 0.74rem; line-height: 1.35; }
body.dash #siteFoot, body.dash #dueFoot { margin-top: 0.6rem !important; font-size: 0.76rem !important; }


/* ======================================================================== *
   OPEN POSITIONS — THE PAGE'S OWN STYLES, RESTORED.

   The owner, 22 September 2026: "I am unable to post an opening". He was
   right, and it was not the 403 fixed alongside this — the nginx log showed
   no POST had EVER reached the server from that page.

   The cause: this page carries no <style> block and nothing in any
   stylesheet defined its classes. Forty-four of them — the modal, the tabs,
   the filters, every pill — had no rule anywhere. The retheme that removed
   the per-page stylesheets took this one with it, and app-hr.css never got
   the replacement. Its own comment records the same thing happening to the
   increment letter page.

   So "+ Post Opening" ran, added `show` to the modal, and `show` meant
   nothing: no dialog, no form, nothing to submit. A dead button with no
   error, which is the worst kind — it reads as "the system is broken" and
   leaves nothing to report.

   THE MODAL FIRST, because that is what unblocks the work.
 * ======================================================================== */

.modal-back {
  /* HIDDEN UNTIL `show`. Without this rule the dialog is an ordinary block
     in the flow of the page: it was never invisible, it was inert. */
  display: none;
  position: fixed; inset: 0; z-index: 1060;
  background: rgba(19, 26, 28, 0.55);
  padding: 1.5rem 1rem;
  overflow-y: auto;
}
.modal-back.show { display: flex; align-items: flex-start; justify-content: center; }

.gli-modal {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.2rem 1.3rem;
  width: min(44rem, 100%); margin: auto;
  box-shadow: 0 18px 40px rgba(19, 26, 28, 0.18);
}
.gli-modal h3 { font-size: 1.1rem; margin: 0 0 0.9rem; }
.gli-modal label { display: block; font-size: 0.82rem; color: var(--ink-2); margin: 0.7rem 0 0.25rem; }
.gli-modal input[type="text"], .gli-modal input[type="number"],
.gli-modal input[type="date"], .gli-modal select, .gli-modal textarea {
  width: 100%; padding: 0.5rem 0.6rem; font: inherit; font-size: 0.92rem;
  border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink);
}
.gli-modal textarea { min-height: 4.5rem; }

/* A note under a field, explaining a consequence rather than the field. Used
   where an answer has a reach the person cannot see from the form — such as a
   name that is being kept off every other screen. */
.gli-modal .hint {
  margin-top: 0.3rem; font-size: 0.74rem; line-height: 1.45;
  color: var(--ink-3);
}
/* CONFIDENTIAL, on a label. Small and plain: this marks a field as sensitive
   to the person filling it in, and a loud badge on a form about somebody's
   job is the opposite of discreet. */
/* "Opening from: not recorded" — a gap being stated, not data. Dimmed so it
   reads as an absence rather than a value. */
.cell-sub--gap { font-style: italic; opacity: 0.75; }
.conf {
  display: inline-block; margin-left: 0.35rem; padding: 0.05rem 0.35rem;
  background: var(--paper-2, #F7F2F5); color: var(--teal);
  border: 1px solid var(--line); border-radius: 4px;
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; vertical-align: middle;
}

/* The row that carries a checkbox and its label, which a `display:block`
   label would otherwise push onto two lines. */
.toggle-row { display: flex; align-items: center; gap: 0.45rem; margin: 0.6rem 0; }
.toggle-row label { margin: 0; font-size: 0.85rem; }
.toggle-row input { width: auto; }

.position-meta {
  font-size: 0.8rem; color: var(--ink-3); background: var(--bg);
  border: 1px solid var(--line); border-radius: 8px;
  padding: 0.5rem 0.6rem; margin-top: 0.5rem;
}

/* The tabs: openings, and the position catalogue behind them. */
.tab-strip { display: flex; gap: 0.3rem; border-bottom: 1px solid var(--line); margin-bottom: 1rem; }
.tab-strip button, .tab-strip .tab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  padding: 0.5rem 0.8rem; font: inherit; font-size: 0.9rem;
  color: var(--ink-3); cursor: pointer;
}
.tab-strip .active, .tab-strip button.active {
  color: var(--ink); border-bottom-color: var(--teal); font-weight: 600;
}
.tab-view { display: none; }
.tab-view.active { display: block; }

.filters { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; margin-bottom: 0.8rem; }
.filters select, .filters input {
  padding: 0.35rem 0.5rem; font-size: 0.85rem;
  border: 1px solid var(--line); border-radius: 8px; background: #fff;
}
.tb-spacer { flex: 1 1 auto; }

.openings-table-wrap { overflow-x: auto; }
.notes-cell { max-width: 22rem; font-size: 0.82rem; color: var(--ink-2); }
.cell-pos { font-weight: 600; }
.cell-skills { font-size: 0.78rem; color: var(--ink-3); }
.row-actions { display: flex; gap: 0.3rem; white-space: nowrap; }
.sort-ind { font-size: 0.7rem; color: var(--ink-3); margin-left: 0.2rem; }
.active-sort { color: var(--teal-dark); }
.lede { color: var(--ink-2); max-width: 76ch; }

/* THE PILLS. Their class names are built from the value — "pri-" + priority
   — so every value the data can hold needs a rule, and anything unmatched
   falls back to the plain pill rather than to no styling at all. */
.pri, .team, .status-pill, .fully-enriched-pill, .needs-enrich-pill, .enrich-status {
  display: inline-block; padding: 0.1rem 0.45rem; border-radius: 100px;
  font-size: 0.7rem; font-weight: 600; letter-spacing: 0.02em;
  background: var(--bg); color: var(--ink-2); border: 1px solid var(--line);
}
.pri-URGENT { background: #FDE7E7; color: #8A1F1F; border-color: #F5C9C9; }
.pri-HIGH { background: #FFF4DB; color: #7A5300; border-color: #F0DFB4; }
.pri-NORMAL { background: var(--bg); color: var(--ink-2); }
.pri-LOW { background: var(--bg); color: var(--ink-3); }
.status-OPEN { background: #E3F4E8; color: #1E6B3A; border-color: #C9E7D3; }
.status-FILLED { background: var(--bg); color: var(--ink-3); }
.status-CLOSED, .status-CANCELLED { background: var(--bg); color: var(--ink-3); text-decoration: line-through; }
.team-Kitchen { background: var(--teal-pale); color: var(--teal-dark); border-color: #EFD7E2; }
.team-Corporate { background: #EEF1F5; color: #41506B; border-color: #DCE2EA; }
.fully-enriched-pill { background: #E3F4E8; color: #1E6B3A; border-color: #C9E7D3; }
.needs-enrich-pill { background: #FFF4DB; color: #7A5300; border-color: #F0DFB4; }

/* The audit trail inside the history modal. */
.audit-entry { border-bottom: 1px solid var(--line); padding: 0.6rem 0; }
.audit-entry:last-child { border-bottom: 0; }
.audit-head { display: flex; gap: 0.5rem; align-items: baseline; flex-wrap: wrap; }
.audit-action { font-weight: 600; font-size: 0.88rem; }
.audit-when, .audit-by { font-size: 0.76rem; color: var(--ink-3); }
.audit-details { margin-top: 0.3rem; font-size: 0.82rem; color: var(--ink-2); }
.change-row { display: flex; gap: 0.4rem; flex-wrap: wrap; font-size: 0.8rem; }
.field-name { color: var(--ink-3); }
.field-from { text-decoration: line-through; color: var(--ink-3); }
.field-to { font-weight: 600; }

/* The fill-by date, when it is close or past. Built in JavaScript rather
   than written in the markup — "cls = 'fb-due'" — which is why the first
   sweep for unstyled classes missed both of them: a scan of class="..."
   attributes cannot see a class name that is assembled at render time. */
.fb-due { color: var(--err); font-weight: 600; }
.fb-soon { color: var(--warn); font-weight: 600; }

/* ── THE INBOX ────────────────────────────────────────────────────────────
   One list, two sources. The row's left edge carries the waiting state as a
   colour, because that is the one thing being scanned for — a table where
   every row looks the same is a table read top to bottom, and the whole point
   of sorting by longest wait is that you do not have to. */
.ibx__bar {
  display: flex; align-items: center; gap: 0.7rem;
  flex-wrap: wrap; margin: 0 0 0.9rem;
}
.ibx__spacer { flex: 1 1 auto; }

.ibx__tiles { display: flex; gap: 0.4rem; }
.ibx__tile {
  display: flex; flex-direction: column; align-items: flex-start;
  padding: 0.4rem 0.7rem; border: 1px solid var(--line);
  border-radius: 8px; background: transparent; cursor: pointer;
  line-height: 1.15; text-align: left;
}
.ibx__tile:hover { border-color: var(--teal); }
.ibx__tile.is-on {
  background: var(--teal); border-color: var(--teal); color: #fff;
}
.ibx__n { font-size: 1.05rem; font-weight: 700; }
.ibx__lbl { font-size: 0.7rem; opacity: 0.85; }

.ibx__pills { display: flex; gap: 0.3rem; }
.ibx__pill {
  padding: 0.3rem 0.6rem; border: 1px solid var(--line); border-radius: 999px;
  background: transparent; color: var(--ink-2); cursor: pointer;
  font-size: 0.78rem;
}
.ibx__pill b { font-weight: 700; margin-left: 0.25rem; color: var(--ink-3); }
.ibx__pill:hover { border-color: var(--teal); }
.ibx__pill.is-on {
  background: var(--teal); border-color: var(--teal); color: #fff;
}
.ibx__pill.is-on b { color: #fff; }

.ibx__search {
  min-width: 12rem; padding: 0.35rem 0.6rem;
  border: 1px solid var(--line); border-radius: 8px; font-size: 0.85rem;
}

/* A source that could not be read, said once above the rows that did come. */
.ibx__note {
  padding: 0.5rem 0.75rem; margin: 0 0 0.6rem;
  background: #FFF6E5; border: 1px solid #F0D9A8; border-radius: 8px;
  color: #6B4E16; font-size: 0.82rem;
}

.ibx { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.ibx__row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) 11rem 8rem 8rem;
  gap: 0.75rem; align-items: start;
  padding: 0.6rem 0.8rem;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid transparent;
}
.ibx__row:last-child { border-bottom: 0; }
.ibx__row--soon { border-left-color: var(--warn); }
.ibx__row--late { border-left-color: var(--err); }

.ibx__tag {
  display: inline-block; padding: 0.05rem 0.35rem; border-radius: 4px;
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase;
}
.ibx__tag--libredesk { background: var(--paper-2, #F7F2F5); color: var(--ink-2); }
.ibx__tag--whatsapp { background: #E4F5E9; color: #1B5E32; }
.ibx__ref { display: block; margin-top: 0.2rem; font-size: 0.68rem; color: var(--ink-3); }

.ibx__title { font-size: 0.9rem; font-weight: 600; }
.ibx__title a { color: inherit; text-decoration: none; }
.ibx__title a:hover { text-decoration: underline; }
.ibx__unread {
  display: inline-block; min-width: 1.1rem; padding: 0 0.3rem;
  background: var(--teal); color: #fff; border-radius: 999px;
  font-size: 0.66rem; font-weight: 700; text-align: center;
}
.ibx__detail {
  margin-top: 0.15rem; font-size: 0.8rem; color: var(--ink-2);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.ibx__who, .ibx__with { font-size: 0.82rem; color: var(--ink-2); min-width: 0; }
.ibx__wait { font-size: 0.85rem; text-align: right; }
.ibx__row--late .ibx__wait strong { color: var(--err); }
.ibx__row--soon .ibx__wait strong { color: var(--warn); }
.ibx__done { color: var(--ink-3); }
.ibx__sub {
  display: block; font-size: 0.7rem; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ibx__wait .ibx__sub { white-space: normal; }

.ibx__empty {
  padding: 1.4rem; text-align: center; color: var(--ink-3);
  border: 1px dashed var(--line); border-radius: 10px;
}
.ibx__foot { margin-top: 0.7rem; font-size: 0.76rem; color: var(--ink-3); }

@media (max-width: 800px) {
  .ibx__row { grid-template-columns: 3.5rem minmax(0, 1fr) 6rem; }
  .ibx__who, .ibx__with { display: none; }
}

/* Reply, on a WhatsApp row. Small: the queue is for reading, and a row of
   loud buttons down the right-hand side competes with the waiting times,
   which are the thing being scanned for. */
.ibx__reply {
  padding: 0.2rem 0.55rem; border: 1px solid var(--teal); border-radius: 6px;
  background: transparent; color: var(--teal); cursor: pointer;
  font-size: 0.75rem; font-weight: 600;
}
.ibx__reply:hover { background: var(--teal); color: #fff; }
.ibx__reply:disabled { opacity: 0.6; cursor: default; }
.ibx__link { color: var(--ink-3); text-decoration: none; }
.ibx__link:hover { text-decoration: underline; }

/* ── MASKED FIGURES ───────────────────────────────────────────────────────
   For the person reading over a shoulder. The dots are the normal state, so
   they are styled to look deliberate rather than like a field that failed to
   load — a row of grey boxes reads as broken and gets ignored. */
.msk { display: flex; align-items: center; gap: 0.35rem; }
.msk__text { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
.msk:not(.msk--shown) .msk__text { color: var(--ink-2); }
.msk__eye,
.msk-wrap__eye {
  flex: 0 0 auto; padding: 0 0.2rem; border: 0; background: transparent;
  color: var(--ink-3); cursor: pointer; font-size: 0.85rem; line-height: 1;
}
.msk__eye:hover,
.msk-wrap__eye:hover { color: var(--teal); }
.msk--shown .msk__text { color: var(--teal-dark); font-weight: 600; }

/* The one on the card header, for all of them at once. */
.msk__all {
  padding: 0.2rem 0.6rem; border: 1px solid var(--line); border-radius: 999px;
  background: transparent; color: var(--ink-3); cursor: pointer;
  font-size: 0.74rem;
}
.msk__all:hover { border-color: var(--teal); color: var(--teal); }
.msk__all[data-shown="1"] { border-color: var(--teal); color: var(--teal); }

/* THE EDIT FORM. The cover sits over the input; the input keeps its real
   value underneath so Save writes what was always there. */
.msk-wrap { position: relative; }
.msk-cover {
  position: absolute; inset: 0; width: 100%;
  padding: 0 2rem 0 0.75rem; text-align: left;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); color: var(--ink-2); cursor: pointer;
  font: inherit; font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
}
.msk-cover:hover { border-color: var(--teal); }
.msk-wrap__eye {
  position: absolute; right: 0.4rem; top: 50%; transform: translateY(-50%);
  z-index: 2;
}
.msk-wrap--shown input { border-color: var(--teal); }
.msk-wrap--empty .msk-wrap__eye { display: none; }

/* ── OFFER LETTERS ──────────────────────────────────────────────────────
   The tracked offer: written here, answered by somebody who does not work
   here yet, and the salary on it becomes an appointment letter and a
   register row. See public/admin/offers/ and routes/offers.js. */

/* A section heading inside a form, between the who and the money. */
.sec { font-size: 0.86rem; font-weight: 700; letter-spacing: 0.02em;
  margin: 1.1rem 0 0.5rem; color: var(--ink-2); }

/* The running total under the breakup. Not a table: it is three lines at
   most, and a table of three lines reads as a form somebody must fill. */
.ofr-sum { display: grid; gap: 0.2rem; margin-top: 0.8rem;
  padding-top: 0.6rem; border-top: 1px solid var(--line); }
.ofr-sum div { display: flex; justify-content: space-between; gap: 0.6rem;
  font-size: 0.9rem; }
.ofr-sum span { color: var(--ink-3); }
.ofr-sum b { font-variant-numeric: tabular-nums; }

.ofr-table { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
.ofr-table th { text-align: left; font-size: 0.72rem; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ink-3); font-weight: 700;
  padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--line); }
.ofr-table td { padding: 0.55rem 0.6rem; border-bottom: 1px solid var(--line);
  vertical-align: top; }
.ofr-table tr:last-child td { border-bottom: 0; }

/* Where an offer has got to. Amber for a change asked for, because it is
   waiting on HR rather than on the candidate. */
.ofr-pill { display: inline-block; font-size: 0.7rem; font-weight: 700;
  padding: 0.12rem 0.5rem; border-radius: 999px; white-space: nowrap; }
.ofr-pill--ok { background: #E3F4E8; color: #1E6B3A; }
.ofr-pill--err { background: #FDECEA; color: #8E2820; }
.ofr-pill--warn { background: #FFF4DB; color: #7A5300; }
.ofr-pill--wait { background: #EEF0F1; color: #4A5357; }

/* What the candidate asked to be changed, in their words. Shown on the row
   rather than behind a click: it is the one thing on this screen that needs
   somebody to do something. */
.ofr-note { font-size: 0.74rem; color: var(--ink-2); margin-top: 0.25rem;
  max-width: 16rem; line-height: 1.4; }

.ofr-dead { font-size: 0.72rem; color: var(--err); margin-top: 0.15rem; }
