/* ===========================================================================
   Storyteller HR — stylesheet
   Plain CSS, no framework. One committed white palette: there is no dark
   mode, so a machine set to dark does not repaint the interface.

   Palette and metrics sampled from apple.com's computed styles: #1D1D1F on
   white, one blue for anything actionable, hairlines instead of boxes, and
   the 980px button radius Apple actually uses. White carries the page, the
   sidebar and the form fields; #F5F5F7 is kept for just three jobs — stat
   tiles, row hover, and the active sidebar row.

   Structure, in cascade order:
     1. font + tokens
     2. base elements
     3. shell, page furniture, components
     4. payslip document
     5. @media (max-width: 900px)  — sidebar becomes a drawer
     6. @media (max-width: 620px)  — phone scale + tables fold into cards
     7. @media print               — payslip only

   Every selector matches markup already produced by src/views/*. No template
   or script changes are needed.
   =========================================================================== */

@font-face {
  font-family: "Inter";
  src: url("/inter.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --accent:        #0071E3;
  /* Misnamed and kept only for the day it is corrected: this is LIGHTER
     than --accent, not darker. White on it reads at 4.32:1, under the
     4.5:1 small text needs, so nothing white should sit on it. Both
     places that did now use --tint-accent-ink, which really is darker. */
  --accent-dark:   #0077ED;
  --accent-soft:   #E8F1FD;
  --accent-text:   #ffffff;
  --link:          #0066CC;

  --bg:            #ffffff;
  --surface:       #ffffff;
  --surface-2:     #f5f5f7;
  --surface-3:     #fafafc;
  --sidebar-bg:    #ffffff;
  --sidebar-text:  #1d1d1f;
  --sidebar-muted: #86868b;

  --text:          #1d1d1f;
  --text-muted:    #6e6e73;
  /* The small uppercase labels: Set names, "RULES", table headings, the
     record's field names, the sidebar sections. Lighter than the body text but
     not as faint as Apple's #86868b, which measured 3.6:1 on white — under the
     4.5:1 that text this small wants. This reads 4.6 on white, so it still
     clears that mark, and stays a clear step lighter than --text-muted. */
  --text-faint:    #75757b;

  /* Secondary text: hints, subtitles, empty states, stat captions and the
     sign-in footer. One size for the lot — change it here and every one
     follows. Each rule keeps its own spacing and alignment, so any single one
     can still be given its own size later.
     Not included: form labels (--text-label), validation errors, and
     breadcrumbs — all of which are deliberately larger. Breadcrumbs are set
     where they are defined, since they are navigation rather than commentary. */
  --text-sm:       10px;

  /* Form labels — the name of a field, rather than an explanation beneath
     it. Kept separate from --text-sm, and deliberately larger: a label has to
     stay readable at a glance even when the helper text under it is small. */
  --text-label:    12px;

  --border:        #e8e8ed;
  --border-strong: #d2d2d7;

  /* Behind white text — buttons, and nothing else. */
  --good:          #16a34a;
  /* The same green as text, on a white page or on the pale green below.
     No longer used by the status labels — they are words on white now and
     take the darker --tint-good-ink. Still used for figures and totals. */
  --good-ink:      #1a9448;
  --good-bg:       #e8f5ea;
  /* WARNING IS RED NOW, not amber. Asked for outright, and these four are the
     whole of it: everything that was amber on this site came from here.

     The names are kept because they are jobs, not colours - warn is the tone
     for "this needs your attention", whatever colour that tone happens to
     wear. Each one points at the red already doing the same job for errors, so
     the two can never drift apart by half a shade.

     WHAT IT COSTS, said plainly: a warning and an error now look identical. A
     note saying "twelve rows were skipped" is the same colour as one saying
     "that failed", and the amber pill on somebody locked out is the same red as
     a rejected claim. The page can no longer say "look at this" in a voice
     softer than "this went wrong".

     The amber that was here, should it ever be wanted back:
       --warn #d15f00   --warn-bg #fef2e6   --warn-figure #d97706   --warn-ink #b05000

     Every one of them reads BETTER as red, which was not the reason for the
     change but is worth having. Measured against the background each actually
     sits on: the notice text goes from 3.56:1 to 4.17, the day count on white
     from 3.93 to 4.76, the badge lettering from 4.77 to 5.41, and the big
     figure on a stat tile from 2.93 to 4.37.

     That last one is worth a second look. The old note here claimed 3.19:1 for
     the figure and called it "above the line, with little to spare" - but 3.19
     was measured against WHITE, and a stat tile is not white, it is --surface-2.
     On the tile it actually stood at 2.93, under the 3:1 large text is asked
     for. It had been under the line the whole time and the note said otherwise. */
  --warn:          var(--bad);
  --warn-bg:       var(--bad-bg);
  /* The figure on a stat tile, matching the red tile beside it exactly. */
  --warn-figure:   var(--bad);
  /* Lettering on the pale red ground - the deeper red, same as a bad badge. */
  --warn-ink:      var(--tint-bad-ink);
  /* Waiting is not warning. A leave request nobody has answered yet is not
     a problem, it is an unfinished job — so it reads blue rather than
     amber, and "needs attention" is left to the things that really do.

     Chosen for distance rather than taste. As amber, Pending sat 31 apart
     from the red of Rejected, and to somebody red-green colour blind the
     two were 6 apart — the same colour, in practice. This is 96 apart, and
     110 apart for that reader. It is a true blue and not a blue-grey on
     purpose: Cancelled is already grey, and the slates that separate
     nicely from red land on top of it instead. */
  --waiting-ink:   #1D4E89;
  /* The pale ground under a waiting status label. The other four tones had
     a background already; this one was invented alongside the ink and never
     needed one, because the labels wore no container. #1D4E89 sits at 7.20:1
     on it, the widest margin of the five. */
  --waiting-bg:    #e8eef8;
  /* Approve Selected, at the head of a table, is solid green with white on it.

     THIS IS --good, AND WHITE ON IT IS 3.30:1. Small text is meant to stand
     4.5:1 from what it sits on. This one does not, and it is the only place
     in the app that does not.

     That is a deliberate choice, made in v0.70.2 after seeing the compliant
     greens on screen and finding them too dark. It was measured first, not
     missed. What it costs is legibility for weaker eyesight, dim screens and
     glare — the button is still findable by its shape and colour, but its
     lettering is the thinnest in the app.

     If it ever needs to be put right, the line is #158539 at 4.72:1 and
     nothing brighter clears it. Changing this one value is the whole fix. */
  --good-solid:       #16a34a;
  --good-solid-hover: #15803d;

  /* Reject Selected, beside Approve Selected at the head of a table.

     THIS IS NO LONGER --bad, and the difference is deliberate. It started as
     --bad #e11d2b — the red these buttons wore before they went tinted — and
     was taken one step darker by eye. The step is small: about 9% down in
     brightness, which is just past the point where a change is visible rather
     than imagined.

     They are separate variables now because the button was what wanted
     darkening, not the red itself. --bad still lettering the required-field
     marks, the figures on the red stat tiles and the outlined Delete button,
     all unchanged. Moving --bad would have quietly dragged those along.

     White on this reads 5.11:1, past the 4.5:1 small text is asked to stand —
     so unlike the green next to it, this button is readable. The pair looks
     like a set and is mismatched underneath. Worth knowing before anyone
     reasons from one to the other.

     The hover is --tint-bad-ink, the red the tinted Reject letters in. */
  --bad-solid:        #d81b28;
  --bad-solid-hover:  #c01824;
  --bad:           #e11d2b;
  --bad-bg:        #fdecec;

  /* The three coloured buttons — Approve, Reject, and the blue one that
     saves things — are a pale wash of their own colour rather than a solid
     block of it. These are the colour taken deeper, used for both the
     lettering and the edge. They have to be deeper: the bright originals
     above are pitched to be read on WHITE, and on their own pale wash the
     green managed only 2.93:1, which nobody can read at 12.5px. See the
     Buttons section for the rest of the reasoning and the figures.

     The originals are untouched and still doing their own jobs elsewhere:
     --accent on the nav badge and the picked day in a date picker, --good
     and --bad as figures and as badge lettering. */
  --tint-accent-ink:   #0062C4;
  --tint-accent-hover: #d7e6fb;
  --tint-good-ink:     #137535;
  --tint-good-hover:   #d8ecdd;
  --tint-bad-ink:      #c01824;
  --tint-bad-hover:    #fbdada;
  /* Cancel is filled too, so it hovers the same way rather than by a
     filter — a filter would drag its new edge down with the fill, and the
     other three hold their edges still. This is the grey at brightness
     .94, which is what the filter used to produce. */
  --neutral-hover:     #e6e6e8;

  --radius:        12px;
  --radius-sm:     9px;
  --radius-pill:   980px;
  /* Status labels. Deliberately nowhere near --radius-pill: the buttons are
     lozenges, and a status label that curved like one would be the same
     costume on a different kind of thing. Boxy enough to read as a field on
     the row, rounded enough not to look like a table cell with a fill. */
  --radius-badge:  4px;
  /* The room a dropdown keeps on its right for its arrow: the arrow itself,
     the gap it sits in from the edge, and the gap the text has on its left. */
  --select-arrow-gap: 28px;
  /* How far the arrow itself sits in from the right-hand edge. */
  --select-arrow-inset: 10px;
  --shadow:        0 1px 2px rgba(0, 0, 0, .03), 0 2px 10px rgba(0, 0, 0, .03);
  --glass:         rgba(255, 255, 255, .72);
  --hover:         rgba(0, 0, 0, .045);
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Inter", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: "Bahnschrift", ui-monospace, SFMono-Regular, "Lucida Console", "Courier New", monospace;
}

/* There is deliberately no prefers-color-scheme block. This interface is
   white by decision, so a machine set to dark mode must not repaint it. */

* { box-sizing: border-box; }

/* The browser hides [hidden] elements with a rule weaker than any class here,
   so a hidden .btn or .card would otherwise stay on screen. This makes the
   attribute mean what it says everywhere. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 13.5px;
  line-height: 1.5;
  letter-spacing: -.008em;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0 0 .3em; line-height: 1.22; font-weight: 600; letter-spacing: -.016em; }
h1 { font-size: 21.5px; }
h2 { font-size: 15.5px; }
h3 { font-size: 12.5px; }
p  { margin: 0 0 .7em; }
a  { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.muted      { color: var(--text-muted); font-size: var(--text-sm); margin: 0; }
.small      { font-size: 12px; }
.mono       { font-family: var(--mono); letter-spacing: 0; font-variant-numeric: tabular-nums; }
.align-right  { text-align: right; }
.align-center { text-align: center; }
.align-left   { text-align: left; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: var(--accent-text); padding: .6rem 1rem;
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0; }

/* ------------------------------- Shell ---------------------------------- */

.shell { display: flex; min-height: 100vh; }

.sidebar {
  /* Sized to the longest menu label ("Public Holidays") plus its icon, so the
     bar is no wider than it needs to be and the page gets the rest. */
  width: 198px;
  flex: 0 0 198px;
  background: var(--sidebar-bg);
  color: var(--sidebar-text);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

/* A ROW NOW, NOT A STACK: the marks and the company name keep their own stack
   inside .brand-stack, and the bell stands beside them on the right. Asked for.
   Everything the stack used to be told is told to .brand-stack below, unchanged
   — the numbers in it were tuned and none of them has moved. */
.brand {
  display: flex; flex-direction: row; align-items: center; gap: 6px;
  padding: 14px 14px 7px;
  border-bottom: 1px solid var(--border);
}
.brand-stack {
  display: flex; flex-direction: column; align-items: flex-start;
  /* THE GAP BETWEEN THE PRODUCT MARK AND THE CUSTOMER'S OWN NAME UNDER IT.
     6px. It was 3, which read as one thing broken over two lines rather than
     as two names stacked; it went to 8, and was then asked back a little.

     Enough to tell the two apart and not so much that they stop belonging
     together — they are still a stack, and the colour difference set below is
     the other half of saying so.

     Only ever seen with the bar opened out. Narrowed to a rail, the wordmark
     and the company name are both taken off the page and the square mark
     stands alone, so there is nothing for a gap to sit between. */
  gap: 6px;
  /* Takes whatever room is left once the bell beside it has its corner, so a
     long company name wraps rather than pushing the bell off the edge. */
  min-width: 0; flex: 1 1 auto;
}
/* The padding and the dividing line moved up to .brand, which is the row that
   now holds this stack and the bell. The note that came with them is worth
   keeping: THE RAIL STATES THE PADDING AGAIN further down this file, because
   the two need different room at the SIDES — 14px opened, 8px in a 62px rail.
   The top and bottom must stay in step across both, or the dividing line under
   the brand, and the whole menu beneath it, shifts the moment the rail opens
   under the pointer. */
/* The wordmark itself, as a picture. Sized by width alone: the height
   follows the picture's own proportions, so a redrawn logo of a different
   shape drops in without this number needing to change. */
.brand-logo {
  display: block; width: 94px; height: auto;
}
.brand-company {
  /* Dark grey rather than the near-black of the logo above it, so the two
     read as a stack: the product name first, the customer’s name under it. */
  font-size: 12px; color: #4a4a4f; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The square mark. Only ever shown in place of the wordmark, when the bar is
   narrowed to its icons — see the block at the foot of this file. */
.brand-mark { display: none; width: 26px; height: 26px; border-radius: 6px; }

/* Sign out, which grew an icon so that it still says something narrowed. */
.sign-out { display: flex; align-items: center; justify-content: center; gap: 7px; }
.sign-out-icon { display: none; }

.nav { flex: 1; padding: 10px 8px; }
.nav-section { margin-bottom: 14px; }
/* Not a heading. These four words group the menu, they do not begin a
   section of the page — and marked up as headings they came BEFORE the
   page's own title in the reading order, so anybody moving through a page
   by heading met four navigation labels before finding out what page they
   were on. The grouping is still announced, through the group label on the
   section itself. Same size, same colour, same place. */
.nav-section-label {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-faint); margin: 0 0 4px 8px; font-weight: 600;
}
.nav-link {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 9px; margin-bottom: 1px;
  border-radius: 8px;
  color: var(--sidebar-text); font-size: 12px;
  text-decoration: none;
}
.nav-link:hover { background: var(--hover); text-decoration: none; color: var(--text); }
/* The sidebar is white, so a white pill would be invisible: the active row
   takes the grey instead and needs no shadow to lift it. */
.nav-link.is-active {
  background: var(--surface-2); color: var(--text); font-weight: 600;
}
.nav-icon { display: grid; place-items: center; flex: 0 0 18px; color: var(--text-muted); }
.nav-link.is-active .nav-icon { color: var(--accent); }
.nav-label { flex: 1; }
.nav-badge {
  background: var(--accent); color: var(--accent-text);
  font-size: 10px; font-weight: 600;
  border-radius: var(--radius-pill);
  min-width: 18px; text-align: center;
  /* The digit sat a pixel and a half high in the pill. Text is centred by the
     font's own metrics, which reserve room under the baseline for the tails of
     letters like g and y — room a digit never uses, so all of it fell below
     and none above. Giving the line the whole height of the pill spreads the
     spare space evenly around it instead. The padding moves to the line. */
  line-height: 17px; padding: 0 6px;
}
.nav-link.is-active .nav-badge { background: var(--accent); }

.sidebar-foot {
  padding: 10px 12px 12px;
  border-top: 1px solid var(--border);
}
.who { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
.avatar {
  width: 27px; height: 27px; flex: 0 0 27px; border-radius: 50%;
  background: var(--surface-2); color: var(--text-muted);
  display: grid; place-items: center; font-size: 10.5px; font-weight: 600;
}
.who-text { display: flex; flex-direction: column; min-width: 0; }
.who-text strong {
  font-size: 12px; color: var(--text); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.who-text small {
  font-size: 11px; color: var(--sidebar-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.content { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  display: none;
  align-items: center; gap: 9px;
  height: 44px; padding: 0 12px;
  background: var(--glass);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 20;
}
.topbar-title { font-weight: 600; font-size: 12.5px; }
.menu-toggle {
  background: none; border: 1px solid var(--border-strong); color: var(--text);
  border-radius: 8px; padding: 4px 6px; cursor: pointer;
  display: grid; place-items: center;
}
.menu-toggle:hover { background: var(--hover); }

main {
  flex: 1;
  padding: 22px 26px 34px;
  max-width: 1060px;
  width: 100%;
}

.foot {
  padding: 14px 26px 20px;
  font-size: 12px; color: var(--text-faint);
  display: flex; gap: 14px; flex-wrap: wrap; justify-content: space-between;
  border-top: 1px solid var(--border);
}
/* Which build this is. Quieter than the name beside it: it is there for the
   rare moment somebody has to report a problem, not to be read every day. */
.foot-version { font-variant-numeric: tabular-nums; letter-spacing: .01em; opacity: .8; }

/* ------------------------------ Page bits -------------------------------- */

.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-bottom: 18px;
}
.page-head .muted { font-size: 12px; }
/* align-items keeps a badge at its natural height instead of being stretched
   to match the buttons beside it. */
/* position, so the contents dropdown can hang from the right-hand end of it. */
.page-actions {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  position: relative;
}

/* The buttons in the top right of a page are solid blue with white on them,
   and they are the only ones left on the site that are.

   That is the point of them rather than an inconsistency. Everything inside
   a page — Save, Approve, Reject — is now a pale wash of its colour, which
   makes those buttons quiet and even-weighted. This is the one place a page
   is allowed to raise its voice: the two or three things you came to this
   page to start. Apply for leave. Add employee. New payslip.

   Written here rather than as a tone on each button because it is a fact
   about WHERE a button is, not about what it does. A page header added
   tomorrow gets it without anybody remembering to ask.

   The cost of that convenience: this cannot tell one blue button from
   another. linkButton defaults to the secondary tone, so a "Cancel" written
   the ordinary way would go solid blue along with "Add employee". The one
   place that happens — the employee form header — asks for the grey tone
   outright, and says why at the line. Anything put up here that should NOT
   shout needs the same treatment.

   Deliberately narrow: only the two blue tones. Grey Cancels, red Deletes
   and plain ghost buttons are left exactly as they are, and a status badge
   sitting up here is not a .btn at all.

   White on this blue reads at 4.70:1, over the 4.5:1 small text needs. The
   hover deliberately does NOT use --accent-dark: despite the name that
   colour is LIGHTER than --accent, and white on it falls to 4.32:1, under
   the line. It darkens to --tint-accent-ink instead, which is a real step
   down and reads at 5.93:1. */
/* The blue buttons at the head of a card — Edit, Save, Add Payroll Element and
   Mark Selected as Paid, twelve of them across the app.

   Solid --accent with white on it, the same blue a page-header button wears.
   White on it is 4.70:1, past the 4.5:1 small text is asked to stand.

   ONE RULE FOR ALL THREE COLOURS NOW. Green, red and blue in a card header are
   each picked out by where the button sits, and the buttons inside the table
   rows below are left tinted. For a while the blue was picked out by a class
   instead, because only one blue button was meant to be solid and eleven were
   meant to stay pale; that is no longer true, so the class is gone and this
   reads like its neighbours.

   What this rule now says, and it is worth saying out loud because it decides
   the look of anything added later: THE MAIN ACTION AT THE TOP OF A CARD IS A
   SOLID BLOCK OF COLOUR. A new blue button put in a card header will be solid
   without anybody asking for it. That is the intent, not an accident.

   Secondary buttons in the same headers stay pale — Show, Add, Review all, All
   leave, All claims — which is what keeps a card header from being a row of
   competing blocks. */
/* .rule-set-buttons is here for the same reason: it IS a card header, drawn
   by hand rather than by card(), and the Edit/Save button in it is the main
   action of that Set. Duplicate and Remove beside it stay pale, which is what
   keeps the row from being three competing blocks.

   .filters-tight is the third: a one-line settings form inside a card body —
   "Approve Claims Automatically", "Publish Payslips Automatically" and three
   others just like them. Two dropdowns and a Save, and the Save is the whole
   point of the card. It sits low rather than in the header only because the
   form is short enough to fit on one line, which is a fact about the layout
   and not about how important the button is.

   Narrow on purpose. Only .filters-tight of the .filters rows, and only the
   blue tone, so the small grey "Show" on the three GET filter forms written
   the same way stays grey.

   This paragraph used to name the Upload button on an employee’s documents as
   the thing deliberately left out. It is in now — .card-add, below — because it
   was asked for. The reasoning that excluded it was about not sweeping in every
   .filters row at once, and that still holds: .card-add is one form in one
   card, not a shape that recurs. */
/* .element-save is the fourth, and the plainest case of the lot: one button, on
   its own, at the foot of the card that holds every Rules and Actions Set. It
   is what saves the payroll element — "Save changes", or "Create element" on a
   new one. It sits at the bottom rather than the top only because it comes
   after the thing it saves. */
/* .card-save is the fifth, and it is .element-save's case again on another
   page: "Create draft payslips", alone at the foot of the card that raises a
   month of payslips. Two boxes above it and one button below, and the button
   is the entire point of the card — it is what makes the payslips.

   Named for the shape rather than the page, because that is what decides it:
   one primary button, on its own, at the foot of a card, doing the thing the
   card exists to do. .element-save is the same idea under a name that only
   fits payroll elements; the two could be one class, which would mean touching
   the payroll settings page, so they are left as two for now. */
/* #leave-request-form is the one picked out by the form it is in rather than
   by a class. "Submit request", at the foot of the
   leave form: one primary button, on its own, doing the thing the form exists
   to do — the same shape as .element-save and .card-save above.

   Scoped to the one form on purpose. The obvious selector, .form-actions
   .btn-primary, would have taken thirteen buttons across seven pages with it,
   sign-in among them. Narrowing by id follows what .filters .field:has(
   #residency_name) does further down: name the one control you mean.

   .auth-card is the seventh, and sign-in is no longer the exception above: a
   standalone card on an empty page, holding the one action the page exists
   for. It covers all three of those pages — Sign in, Change password, and the
   "Go to sign in" button on a refusal shown to somebody signed out — because
   they are the same control in the same place, and one of the three wearing a
   different blue would read as a fault rather than a distinction. */
/* .main-action is the eleven one-off rows above, under one name at last.

   THEY WERE NOT ALL THE SAME THING, WHICH IS WHY ONLY SOME OF THEM WENT.
   Five of the names below cover many buttons each and stay listed by name:
   .card-actions is the head of every card drawn by card(); .rule-set-buttons
   is a card head drawn by hand; .filters-tight is a one-line settings form and
   there are five of those; .auth-card covers three signed-out pages;
   .public-bar is the site header. Each says something about a PLACE, and the
   rule that the main action there is solid follows from the place.

   The rest were one row apiece, named one at a time as each was asked for, and
   every one of them is the same shape: one primary button, alone at the foot of
   a card or a form, doing the thing that card or form exists to do. Nine rows
   wore seven names for one idea. They all carry .main-action now, and the seven
   selectors that listed them here are this one.

   NOTHING WAS RENAMED. .card-add carries twenty other rules in this file and
   .element-save eight — those classes do layout, and only incidentally sat
   beside a blue button. Adding a name is safe; taking one away would have been
   a different and much larger change.

   A twelfth button of this shape now needs .main-action on its row and no edit
   here at all. */
.card-actions .btn-primary,
.rule-set-buttons .btn-primary,
.filters-tight .btn-primary,
.auth-card .btn-primary,
.public-bar .btn-primary,
.main-action .btn-primary {
  background: var(--accent);
  color: var(--accent-text);
  border-color: var(--accent);
}
.card-actions .btn-primary:hover,
.rule-set-buttons .btn-primary:hover,
.filters-tight .btn-primary:hover,
.auth-card .btn-primary:hover,
.public-bar .btn-primary:hover,
.main-action .btn-primary:hover {
  background: var(--tint-accent-ink);
  border-color: var(--tint-accent-ink);
}

.page-actions .btn-primary, .page-actions .btn-secondary {
  background: var(--accent); color: var(--accent-text);
  border-color: var(--accent);
}
.page-actions .btn-primary:hover, .page-actions .btn-secondary:hover {
  background: var(--tint-accent-ink); border-color: var(--tint-accent-ink);
}
/* EXCEPT THE ONES MARKED QUIET. A page heading fills every button in it,
   which is right where there is one thing to do on the page and wrong where
   there are three: Import, Export CSV and Add employee side by side as three
   solid blue blocks say nothing about which is the one you came for.

   These two go back to what a secondary button is everywhere else - blue
   lettering, white behind it, a grey outline. Asked for. The Add button
   beside them stays solid and is now the only filled thing in the row.

   Written out again here rather than left to the rule above, which is the
   arrangement where one gets changed and the other forgotten. A check now
   compares the two rules against each other, so they cannot drift apart.

   Two classes plus the row, so it beats the rule above wherever it sits in
   this file. */
.page-actions .btn-secondary.btn-quiet {
  background: transparent; color: var(--accent); border-color: var(--border-strong);
}
.page-actions .btn-secondary.btn-quiet:hover {
  background: var(--surface-2); border-color: var(--border-strong);
}

.breadcrumb { font-size: 12px; color: var(--text-muted); margin-bottom: 9px; }
.breadcrumb .sep { margin: 0 5px; opacity: .45; }
.breadcrumb .current { color: var(--text); }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 16px;
  overflow: hidden;
}

.card-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
  padding: 13px 17px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.card-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* The heading is a row: words on the left, buttons on the right. A long
   subtitle used to push the buttons onto a line of their own, because a text
   block will not shrink below its longest unbreakable line unless it is told
   it may. min-width: 0 is that permission — the words rewrap into the space
   left over and the buttons stay in the corner.

   Wrapping has to be off for that to happen at all: a wrapping row is split
   into lines by how big things WANT to be, before anything is asked to
   shrink — so the buttons were pushed to their own line and the permission
   above never came into it.

   Above phone width only: on a narrow screen there is no space to take, so
   wrapping onto a second line is the right answer there. */
@media (min-width: 621px) {
  .card-head { flex-wrap: nowrap; }
  .card-head > :first-child { flex: 1 1 auto; min-width: 0; }
  .card-head > .card-actions {
    flex: 0 0 auto;
    /* Centred between the top of the card and the line under the heading.
       The heading lines its contents up at the top, which is right for the
       words — a title should sit on the first line, not float. A button has
       no first line to sit on, so it is centred against the block instead. */
    align-self: center;
  }
  /* Except a card whose heading IS its field — that one is a column. */
  .card-head.card-head-field { flex-wrap: wrap; }

  /* A heading with a title and nothing else: line it up down the middle.

     A title with a description under it belongs at the top — the two read
     downwards and centring the pair would leave the title floating in the
     middle of its own text. A title on its own has nothing to read down to,
     and sits beside a button that is taller than it is, so lining them up
     at the top left the title looking six pixels high of the button.

     Asked of the markup rather than answered by a class the page sets: the
     heading either has a description or it does not, and that is already
     there to be seen. A class saying so would be the same fact written
     twice, and two copies of a fact drift apart.

     card-head-field is excluded because it is not a row of title-and-buttons
     at all — it is a label with its input beneath, stacked. */
  .card-head:not(.card-head-field):not(:has(p.muted)) { align-items: center; }
  /* The title keeps a gap under itself for the description to sit in. With no
     description that gap is just dead space, and it pushed the title two
     pixels above the middle of its own block. */
  .card-head:not(.card-head-field):not(:has(p.muted)) h2 { margin-bottom: 0; }
}
/* One height for everything standing in a card's heading — dropdowns, small
   buttons and ordinary ones alike — so a row lines up top and bottom instead
   of each control being as tall as its own component happens to be. The number
   is the ordinary button's height, which was already the tallest of them. */
.card-actions .btn,
.card-actions select { min-height: var(--card-action-height); }

/* A chooser standing in a card heading instead of a row of filters - the Team
   Calendar, where the month IS the card, so a Previous/Next pair would have
   meant six presses to reach a month half a year away.

   The width lives here because the filter row only sets one for boxes inside
   itself, and the dressed-up month box is display: block - given no width it
   would stretch the whole heading. Wide enough for "September 2026". */
/* Wrapping, and a box that may shrink: on a phone the chooser, Show and This
   month are wider than the card, and without both the last of them hung off
   the right edge with no way to reach it. */
.card-actions-form { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Wide enough for the longest thing it can ever hold and no wider. "September
   2026" measures 97.7px at this size, the placeholder 95.2 — plus 10 of padding
   on the left, 36 on the right for the calendar button, and the border. 150
   leaves a few pixels spare; 186, which it was, left forty. */
.card-actions-form .datefield { width: 150px; max-width: 100%; }
.card-actions-form input { min-height: var(--card-action-height); }

/* A card that is a single field: the heading is the label, with the box
   beneath it, so the name is not written twice. */
.card-head-field { flex-direction: column; align-items: stretch; gap: 7px; }
.card-head-label {
  margin: 0;
  font-size: 15.5px; font-weight: 600; letter-spacing: -.016em; color: var(--text);
}
.card-head-label .req { color: var(--bad); margin-left: 2px; }
.card-body { padding: 17px; }
.card-body > :last-child { margin-bottom: 0; }

.grid { display: grid; gap: 14px; margin-bottom: 16px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }

/* Inside a grid the gap already provides the spacing, so a card must not add
   its own bottom margin on top of it. */
.grid > .card { margin-bottom: 0; }

/* --------------------------- Section dividers ---------------------------- */
/* The dashboard is two halves — what other people are waiting on, and what is
   yours. Cards alone gave no sign where one stopped and the other began, so
   the page read as one long run of boxes.

   Deliberately NOT sized like a card heading. A label the same weight as the
   cards under it competes with them for the eye; this one is small, quiet and
   spaced out, and the hairline running off to the right is what actually does
   the dividing.

   The line is drawn by the divider itself rather than bordered onto the word,
   so it fills whatever width is left over however long the word happens to
   be. */
.section-head {
  display: flex; align-items: center; gap: 11px;
  margin: 26px 0 13px;
}
.section-head h2 {
  flex: 0 0 auto; margin: 0;
  /* Fifteen, asked for. It went to twelve along with the payslip section
     headings, which are a different rule and stay at twelve — the two are no
     longer the same size, which is a choice rather than an oversight. */
  font-size: 15px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-faint);
}
.section-head::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--border);
}
/* The first one follows the page heading, which has already left a gap of its
   own. Without this the two gaps stack and the page opens on a hole.

   TWO CLASSES ON PURPOSE. One class against one class is settled by which was
   written last in this file, and the narrow-screen block near the bottom sets
   .section-head margins again — so a single-class rule up here lost to it and
   the first divider got the full gap back on a phone. This has happened twice
   before in this file (the month arrows, both times); the shape that does not
   come back is the one that wins on its own merits. */
.section-head.section-head-first { margin-top: 2px; }

/* ------------------------------- Stats ----------------------------------- */
/* The tone colours the figure itself rather than a bar down the left edge —
   same information, far less furniture. */

.stat {
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 13px 15px;
  display: flex; flex-direction: column; gap: 2px;
}
.stat-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; }
.stat-value {
  font-size: 21px; font-weight: 600; line-height: 1.12; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.stat-hint  { font-size: var(--text-sm); color: var(--text-muted); }
.stat-good .stat-value { color: var(--good-ink); }
.stat-warn .stat-value { color: var(--warn-figure); }
/* Money sitting in a queue is not money at risk. This follows the Pending
   label rather than the warning red beside it, so one colour means one thing
   on the page. It is also a great deal easier to read: 8.39:1 against the
   4.37 --warn-figure now manages on the same tile. */
.stat-waiting .stat-value { color: var(--waiting-ink); }
.stat-bad  .stat-value { color: var(--bad); }
/* .stat-neutral is intentionally unstyled: it is statTile()'s default tone and
   means "no state to report", so the figure keeps the normal text colour. */

/* ------------------------------- Tables ---------------------------------- */

.table-wrap { overflow-x: auto; margin: 0 -17px -17px; }
/* A table at the top of a card sits flush against the heading above it: the
   body's padding is taken back so there is no band of empty space between the
   card head and the column names.

   THE SECOND LINE IS THE SAME TABLE WITH THE LOCK WRAPPER ROUND IT. Cards
   that shut until Edit is pressed put a plain div round their contents to
   carry that, which stopped the table being a direct child - so the rule
   missed, the padding stayed, and 17px of nothing sat above the column names.
   It read as headers that were far too tall. Public Holidays and Residency
   Status Options on Settings, and the Login and Leave cards on an employee
   record, all had it.

   THE THIRD LINE is the same again with a form round the lot - the Leave
   Entitlement card, where the lock cannot be the form's first child because
   the hidden security field comes before it. That field draws nothing, so the
   table is still the first thing anybody sees. */
.card-body > .table-wrap:first-child,
.card-body > [data-table-lock]:first-child > .table-wrap:first-child,
.card-body > form:first-child > [data-table-lock] > .table-wrap:first-child { margin-top: -17px; }
table { width: 100%; border-collapse: collapse; font-size: 12px; }
thead th {
  text-align: left; font-weight: 600; font-size: 10.5px;
  text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint);
  padding: 9px 17px; border-bottom: 1px solid var(--border-strong);
  background: transparent; white-space: nowrap;
}
tbody td { padding: 10px 17px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--surface-3); }
td.align-right, th.align-right { text-align: right; font-variant-numeric: tabular-nums; }
td.align-center, th.align-center { text-align: center; }
/* "Nothing here yet." Two shapes wear this: a paragraph sitting in a card
   body, and a row of a table that has no rows.

   The paragraph needs no side padding - the card body is already holding it
   in. A TABLE CELL IS NOT, and this was overriding the padding every other
   cell in the table gets. The message sat hard against the card's left edge,
   17px adrift of every other word in the table, and squeezed to two thirds
   the height of a real row. It read as text that had been dropped in
   unformatted. */
.empty { color: var(--text-muted); padding: 4px 0; margin: 0; font-size: var(--text-sm); }
td.empty { padding: 10px 17px; }

.table-total td { border-top: 1px solid var(--border-strong); font-weight: 600; }

/* A second scrollbar for a table too wide for the screen.
   The real one lives at the bottom of .table-wrap - which, on a table with
   many rows, sits far down the page. Reaching it meant scrolling all the way
   down first. This floats above the page instead, follows whichever wide
   table is currently on screen, and is drawn and positioned by app.js -
   these rules only say what it looks like once it is there.

   Hidden by default (opacity 0, no pointer events) rather than with `hidden`,
   so the fade in and out is the thing that is seen, not a sudden appearance. */
.scroll-strip {
  position: fixed; z-index: 20; bottom: 12px;
  display: flex; align-items: center; gap: 4px;
  padding: 7px 8px;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); box-shadow: var(--shadow);
  opacity: 0; transform: translateY(6px); pointer-events: none;
  transition: opacity .15s ease, transform .15s ease;
}
.scroll-strip.is-shown { opacity: 1; transform: translateY(0); pointer-events: auto; }
.scroll-strip-track {
  flex: 1 1 auto; min-width: 0; height: 6px; border-radius: var(--radius-pill);
  background: var(--surface-2); position: relative; cursor: pointer;
}
/* Grey rather than the accent blue, on purpose: this is chrome for reading a
   table, not a call to press something, and blue on a page already using blue
   for its real buttons made the scrollbar read as another one of them.

   THE SAME GREY AT REST AS ON A MOUSEOVER, ASKED FOR AFTER TRYING A LIGHTER
   RESTING SHADE that a mouseover then had to darken away from — the
   lightening and the darkening together read as a different, heavier control
   turning up rather than the same one there all along. Set once, on the rest
   rule, so a mouseover has nothing left to change and the two cannot drift
   apart: --text-muted, this file's usual quiet grey for text, visible enough
   against the track behind it (4.7:1) without being the app's near-black. */
.scroll-strip-thumb {
  position: absolute; top: 0; left: 0; height: 6px;
  border-radius: var(--radius-pill); background: var(--text-muted); cursor: grab;
}
/* Picking it up to actually drag is treated as more deliberate than merely
   passing the mouse over it, and gets the app's own near-black in return -
   the one colour on this control that still changes. */
.scroll-strip-thumb:active { background: var(--text); cursor: grabbing; }
.scroll-strip-thumb:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
}
/* The two step buttons either end of the track - the same grey as the thumb
   at rest, since together they are one control. No border and no fill of
   their own: on a strip this slight, a button-shaped box at each end would
   outweigh the thing it is a button for. */
.scroll-strip-arrow {
  flex: 0 0 auto; width: 20px; height: 20px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--text-muted); cursor: pointer;
}
.scroll-strip-arrow:hover { background: var(--surface-2); color: var(--text); }
.scroll-strip-arrow:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.scroll-strip-arrow:disabled { opacity: .35; cursor: default; background: none; }
.scroll-strip-arrow svg { width: 14px; height: 14px; }
/* Printed pages are never wider than themselves. */
@media print { .scroll-strip { display: none; } }

/* --- Waiting for Save -----------------------------------------------------
   On the settings cards that lock — residency statuses, public holidays, leave
   types — Remove and Add no longer act the moment they are pressed. They wait
   for Save, so the page has to show the difference between a row that is gone
   and one that is going.

   A row on its way out is faded and ruled through rather than taken away. Taken
   away, there would be nothing left to press to change your mind, and the table
   would jump under the pointer. Ruled through, it is plainly still there and
   plainly not staying. */
tr[data-going] > td { opacity: .45; text-decoration: line-through; }
/* Except the last cell, which holds the button that undoes it: a control ruled
   through reads as one that no longer works, and this is the one that does. */
tr[data-going] > td:last-child { opacity: 1; text-decoration: none; }

/* And the other half: names typed into the Add box, listed under it until Save
   picks them up. Nothing is a row in the table yet — it has no usage figures
   and nothing to rename — so it is a plain list rather than a pretend row. */
.staged-adds { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.staged-adds li {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 6px 3px 10px;
  border: 1px dashed var(--border-strong); border-radius: var(--radius-pill);
  background: var(--surface-2); font-size: 12px;
}
.staged-add-drop {
  border: none; background: none; cursor: pointer; padding: 0 2px;
  color: var(--text-muted); font-size: 14px; line-height: 1;
}
.staged-add-drop:hover { color: var(--bad); }
.staged-add-drop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- The bell, under the logo ----------------------------------------------
   Where it is, and why it is there rather than in the menu below it: this is
   the only part of the page that is always on screen. On a desktop the sidebar
   is a rail of icons that never goes away, the bar across the top exists only
   on a narrow screen, and the top right of the page is where each page puts
   its own buttons. A count nobody can see is not a count.

   Drawn like a nav link, because it is one — same height, same corner, same
   hover — so it belongs to the list under it rather than reading as a stray
   button somebody stuck above it. The one difference is the count. */
/* A SMALL SQUARE, to sit beside the square the logo is drawn from rather than
   compete with it. Squared to the same corner as the mark — not a circle,
   which would read as a different kind of thing next to it — and deliberately
   smaller: the logo is the name of the product and this is a button next to
   it, so the two should not look like a pair. */
.brand-bell {
  position: relative;
  flex: 0 0 auto;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: var(--radius-sm);
  color: var(--text-muted); text-decoration: none;
}
.brand-bell:hover { background: var(--hover); color: var(--text); }
.brand-bell:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Unread says so twice: the bell takes the app's blue AND the count appears.
   Colour on its own is no use to somebody who cannot see the difference, and a
   small figure on its own is easy to miss. */
.brand-bell.has-unread { color: var(--accent); }
/* Smaller than the 18px the menu icons use, because this sits beside the logo
   rather than in a row of its own. */
.brand-bell svg { display: block; width: 15px; height: 15px; }
/* On the shoulder, half off the corner, which is where a count goes. */
.brand-bell-count {
  position: absolute; top: -3px; right: -4px;
  min-width: 14px; height: 14px; padding: 0 3px;
  display: grid; place-items: center;
  border-radius: var(--radius-pill);
  background: var(--bad-solid); color: #fff;
  font-size: 9px; font-weight: 700; line-height: 1;
  /* Lifted off whatever is behind it, so the digit never sits half on the
     icon and half on the page. */
  box-shadow: 0 0 0 2px var(--surface);
}

/* --- The notifications list ------------------------------------------------
   Things that have happened to one person, newest first. Deliberately plainer
   than a table: every row is one sentence and a time, and a table's headings
   would be three words explaining two.

   A row nobody had read when the page opened keeps a mark down its left edge
   for that one viewing. It is gone next time — reading them IS having read
   them — so this is the only chance to show which ones were new, and losing
   that would make the page identical whether you had been away five minutes or
   five weeks. */
.notice-list { list-style: none; margin: 0; padding: 0; }
.notice-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 11px 0 11px 12px;
  border-bottom: 1px solid var(--border);
  border-left: 3px solid transparent;
}
.notice-row:last-child { border-bottom: none; }
.notice-row.is-new { border-left-color: var(--accent); background: var(--surface-2); }
.notice-row-what { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.notice-row-title { color: var(--text); font-weight: 600; }
a.notice-row-title { color: var(--accent); text-decoration: none; }
a.notice-row-title:hover { text-decoration: underline; }
/* The reason a request was turned down. Quieter than the line above it, and
   allowed to wrap: it is a sentence somebody wrote, not a label. */
.notice-row-body { color: var(--text-muted); font-size: 12px; }
/* Never wraps, never shrinks: it is four words at most and it is what the eye
   runs down the right-hand side to find. */
.notice-row-when {
  color: var(--text-faint); font-size: 11.5px; white-space: nowrap; flex: 0 0 auto;
}

/* ------------------------------- Badges ---------------------------------- */

/* --- Status labels ---------------------------------------------------------
   A note about the row, not an object in it.

   These were filled pills, and the buttons beside them are filled pills too —
   same wash, same shape, a few pixels apart in size. In an Actions column
   sitting next to a Status column that meant the thing telling you what IS
   and the thing you PRESS looked like the same kind of object. Taking the
   container away leaves the buttons as the only lozenges on the page.

   Small capitals rather than sentence case because the eye needs something to
   mark this out as metadata once the shape has gone. The letter-spacing is
   not decoration: capitals set tight are markedly harder to read.

   The colours are darker than the ones the pills wore. Those were pitched to
   sit on their own pale backgrounds and manage 3.9:1 on white; these clear
   5.2:1 or better, so the old "the colour only agrees with the word" excuse
   is no longer needed for them. */
/* THE CONTAINER IS BACK, AND IT IS NOT A PILL.

   The note above records why the filled pills went: beside a filled, rounded
   Approve button they were the same shape and wash a few pixels apart, so the
   thing telling you what IS and the thing you PRESS looked alike. Taking the
   container away fixed that by removing one of the two shapes.

   A box fixes it by making the two shapes different instead. Buttons keep
   --radius-pill at 980px; these take --radius-badge at 4px. Nothing on the
   page is shaped halfway between, so the pair cannot be confused at a glance.

   THE INKS DID NOT CHANGE, AND THAT WAS CHECKED RATHER THAN ASSUMED. They
   were darkened when the pills went, pitched for white. A pale tint is darker
   than white, so every one of them loses a little: measured on its own
   background each still clears 4.5:1 — good 5.16, warn 5.41, waiting 7.20,
   bad 5.41, muted 4.66. Warn and bad read the same because warn IS bad now:
   the amber was retired and both wear the same red. Muted is the thin one and
   is why the tints are as pale as they are; a deeper fill would put it under
   the line.

   The small capitals stay. They marked these out as metadata when the shape
   was gone, and they go on doing it now the shape says something different
   from what a button says. */
.badge {
  /* Still inline-flex: a status label often sits in a row of buttons, where
     it would otherwise be baseline-aligned against things much taller. */
  display: inline-flex; align-items: center;
  font-size: 9px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap;
  /* THE VERTICAL PADDING IS EVEN, AND THAT IS THE CORRECTION.

     Measured inside the box, the capitals sit about 1px low: align-items
     centres the LINE box, and a line box keeps room under the baseline for
     descenders that all-caps text never uses.

     DO NOT “FIX” THAT. It was fixed once, with 1px above and 3px below, and
     the labels came out visibly worse. The badge is inline-flex aligned on its
     BASELINE, so it hangs a shade high on the row — and the caps sitting a
     shade low inside it cancel that out. Correcting one half of a
     self-cancelling pair doubles the error you can actually see.

     Measured ink-to-ink against the words beside it in the row, which is what
     the eye compares: even padding lands at -0.40px. 1px/3px made it -0.90px,
     and vertical-align:middle is worse again at -0.61px even, -1.61px lopsided.

     ACROSS is a real correction and stands: letter-spacing is added after the
     LAST letter too and paints nothing, so the box ran 0.72px wider on the
     right than the glyphs needed and the word sat 0.37px left of centre.
     Taking .08em back off the right padding cancels exactly what was added,
     and nothing else on the page depends on that gap. */
  padding: 2px calc(7px - .08em) 2px 7px; border-radius: var(--radius-badge);
}
/* CENTRING THE CAPITALS, WHERE THE BROWSER CAN DO IT PROPERLY.

   The note above explains why the vertical padding is even: the capitals sit a
   shade low inside the box, the box hangs a shade high on the row, and the two
   cancel out. That is a truce rather than a fix — both offsets are still there,
   and looking at a label on its own you can see the word sitting low.

   text-box trims the line box to the cap height at the top and the baseline at
   the bottom, throwing away the room kept under the baseline for descenders
   that ALL-CAPS text never has. That room is the whole cause: it is why
   centring the box does not centre the letters. With it gone, even padding
   centres the capitals themselves — measured, -0.35px becomes 0.08px.

   DISPLAY CHANGES WITH IT, AND HAS TO. On inline-flex the text is an anonymous
   flex item and the trim never reaches it: setting text-box on the badge did
   nothing whatsoever until the display changed. inline-block was measured
   against inline-flex first and came out identical on every figure, including
   how the badge sits on the row, so nothing is given up for the swap.

   THE PADDING GROWS TO PUT THE HEIGHT BACK. Trimming takes the box down to
   about 10.5px; 5.5px above and below returns it to 17.55px, which is the
   17.5px it has been all along, so nothing around it moves.

   Behind @supports because Firefox has not shipped this yet. Where it is
   missing the rule does not apply and the labels look exactly as they do now —
   the fallback is the current design, not a broken one. */
@supports (text-box: trim-both cap alphabetic) {
  .badge {
    display: inline-block;
    text-box: trim-both cap alphabetic;
    padding-top: 5.5px; padding-bottom: 5.5px;
  }
}

.badge-good  { color: var(--tint-good-ink); background: var(--good-bg); }    /* 5.16:1 on its own ground */
.badge-warn  { color: var(--warn-ink); background: var(--warn-bg); }         /* 5.41:1 — the same red as bad */
.badge-waiting { color: var(--waiting-ink); background: var(--waiting-bg); } /* 7.20:1 */
.badge-bad   { color: var(--tint-bad-ink); background: var(--bad-bg); }      /* 5.41:1 */
.badge-muted { color: var(--text-muted); background: var(--surface-2); }     /* 4.66:1 */
.badge-info  { color: var(--tint-accent-ink); background: var(--accent-soft); } /* 5.35:1 — the app's blue */

/* ------------------------------ Buttons ---------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 16px; border-radius: var(--radius-pill);
  border: 1px solid transparent; font-size: 12.5px; font-weight: 500;
  font-family: inherit; cursor: pointer; text-decoration: none; white-space: nowrap;
  letter-spacing: -.008em; line-height: 1.2;
  transition: background .15s ease, border-color .15s ease;
}
.btn:hover { text-decoration: none; }
/* --- The three tinted buttons --------------------------------------------
   Primary, Approve and Reject were solid blocks of colour carrying white
   lettering. They are now a pale wash of the same colour, with the colour
   itself as the lettering and as the edge.

   THERE IS NO EDGE ON THEM NOW. Asked for, and it is the seventh step of
   the six this note already describes: the border was walked down by eye
   over full strength, halfway, 30%, 22%, 15% and 12%, and at 12% the note
   below had already concluded it was decorative. Below about 1.15 from the
   fill there is nothing left to take away except the line itself, so that
   is what was taken.

   What this costs, said plainly. A pale fill on a white page is 1.14:1
   against it, so with nothing round it these are read by their FILL and
   their LETTERING alone. That was already true at 12% — the border was the
   last trace of a line rather than the thing doing the work — but it is now
   the whole of it. Somebody skimming a busy row sees four coloured labels
   where they used to see four coloured labels with a hairline. If they ever
   stop reading as things you can press, THIS is the change that did it, and
   the paragraph after next says exactly how to put it back.

   THE EDGE IS DECORATIVE AT THIS VALUE, and calling it anything else would
   be a fiction. It stands 1.18:1 (green), 1.18:1 (blue), 1.21:1 (red) and
   1.27:1 (grey) from the fill it surrounds, where 1.00 would be the same
   colour exactly. It is a suggestion of an edge — enough to catch the light
   at the corner of a pale shape, not enough to draw one.

   That is the intended look, arrived at deliberately and not by drift. What
   it means in practice is that these buttons are now read as buttons by
   their FILL and their LETTERING, and the border is the last trace of a
   line rather than the thing doing the work.

   Two things follow, and both are worth knowing before this is touched
   again:

   Going lower is not a smaller number, it is no border. Below about 1.15
   there is nothing left to reduce, and a border painted the same colour as
   its own fill is a mistake rather than a style. That is why this ended in
   removal rather than in a seventh smaller number.

   And the lettering is now carrying the button on its own. That figure has
   not moved through any of this — 5.16:1 or better on every one — and it is
   the one that must never be traded for a softer look, because it is what
   decides whether the word can be read at all.

   The dial, for putting one back: 12% is where it was, 15% gives 1.23-1.35:1
   from the fill, 22% gives 1.53-1.72:1 against the page, 30% gives
   1.71-1.90:1, 50% gives 2.36-2.73:1, and roughly 70% is where they cross
   the 3:1 the rules ask of a control's outline. It was one number in four
   places below, and all four are now simply absent: every .btn already
   carries a 1px transparent border, so adding border-color back to these
   four rules is the whole of it and nothing moves size either way.

   The lettering is better than what it replaces, not merely acceptable. On
   its own wash the ink reads at 5.16:1 (green), 5.20:1 (blue) and 5.41:1
   (red), and nothing drops below 4.68:1 under the pointer — all clear of
   the 4.5:1 that 12.5px text needs. The solid versions were 3.30:1, 4.70:1
   and 4.76:1, so the green in particular was under the line all along.

   What this costs: these buttons no longer shout. On a row where Approve
   and Reject sit beside Delete and Cancel, all four now carry about the
   same weight, and only colour separates them. That was the trade accepted
   when this was chosen over dropping the solid buttons to 50% opacity —
   opacity fades the lettering along with the fill and lands at 1.8-2.3:1,
   which is not a quieter button, it is an unreadable one that also looks
   disabled.

   Going back is three rules: fill with --accent / --good / --bad, letter in
   white, drop the border-color. .btn-danger-solid keeps its name against
   that day; it means "the louder of the two reds" rather than a promise
   about how it is painted. */
.btn-primary {
  background: var(--accent-soft); color: var(--tint-accent-ink);
}
.btn-primary:hover { background: var(--tint-accent-hover); }
/* Blue lettering on the page itself, inside a grey outline.

   ONE FIGURE WORTH LEAVING HERE, since it was measured. This button has no
   fill — the page shows through it — so the outline is the only thing marking
   where it is, and the grey stands at 1.51:1 against the page where 3:1 is
   what a control's own boundary is asked to reach. An outline in the blue it
   letters in comes to 4.70:1. That was tried and reversed: it is louder than
   the grey, and these buttons are meant to recede beside the solid blue in a
   card heading. Recorded rather than argued — the numbers are here if the
   question comes round again. */
.btn-secondary { background: transparent; color: var(--accent); border-color: var(--border-strong); }
.btn-secondary:hover { background: var(--surface-2); }
/* A chevron either side of the month chooser, for stepping one month at a time
   without opening the panel.

   Squared off to the same corner as the month box they stand against, rather
   than left as the pill every other button is. At this width a pill IS a
   circle, and two circles either side of a rounded box read as a different
   kind of thing from it. Asked for, and it is right: these belong to the box,
   not to the row of buttons.

   WRITTEN AS .btn.btn-step, TWO CLASSES DEEP, ON PURPOSE. .btn sets padding:
   7px 16px and the narrow screen sets it again - and one class against one
   class is decided by which was written later in the file. Written as one, this
   lost to .btn above it, then lost again to the narrow screen below it: 28px of
   side padding in a 30px button left the chevron one pixel wide, and the arrows
   drew as empty circles. Two classes wins wherever it sits.

   ALL THE PADDING GOES, top and bottom as well as the sides. The width is
   fixed, or the chevron would sit in the middle of a button as wide as one
   holding a word - and with the padding gone the height falls back to the one
   height every control in a card heading shares, which is what the month box
   beside it stands at.

   Keeping the 7px top and bottom was what made the arrows grow taller than
   that box the moment the chevron went from 14px to 18: 18 and 7 and 7 and
   a border is 34, and that is more than the row is built on. */
.btn.btn-step {
  width: var(--card-action-height); min-width: var(--card-action-height); padding: 0;
  border-radius: var(--radius-sm);
  /* Black rather than the blue every other secondary button letters in. Asked
     for, and it suits what these are: blue on this site means "this takes you
     somewhere", and stepping a month does not take you anywhere — the calendar
     under it simply redraws. The chevron is drawn in currentColor, so this one
     line is the whole of it.

     Two classes again, for the reason set out above: .btn-secondary sets the
     colour and is one class, so a one-class rule here would be settled by which
     came later in the file rather than by which is more specific. */
  color: var(--text);
}
/* Delete: outlined, and staying outlined. Its hover used to fill with
   --bad-bg, which is now exactly what Reject wears standing still — and on
   the Claims page the two sit in the same row of buttons. It reddens its
   edge instead, so the filled one is always the one that decides a claim
   and the outlined one is always the one that destroys it. */
.btn-danger { background: transparent; color: var(--bad); border-color: var(--border-strong); }
.btn-danger:hover { background: var(--surface-2); border-color: var(--bad); }
/* THE ONE BUTTON THAT IS FILLED IN, not the whole family.

   Solid red with white lettering was asked for on "Delete Employee Record",
   and only on that. It was tried on .btn-danger itself first, which turned
   every Remove on the payroll item page into a red block — and red LETTERING
   on those was itself asked for earlier, and is held in place by a test. So
   this is written as its own class and worn by one button.

   Two classes deep, the way .btn.btn-step above is and for the same reason:
   .btn-danger sets a background one line up, and one class against one class
   is settled by whichever was written later in the file. Two beats one
   wherever either of them ends up sitting.

   --bad-solid rather than --bad, because that is the token that exists for
   this exact job: a red chosen to be read on with white lettering (5.11:1,
   and 6.18:1 once it darkens under the pointer). --bad is a shade lighter and
   is spoken for elsewhere — the asterisk on a required field letters in it. */
.btn-danger.btn-danger-fill {
  background: var(--bad-solid); color: #fff; border-color: var(--bad-solid);
}
.btn-danger.btn-danger-fill:hover {
  background: var(--bad-solid-hover); border-color: var(--bad-solid-hover);
}
.btn-success {
  background: var(--good-bg); color: var(--tint-good-ink);
}
.btn-success:hover { background: var(--tint-good-hover); }

/* Approve Selected — the green button at the head of a table, not the ones in
   its rows.

   It decides a whole batch at once, so it is the loudest thing in the card
   header rather than one of a pair on a single line. Written by WHERE it is,
   like the solid blue in a page header: .card-actions is the strip beside a
   card title, .cell-actions is a row, and the row buttons stay tinted.

   The green IS --good, chosen knowingly. See --good-solid at the top for what
   that costs and what the fix is if it is ever wanted back. In short: white on
   it is 3.30:1 where 4.5:1 is asked, and this is the one button in the app
   below the line.

   Worth saying plainly, because the history here is easy to misread: this is
   the exact pairing that sent every other button tinted. Those buttons should
   not follow it back. They letter in their own colour on a pale wash and read
   at 5.16:1 or better, and that is why the row Approve is written separately
   from this one. */
.card-actions .btn-success {
  background: var(--good-solid);
  color: #fff;
  border-color: var(--good-solid);
}
.card-actions .btn-success:hover {
  background: var(--good-solid-hover);
  border-color: var(--good-solid-hover);
}
/* Reject: the mirror of Approve above it, for where the two stand together
   and the outlined version made the pair look mismatched. */
.btn-danger-solid {
  background: var(--bad-bg); color: var(--tint-bad-ink);
}
.btn-danger-solid:hover { background: var(--tint-bad-hover); }

/* Reject Selected — the red button at the head of a table, not the ones in its
   rows. Written by WHERE it is, exactly as Approve Selected above: only the
   two bulk buttons sit in a .card-actions, and the six row Rejects do not.

   See --bad-solid at the top for the figures. The short version is that this
   one is readable and the green beside it is not, which is not a symmetry
   anybody should assume holds. */
/* .confirm-actions is here for a reason worth writing down: on the page that
   asks before something cannot be undone, the button that goes ahead was
   PALER than the Cancel beside it. Whatever else that page is, the thing you
   are being warned about should not be the quietest control on it. */
.card-actions .btn-danger-solid,
.confirm-actions .btn-danger-solid {
  background: var(--bad-solid);
  color: #fff;
  border-color: var(--bad-solid);
}
.card-actions .btn-danger-solid:hover,
.confirm-actions .btn-danger-solid:hover {
  background: var(--bad-solid-hover);
  border-color: var(--bad-solid-hover);
}
.btn-ghost { background: transparent; color: var(--text); border-color: var(--border-strong); }
.btn-ghost:hover { background: var(--hover); color: var(--text); }
/* Filled light grey with ordinary text on it — for Cancel, which is a way out
   rather than a decision or a way onward. Filled rather than outlined so it
   reads as a real button beside the green, red and blue ones, but in the one
   colour that says nothing about what happens next. The text reads at
   15.46:1 against the grey, which is the strongest pairing on the site.

   No edge, along with the three above it. It used to take the same 12% step
   from fill to lettering that they did, and because Cancel letters in
   near-black rather than a mid-toned colour that step landed a shade darker
   than theirs — which is worth remembering if one is ever put back: the
   same number does NOT give Cancel the same weight, and it looked heavier
   beside Approve at any value the coloured three were comfortable at.

   Nothing moved size. Every .btn already carries a 1px transparent border,
   so taking the colour off it changes no geometry — the button does not
   shrink and nothing beside it shifts. */
.btn-neutral {
  background: var(--surface-2); color: var(--text);
}
.btn-neutral:hover { background: var(--neutral-hover); }
.btn-block { width: 100%; }
.btn-sm { padding: 4px 12px; font-size: 12px; }
.btn:disabled { opacity: .4; cursor: not-allowed; }

.btn-row { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
.inline-form { display: inline; }

/* The popup a refused save opens. A native <dialog>, so the browser handles
   focus, Escape and keeping it on top. */
.error-popup {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 22px 24px;
  max-width: 420px;
  background: var(--surface);
  color: var(--text);
}
.error-popup h2 { margin: 0 0 8px; font-size: 17px; }
.error-popup p { margin: 0 0 16px; color: var(--text-muted); line-height: 1.5; }
.error-popup::backdrop { background: rgba(0, 0, 0, .32); }
/* Two buttons sharing one table cell. Inline-flex keeps the pair only as wide
   as its contents, so a right-aligned cell still pushes them to the edge. */
.cell-actions { display: inline-flex; align-items: center; gap: 6px; }

/* ------------------------------- Forms ----------------------------------- */

.field { margin-bottom: 13px; }
.field label {
  display: block; font-size: var(--text-label); font-weight: 500; color: var(--text-muted);
  margin-bottom: 4px;
}
.field .req { color: var(--bad); margin-left: 2px; }

/* A dropdown's own arrow is painted by the browser hard against the right-hand
   edge of the box, and no amount of padding will move it — padding only pushes
   the text away. So the browser's arrow is switched off and one of ours is
   drawn in its place, as a background image we can put where we like: in from
   the edge by --select-arrow-inset, with the text stopping short of it at
   --select-arrow-gap. The chevron is a data: URL rather than a file so that a
   dropdown never waits on a second request to look finished; its colour is
   written out because a background image cannot read a custom property.
   Written after the shared box style, whose `padding` shorthand would
   otherwise reset the room kept on the right. */

input[type=text], input[type=email], input[type=password], input[type=date],
input[type=number], input[type=month], input[type=search], select, textarea {
  width: 100%; padding: 7px 10px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  /* White, not tinted: on a white page a filled field reads as disabled. */
  background: var(--surface); color: var(--text);
  font-family: inherit; font-size: 12.5px; letter-spacing: -.008em;
  transition: border-color .15s ease, box-shadow .15s ease;
}
select {
  appearance: none; -webkit-appearance: none;
  padding-right: var(--select-arrow-gap); text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.2 5 4.8 9 1.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--select-arrow-inset) center;
  background-size: 10px 6px;
}
/* --- The list that drops down -------------------------------------------
   Until recently this was the one part of the site nobody could style: the
   popup was drawn by the operating system, not by the page, which is why it
   looked like Windows and everything around it looked like this. It can be
   opted out of that now, and dressed like the two popups we already draw
   ourselves — the calendar, and the type-to-search list.

   Both halves have to opt in: the box, and the popup.

   A browser that has not got this ignores the whole block and shows the
   ordinary dropdown it always did, working exactly as before. That is why
   nothing in here has to guard against being half-applied — and why it is
   safe to ship while one browser is still catching up. */
@supports (appearance: base-select) {
  select, ::picker(select) { appearance: base-select; }

  /* The box must not change. Opting in turns it into a button, which brings
     its own layout, so everything the shared field style gave it is put back
     by hand. */
  select {
    /* Centred rather than simply stacked, because the box is not always the
       height its own padding asks for. A dropdown standing in a card heading
       is pinned to the height of the buttons beside it, so the row lines up;
       the tight variant used there asks for less and is given the row's own
       height. Laid
       out as a block, the line of text stayed where the padding put it and the
       words sat high with a band of empty space beneath them. Centred, the
       spare pixels fall equally above and below, and the text sits in the
       middle of whatever height the row needs. */
    display: flex; align-items: center; width: 100%;
    padding: 7px var(--select-arrow-gap) 7px 10px;
    border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
    background-color: var(--surface); color: var(--text);
    font-family: inherit; font-size: 12.5px; letter-spacing: -.008em;
    text-align: left; white-space: nowrap; overflow: hidden;
    /* Set to the pixel: a button's line box is not a dropdown's, and left to
       itself the box came out two pixels taller than it was before. */
    line-height: 16px;
  }
  /* That same shrink also pulled a dropdown standing beside an ordinary text
     box shorter than it - noticeably so once the two sit in the same row of
     a form. Pinned back up to the text box's own height, but only for a
     dropdown inside an ordinary labelled field; the compact rows above
     (rule builder, table cells, tight filter bars) ask for less on purpose
     and are left alone. */
  .field select { min-height: 31px; }
  /* Our own chevron is already painted behind the box. The one this brings
     with it would sit on top of it. */
  select::picker-icon { display: none; }

  /* The popup itself: the same surface, border, corner and shadow as the
     calendar and the type-to-search list, so all three read as one family. */
  ::picker(select) {
    padding: 4px; margin-top: 5px;
    max-height: 260px; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: 0 4px 20px rgba(0, 0, 0, .1);
  }

  option {
    padding: 6px 10px; border-radius: var(--radius-sm);
    font-size: 12.5px; color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    cursor: pointer;
  }
  option:hover, option:focus { background: var(--surface-2); outline: none; }
  /* The one already chosen is marked by weight, the way the type-to-search
     list marks it — a tick would be a second way of saying the same thing. */
  option:checked { font-weight: 600; }
  option::checkmark { display: none; }
  /* A heading inside a long list — the platform's feature groups use these. */
  optgroup {
    padding: 6px 10px 2px; font-size: 10.5px; font-weight: 600;
    letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint);
  }

  /* Open reads like focused, because it is. */
  select:open {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
  }
}

/* A machine set to high contrast throws background images away, which would
   leave the dropdowns with no arrow at all. There, the browser's own comes
   back — cramped against the edge, but visible, which matters more. */
@media (forced-colors: active) {
  select { appearance: auto; -webkit-appearance: auto; background-image: none; }
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
textarea { resize: vertical; }

/* --- Page numbers ---------------------------------------------------------

   Under a table that holds more than one page of things. Every number is a
   plain link, so this works with no script running and a middle-click opens a
   page in a tab the way any other link does.

   The page you are on is not a link, because there is nowhere for it to go.
   It is told apart by weight and a filled background rather than colour
   alone. */
.pager {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  margin-top: 14px;
}
.pager-page, .pager-here {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; padding: 5px 8px;
  border-radius: var(--radius-sm); border: 1px solid transparent;
  font-size: 12.5px; line-height: 1.2;
}
.pager-page { color: var(--accent); text-decoration: none; border-color: var(--border-strong); }
.pager-page:hover { background: var(--hover); text-decoration: none; }
.pager-here { background: var(--accent); color: #fff; font-weight: 600; }
/* The numbers stepped over. Not a link and not read aloud - it is punctuation,
   not information. */
.pager-gap { padding: 5px 2px; color: var(--text-faint); }
/* --- The team calendar ----------------------------------------------------

   A month, seven days across. The grid is held to a minimum width and allowed
   to scroll sideways inside its wrapper rather than being rebuilt as a list on
   a phone: seven columns is what makes it a calendar, and a second layout is a
   second thing to keep right.

   No colour comes from the leave type. Each type carries one in the database,
   but the security policy blocks inline styles, so a stored colour cannot
   reach the page as one. The type is named on the chip instead, which also
   survives being read aloud or printed in black and white. */
/* One day column, named once. The width of the calendar is worked out from it,
   so the two cannot disagree.

   THE CALENDAR'S OWN LADDER OF GREYS, three rungs, each its own value rather
   than the site-wide shades a dozen other rules lean on. A calendar is mostly
   empty cells, so its shading covers far more of the screen than a hovered
   table row does and has to be lighter here than anywhere else to carry the
   same weight.

   Three saying three different things, lightest first:

     --cal-outside  a day from the month either side. Not part of this month at
                    all, and the quietest thing on the grid.
     --cal-off      a weekend or public holiday. A real day of this month that
                    nobody is expected in, so it is shaded a step more firmly.
     --cal-heading  the row of day names, which labels the table rather than
                    being part of it, and is darker again.

   The steps are 1.5%, 2.5% and 5% below white — chosen as figures, then rounded
   to the nearest shade a screen can actually show, which lands at 1.57, 2.35
   and 5.10. Blue sits a shade above red and green in each, which is the cool
   tint every other grey on the site carries; without it they read as a warmer,
   flatter grey beside everything around them.

   THE FIRST GAP IS NOW TWO SHADES OUT OF 255 — 251 against 249. That is at the
   edge of what a screen shows and past the edge of what a poor one does, so a
   weekend and a day from another month may look identical on some monitors. It
   is a deliberate choice, not an oversight: the rungs still climb in the right
   order, and the alternative was a heavier weekend than was wanted. If they
   ever need telling apart reliably, the room is between the weekend and the
   heading, where five shades are going spare. */
.cal-wrap {
  overflow-x: auto;
  --cal-col: 120px;
  --cal-outside: #fbfbfc;
  --cal-off: #f9f9fa;
  --cal-heading: #f2f2f5;
  /* The edge round a public holiday. A light red: clearly red at a glance, and
     well short of the red this site uses to say something has gone wrong. A
     holiday is good news. */
  --cal-holiday-edge: #f5bcc0;
}
/* The month is a stack: one row of day names, then one row per week. It used
   to be a single grid of forty-odd cells, which cannot hold a bar running
   across several days - a grid item belongs to one cell. */
.cal-grid {
  display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border);
  border-radius: var(--radius-sm); overflow: hidden;
  /* THE SPACE IT HAS, BUT NEVER LESS THAN SEVEN COLUMNS WIDE.

     The seven columns are set on the rows rather than on this box, so on a
     phone it stayed the width of the screen while the rows inside it ran to
     846px - and with the corners rounded off by overflow: hidden, Sunday was
     cut off with no way to scroll to it. The answer was max-content: hug the
     widest row, and give the wrapper something to scroll.

     THAT WAS TOO LITERAL, and it took adding job titles to the bars to show it.
     "Widest row" counts the BARS, so one person with a long job title stretched
     the whole month to fit their bar - measured at 2409px against the 967 it
     should be, which is to say two and a half screens of sideways scrolling for
     everybody, because of one job title.

     What was actually wanted is said outright now: as wide as there is room
     for, and never narrower than seven columns of --cal-col. Nothing inside can
     widen it, so a long bar has to fit in its days and be trimmed with an
     ellipsis - which is what .cal-bar has always said it wanted. */
  width: 100%;
  min-width: calc(7 * var(--cal-col) + 6px);
}
/* The three things that have to line up in the same seven columns: the day
   names, the boxes under them, and the lanes the bars sit in. One template,
   named once, so they cannot drift apart. */
.cal-headings, .cal-days, .cal-lane {
  display: grid; grid-template-columns: repeat(7, minmax(var(--cal-col), 1fr));
}
.cal-headings, .cal-days { gap: 1px; background: var(--border); }

/* A WEEK IS TWO LAYERS, one laid on the other: the boxes underneath with their
   shading and their date, the bars over the top.

   Both children are put in the same single cell of the week, so they overlap
   and the taller of the two decides how tall the week is. That is what lets a
   week with six people away grow without an empty week growing with it, and it
   needs no absolute positioning - which would have fixed the height and let a
   busy week spill out of its own row. */
.cal-week { display: grid; }
.cal-week > * { grid-area: 1 / 1; }
/* Clear of the date at the top of each box: 6px of box padding, the line the
   number sits on, and the 4px under it. */
.cal-bars {
  display: flex; flex-direction: column; gap: 3px;
  padding-top: 27px; pointer-events: none;
}
/* The 1px between columns, so a bar of one day lines up with its box - and a
   bar of three days covers the two lines inside it and reads as one thing. */
.cal-lane { column-gap: 1px; }
/* THE ROW OF DAY NAMES, and it has to be told apart from the days below it.

   It used to wear --surface-2 with faint lettering - the same grey as a
   weekend and the same grey as a day from the month either side. Three
   different things, one colour: the row naming the columns read as a fourth
   row of greyed-out days rather than as the heading of the table.

   A step darker than any of them, with the body text colour on it, so it
   reads as the label row it is. Grey rather than a tint: the calendar takes
   no colour from anything else, and a coloured band here would be the one
   thing on it that meant nothing.

   THE DARKEST OF THE CALENDAR'S THREE, and now genuinely three: a day from
   another month, a weekend, and this, at 1.5%, 3% and 5% below white. It sat
   on the site-wide shade until those figures were chosen, and has come down
   several times before that — from the strongest grey the site has, which lay
   across a calendar of near-white cells like a bar, to the line grey, to here.

   It has to stay the darkest of the three. Level with the weekend shading and
   the heading reads as a fourth row of days rather than as the label row. */
.cal-head {
  background: var(--cal-heading); padding: 6px 8px;
  /* Bold, at 700, where nearly everything else on the site labels at 600.
     Asked for, and it puts this row in the company it belongs to: the two
     other things set at 700 are the section labels on the dashboard and the
     status pills, both of them small capitals naming what a block of content
     IS rather than being part of it. That is exactly this row's job.

     It also settles a small ambiguity. At 600 the day names carried the same
     weight as the date numbers underneath them, so the heading read as another
     row of the table it heads. */
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text);
}
.cal-day { background: var(--surface); padding: 6px 8px; min-height: 84px; }
/* A day from the month either side, kept so the weeks stay whole.

   THE LIGHTEST RUNG, because it has the least to say. It is not a day of this
   month at all — nothing that happens on it belongs to the month being looked
   at, so it only has to stay out of the way. A calendar is mostly empty cells
   and this shading covers a large part of the grid; at anything heavier the
   whole right-hand side of a month read as switched off. */
.cal-day-outside { background: var(--cal-outside); }
.cal-day-outside .cal-number { color: var(--text-faint); }
/* Weekends and public holidays: nobody is expected in, so leave falling here
   is not a gap in cover and should not read as one.

   A STEP FIRMER THAN THE ROW ABOVE, and the two were one shade until it was
   asked for. They are not the same statement: a Saturday IS a day of this
   month, and somebody reading the grid for cover has to see where the working
   week stops. A day from another month has no such claim on their attention. */
.cal-day-off { background: var(--cal-off); }
/* A public holiday, outlined so it cannot be mistaken for a weekend.

   AN INSET SHADOW, NOT A BORDER, and that is the whole reason this is not the
   obvious one line. The grid is laid out with one-pixel gaps showing a
   background through, so every cell has to stay exactly the size the grid gave
   it; a real border adds two pixels and pushes the week out of line with the
   one above it. Today's ring is drawn the same way, for the same reason. */
.cal-day-holiday { box-shadow: inset 0 0 0 1px var(--cal-holiday-edge); }
/* AFTER THE HOLIDAY RULE ON PURPOSE. A day can be both, and two rings cannot
   both be drawn round one cell. Today wins: it is what somebody is looking for
   when they open the page, and the holiday still says its name inside the
   cell. */
.cal-day-today { box-shadow: inset 0 0 0 2px var(--accent); }
.cal-date { display: flex; align-items: baseline; gap: 6px; margin-bottom: 4px; }
.cal-number { font-size: 12.5px; font-weight: 600; }
.cal-holiday {
  font-size: 10px; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* One run of leave, drawn across the days it covers. */
.cal-bar {
  margin: 0 5px; padding: 3px 6px; border-radius: var(--radius-sm);
  /* Without this a bar refuses to be narrower than its own words, and the
     ellipsis below never happens - it just overflows its days instead. */
  min-width: 0;
  background: var(--good-bg); color: var(--tint-good-ink);
  font-size: 11px; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* The layer it sits in lets clicks through to the boxes underneath; the bar
     itself takes them back, so pointing at one still shows what it says. */
  pointer-events: auto;
}
/* WHERE A RUN CARRIES ON PAST THE END OF A WEEK. A bar cannot turn a corner,
   so a leave from Friday to Tuesday is drawn twice, once on each row. Running
   the cut edge flat to the boundary, and rounding only the ends that really
   are ends, is what says "this continues" rather than "these are two". */
.cal-bar-from-before {
  margin-left: 0; border-top-left-radius: 0; border-bottom-left-radius: 0;
}
.cal-bar-runs-on {
  margin-right: 0; border-top-right-radius: 0; border-bottom-right-radius: 0;
}
/* A COLOUR PER KIND OF LEAVE, with a key under the calendar.

   The kind used to be written on every bar, under the name. That doubled the
   height of a bar for a word most people read once, and pushed the fourth
   person out of a busy week. The colour says it at a glance and costs no room
   at all.

   Eight of them, handed out by where a kind sits in the company's own list.
   A company with more than eight kinds starts round again, which is a poor day
   but a readable one - both are named in the key.

   Pale behind, the same colour darkened for the lettering. Each pair was
   measured against the text on it, not chosen by eye: none is under 4.5:1, and
   11px text needs every bit of that. */
.cal-tone-1 { background: #e8f5ea; color: #17743e; }
.cal-tone-2 { background: #fdeaea; color: #b3261e; }
.cal-tone-3 { background: #f2ecfd; color: #6d28d9; }
.cal-tone-4 { background: #e7f1fb; color: #0b5cad; }
.cal-tone-5 { background: #eeeeec; color: #57534e; }
.cal-tone-6 { background: #fbf0dd; color: #8a5a06; }
.cal-tone-7 { background: #fdeaf3; color: #a3196b; }
.cal-tone-8 { background: #e4f4f6; color: #0d6a75; }

/* Waiting on somebody, so it may yet not happen - but it has to be planned
   around all the same. Outlined rather than filled says "not settled", and the
   outline is the kind's own colour, so a bar still says what it is while it
   says it is not decided.

   Two classes deep so it beats the tone above whichever order they end up in -
   the same trap that made the month arrows draw as empty circles twice. */
.cal-bar.cal-bar-waiting {
  background: transparent;
  box-shadow: inset 0 0 0 1px currentColor;
}

/* The key. Only the kinds actually on this month are listed, so it stays short
   - and because a colour comes from the company's list rather than from what is
   on screen, a shorter key never means a colour has changed hands. */
.cal-key {
  list-style: none; margin: 11px 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 6px 16px;
  font-size: var(--text-sm); color: var(--text-muted);
}
.cal-key-item { display: flex; align-items: center; gap: 6px; }
.cal-key-dot { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
/* The outline, shown the way a waiting bar wears it. */
.cal-key-dot-waiting {
  background: transparent; box-shadow: inset 0 0 0 1px var(--border-strong);
}

/* WHICH DAY A BAR STARTS ON, AND HOW MANY IT COVERS, as fourteen ready-made
   classes rather than a width worked out per bar.

   The security policy blocks inline styles, so a bar cannot carry its own
   placement the usual way. It does not need to: there are only seven days in a
   week and only seven lengths a bar can be. */
.cal-at-1 { grid-column-start: 1; }
.cal-at-2 { grid-column-start: 2; }
.cal-at-3 { grid-column-start: 3; }
.cal-at-4 { grid-column-start: 4; }
.cal-at-5 { grid-column-start: 5; }
.cal-at-6 { grid-column-start: 6; }
.cal-at-7 { grid-column-start: 7; }
.cal-for-1 { grid-column-end: span 1; }
.cal-for-2 { grid-column-end: span 2; }
.cal-for-3 { grid-column-end: span 3; }
.cal-for-4 { grid-column-end: span 4; }
.cal-for-5 { grid-column-end: span 5; }
.cal-for-6 { grid-column-end: span 6; }
.cal-for-7 { grid-column-end: span 7; }
/* --- File fields ---------------------------------------------------------
   Left alone, a file input is a grey system button with a scrap of text
   beside it — the one control on the site that looks like it belongs to the
   browser rather than to us. This dresses the input as one of our fields with
   one of our buttons sitting inside it.

   The button is a pseudo-element, so it cannot be given the .btn class and its
   shape has to be written out again here. The two spellings of that
   pseudo-element are kept as separate rules on purpose: a browser that does
   not recognise a selector throws away the whole rule it appears in, so
   pairing them with a comma would cost us one browser or the other. */
input[type=file] {
  width: 100%;
  /* Tighter than a text field: the button inside carries its own height. */
  padding: 5px 10px 5px 5px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-muted);
  font-family: inherit; font-size: 12.5px; letter-spacing: -.008em;
  cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease;
}
input[type=file]::file-selector-button {
  margin-right: 10px;
  padding: 5px 14px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: transparent; color: var(--accent);
  font-family: inherit; font-size: 12px; font-weight: 500;
  letter-spacing: -.008em; line-height: 1.2;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
input[type=file]::file-selector-button:hover { background: var(--surface-2); }

input[type=file]::-webkit-file-upload-button {
  margin-right: 10px;
  padding: 5px 14px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: transparent; color: var(--accent);
  font-family: inherit; font-size: 12px; font-weight: 500;
  letter-spacing: -.008em; line-height: 1.2;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
input[type=file]::-webkit-file-upload-button:hover { background: var(--surface-2); }

/* Only the button opens the file chooser.
   Left to itself, a file input opens the chooser wherever it is clicked —
   including the empty stretch beside the button, which reads as part of the
   field rather than as something to press. Switching pointer events off for
   the input and back on for the button inside it means a click has to land on
   the button to do anything. Keyboard use is untouched: the field still takes
   focus, and Space or Enter still opens the chooser. */
input[type=file] { pointer-events: none; }
input[type=file]::file-selector-button { pointer-events: auto; }
input[type=file]::-webkit-file-upload-button { pointer-events: auto; }

/* The empty part is no longer clickable, so it must not invite a click. */
input[type=file] { cursor: default; }

/* --- The built file field -------------------------------------------------
   Nothing can be placed inside an <input type=file>: the browser draws its
   button and its text, and that box holds nothing else. So where JavaScript
   runs, app.js builds the field a person sees — this box — and puts the real
   input inside it, out of sight. The button and the chosen files then sit
   together within one outline, which is what a field of this kind looks like.

   Everything above still applies when the script has not run: the bare input
   is styled to match this box, so the page is right either way. */
.filefield {
  position: relative;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  width: 100%; min-height: 38px;
  padding: 4px 10px 4px 4px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: 12.5px; letter-spacing: -.008em;
}
/* The real control: still the thing that holds the files and the thing the
   browser points at over a missing required file, so it is placed rather than
   hidden — a display of none would put it beyond the browser's reach. */
.filefield input[type=file] {
  position: absolute; left: 10px; bottom: 0;
  width: 1px; height: 1px; padding: 0; margin: 0;
  border: 0; opacity: 0;
}
.filefield:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.filefield-button {
  flex: none;
  padding: 5px 14px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: transparent; color: var(--accent);
  font-family: inherit; font-size: 12px; font-weight: 500;
  letter-spacing: -.008em; line-height: 1.2;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
.filefield-button:hover { background: var(--surface-2); }
.filefield-empty { color: var(--text-muted); }

/* --- Chosen files --------------------------------------------------------
   One pill per file: its name, its size, and a way to take that one off
   again. They flow along the row beside the button and wrap onto a second
   line when there are more than fit. */
.filefield-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 6px; min-width: 0;
}
.filepill {
  display: inline-flex; align-items: center; gap: 7px; max-width: 100%;
  /* Less padding on the right: the remove button carries its own. */
  padding: 3px 5px 3px 13px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: var(--surface);
  font-size: 12px; letter-spacing: -.008em; line-height: 1.2;
}
/* When there is not enough room, the name is what gives way — it shortens to
   an ellipsis. The size next to it never does: "117 KB" wrapping onto two
   lines makes the whole pill twice as tall for no reason. */
.filepill-name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 15rem;
}
.filepill-size { flex: none; white-space: nowrap; color: var(--text-faint); }
.filepill-remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 19px; height: 19px; padding: 0; flex: none;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--text-muted);
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.filepill-remove:hover { background: var(--bad-bg); color: var(--bad); }
.filepill-remove:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}

/* --- Date fields ---------------------------------------------------------
   Browsers do supply a calendar button on <input type="date">, but the default
   glyph is almost black, which disappears against a dark field. We replace it
   with our own icon so it is clearly visible in both light and dark mode, and
   give it a proper hit area. app.js also opens the picker when the field
   itself is clicked, so the whole control is clickable, not just the icon. */
input[type=date],
input[type=month] {
  /* Room for the icon, so a long value never runs underneath it. */
  padding-right: 2.2rem;
}
input[type=date]::-webkit-calendar-picker-indicator,
input[type=month]::-webkit-calendar-picker-indicator {
  width: 1.25rem;
  height: 1.25rem;
  margin: 0 -.15rem 0 0;
  padding: 0;
  cursor: pointer;
  opacity: 1;
  border-radius: 5px;
  background-color: transparent;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 1rem 1rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236e6e73' stroke-width='1.8' stroke-linecap='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='2'/%3E%3Cpath d='M3 10h18M8 3v4M16 3v4'/%3E%3C/svg%3E");
  transition: background-color .12s ease;
}
input[type=date]::-webkit-calendar-picker-indicator:hover,
input[type=month]::-webkit-calendar-picker-indicator:hover {
  background-color: var(--surface-2);
}
/* Clear Chrome's inner spin/clear buttons that crowd the icon. */
input[type=date]::-webkit-inner-spin-button,
input[type=month]::-webkit-inner-spin-button { display: none; }

.hint { display: block; color: var(--text-muted); font-size: var(--text-sm); margin-top: 3px; }
/* Deliberately larger than the .hint it replaces when a field fails
   validation: an error has to be read, a hint only has to be available.
   Not on --text-sm for that reason — change this one on its own. */
.error-text { display: block; color: var(--bad); font-size: 12px; margin-top: 3px; }
.field-error input, .field-error select, .field-error textarea { border-color: var(--bad); }

/* A remaining-days figure that a request being filled in would take past what
   is left. Only ever written by the browser, as a preview: the stored figure is
   on the page as a number and is put straight back when the dates change. */
.balance-short { color: var(--bad); font-weight: 500; }

/* The live "days of leave" figure on the leave application form. */
.day-count {
  display: inline-block;
  font-size: 21.5px;
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.15;
  color: var(--accent);
  padding: 3px 0;
  font-variant-numeric: tabular-nums;
}
.day-count-warn { color: var(--warn); font-size: 15.5px; }

/* Only as wide as the box and its words.
   It used to be a block, so the label stretched the whole width of the card —
   and a label toggles what it names, so clicking far out to the right of "Tax",
   on what looks like empty space, ticked the box. Clicking the WORD still
   works, which is what a label is for: it is a bigger target than a 13-pixel
   square for anybody whose hands are unsteady. */
.checkbox,
/* Named twice on purpose. `.field label` sets every label in a field to a
   block, in the muted grey of a field's heading — and it is the more specific
   of the two, so it was quietly winning here: hence the full-width label, and
   the words in a heading's colour and weight rather than a choice's. */
.field label.checkbox {
  display: inline-flex; align-items: center; gap: 7px;
  font-weight: 400; font-size: 12px; color: var(--text);
  margin-bottom: 0; vertical-align: middle;
}
.checkbox input { width: 13px; height: 13px; accent-color: var(--accent); }
/* Where a field holds more than one, they stay stacked as they were rather
   than running together along a line. */
/* A tick box sharing a row with text boxes.
   It carries no heading of its own — the words beside it are the heading — so
   left alone it floats at the top of the row while the boxes next to it sit
   under theirs. This brings it down level with them. */
.field-tick { display: flex; align-items: flex-end; }
.field-tick .checkbox { min-height: 31px; }

.field:has(> .checkbox + .checkbox) {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
}

/* Payslip line items: a description and an amount per row, with a button to
   append more. Sections start with a single empty row. */
.item-group { border: none; padding: 0; margin: 0 0 18px; }
.item-row {
  display: grid; grid-template-columns: 1fr 140px 32px; gap: 7px;
  align-items: center; margin-bottom: 6px;
}
.item-row input[inputmode="decimal"] { text-align: right; }
/* --- The draft payslip editor -------------------------------------------
   Laid out like the payslip that gets printed: one money line per row, what
   it is on the left, the amount on the right, a total under each section. */

.pay-sheet { margin-bottom: 16px; }
.pay-section { margin-bottom: 22px; }
.pay-section > h3 {
  margin: 0 0 3px; font-size: 13px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted);
  padding-bottom: 5px; border-bottom: 1px solid var(--border);
}
.pay-section > .hint { margin: 5px 0 0; }
.pay-lines { margin-top: 7px; }

/* The same three columns as a typed-in row, so a line worked out by a rule and
   a line somebody typed sit on exactly the same grid. */
.pay-line {
  display: grid; grid-template-columns: 1fr 140px 32px; gap: 7px;
  align-items: center; margin-bottom: 6px;
}
.pay-line-what { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.pay-line-label { font-size: 13.5px; }
.pay-line-note { font-size: var(--text-sm); color: var(--text-muted); line-height: 1.35; }
.pay-line input { text-align: right; }
.pay-line-amount { text-align: right; font-variant-numeric: tabular-nums; }

/* A section total: ruled off above, the way a printed payslip subtotals. */
.pay-total-line {
  border-top: 1px solid var(--border); padding-top: 7px; margin-top: 2px;
  font-weight: 500;
}
.pay-total-line .pay-line-what { font-size: 13.5px; }

/* Gross, less deductions, and the net figure the employee actually receives. */
.pay-section-sum { border-top: 2px solid var(--border-strong); padding-top: 12px; }
.pay-line-net {
  border-top: 1px solid var(--border); padding-top: 9px; margin-top: 3px;
  font-size: 16px; font-weight: 600;
}
.pay-line-net .pay-line-amount { color: var(--good-ink); }

/* Inside a section the typed-in rows follow the rule lines with no heading of
   their own, so the two read as one list. */
.pay-section .item-group { margin-top: 0; }
.pay-section .item-row { margin-bottom: 6px; }
.item-group > .btn { margin-top: 3px; }

/* --- The page contents rail ------------------------------------------------
   A list of the sections on this page, kept beside the content so that
   somebody looking for a setting can see it exists rather than scrolling to
   find out. Built from the headings already on the page — see the page-rail
   section of public/app.js.

   THE WIDTH IS THE WHOLE DESIGN PROBLEM HERE. The content column is capped at
   1060px and the sidebar takes 62px, so a rail beside the content is free only
   where the window is wider than the two of them together. Narrower than that
   and every pixel the rail takes is a pixel off the tables, several of which
   already scroll sideways.

   THE SIDEBAR USED TO TAKE 198px, and these numbers were worked out when it
   did. It became a 62px rail of icons, and nobody gave the 136px back: the
   rail went on waiting for a window 136px wider than it needed, and the space
   to the right of it sat empty. At 1400 there were 112px of nothing beyond the
   rail — measured, which is how this was noticed at all.

   So there are three bands, each measured rather than guessed, and each
   redone against the 62px sidebar:

     1303px and wider   62 + 1060 + 18 + 148 + padding = 1303. The rail sits
                        beside the content, in space that was empty anyway.
     1280 to 1303       the rail, with cards giving up at most 23px. The same
                        trade the old 1400 made against 1424, and measured the
                        same way: at 1280 the content column is 1037px on both
                        the densest pages, every four-column form stays four
                        columns, and no table starts scrolling that was not
                        already. It breaks to three columns at about 1215, so
                        there is real room below this line, not a cliff edge.
     621 to 1279        no room for a rail. The same list becomes a Contents
                        button in the TITLE ROW, beside whatever buttons the
                        page already has. Measured on all eleven pages that
                        get a list: it costs no height on ten of them. My Team
                        is the exception — its subtitle is long enough to push
                        the button onto its own line below about 800px, which
                        costs one row there and nowhere else.
     620px and under    nothing. Removed, not shrunk.

   THE LAST BAND IS A DECISION, not an oversight. 620px is the app's own phone
   line — where tables fold into cards and everything drops to phone scale —
   and below it the title row is full. Two pages paid a wrapped row for the
   button there, and Leave paid three lines where it had one. A contents list
   is a convenience; taking a row of a phone screen away from the content to
   hold one is the wrong trade.

   An earlier version put the button above the breadcrumb, so it arrived above
   the page's own name — the first thing on an employee's record was a grey
   pill saying nothing about whose record it was. That is what moving it into
   the title row fixes. */

.with-rail {
  display: flex;
  align-items: flex-start;
  /* 2px, down from 18 in two steps. Above 1303px the content column is at its
     1060px cap, so this gap is the only thing standing between the cards and
     the rail: every pixel taken off it moves the rail a pixel left rather than
     letting the content grow.

     Measured at 1500px, from the right edge of a card to the first word of the
     rail: 54px at 18, 46px at 10, 38px now.

     TWO PIXELS LOOKS LIKE A MISTAKE AND IS NOT. The separation between the
     cards and the rail was never really this gap — it is the content column’s
     own 26px of right padding, which the rail sits outside of. Add the rail’s
     11px inset and the words still stand 37px clear of the card edge with the
     gap at nothing at all. This number is the last 2px of travel, not the
     margin holding the two apart.

     WHICH MEANS THERE IS NO MORE HERE. Anything further has to come out of the
     content column itself: trim its right padding and the cards grow toward a
     rail that has not moved; trim its max-width and the rail comes left at the
     cost of the content. Both are real options and both are bigger decisions
     than a gap. */
  gap: 2px;
}
/* min-width: 0 or a wide table inside will refuse to let the flex item shrink
   and push the rail off the screen instead of scrolling itself. */
.with-rail > main { flex: 1 1 auto; min-width: 0; }

.page-rail {
  flex: 0 0 148px;
  width: 148px;
  position: sticky;
  /* Clear of the top bar. A support session adds a second sticky bar above it,
     so the figure is set from JavaScript rather than written in here. */
  top: var(--page-rail-top, 60px);
  max-height: calc(100vh - var(--page-rail-top, 60px) - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.page-rail-head {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-faint); font-weight: 600;
  margin: 0 0 7px 10px;
}

.page-rail-list { list-style: none; margin: 0; padding: 0; }

/* Both lists letter the same way. Written as two selectors rather than one,
   because the menu is NOT inside .page-rail — it lives in the title row, a long
   way from it in the document. For a while this rule said only .page-rail a,
   which reached the rail and missed the menu entirely: the dropdown's entries
   were unstyled browser links, inline and unpadded, running together as a
   paragraph of blue text. */
.page-rail a,
.page-rail-menu a {
  display: block;
  padding: 4px 9px;
  /* The marker lives in a border that is always there and usually the colour
     of nothing, so the words do not shift sideways when it lights up. */
  border-left: 2px solid transparent;
  color: var(--text-muted);
  font-size: 11.5px; line-height: 1.35;
  text-decoration: none;
}
.page-rail a:hover,
.page-rail-menu a:hover { color: var(--text); text-decoration: none; }
.page-rail a[aria-current="true"],
.page-rail-menu a[aria-current="true"] {
  color: var(--accent);
  border-left-color: var(--accent);
  font-weight: 600;
}

/* In the dropdown the entries are things to hit rather than a margin note, so
   they are a size up and take a highlight the whole width of the panel. */
.page-rail-menu a {
  font-size: 12.5px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
}
.page-rail-menu a:hover { background: var(--surface-2); }

/* --- The same list, where there is no room for a rail ---------------------- */

/* Quiet grey, not the solid blue that .page-actions gives its buttons. Those
   are the two or three things somebody came to the page to do; this is a way of
   looking around it, and it must not outrank them. Same reasoning as Cancel.

   AND LIGHTER THAN EVERY OTHER GREY BUTTON, deliberately. Cancel, and the grey
   buttons on a form, are answers to something somebody is in the middle of
   doing. This one is not part of any task — it is a way of looking around the
   page — so it is the quietest control on the screen.

   Written as .btn.page-rail-toggle rather than .page-rail-toggle so it outranks
   .btn-neutral by specificity rather than by which line happens to come second.
   Nothing else in the app wears this class, so nothing else moves with it.

   THE LETTERING IS AS LIGHT AS IT CAN GO. --text-muted on --surface-3 reads
   4.86:1, and 12px text is asked to stand 4.5:1 from what it sits on. The next
   grey lighter, --text-faint, reads 4.39:1 and is under the line. If this is
   ever wanted fainter still, the honest answer is that the words cannot go —
   only the fill and the edge have anywhere left to move.

   AND THE EDGE HAS NOW RUN OUT. It was lightened three times — 14% of the
   lettering mixed into the fill, then 9%, then 8% — and 8% is the end of the
   road, not a preference.

   At 8% it stands 1.10 from the fill it surrounds, where 1.00 would be the same
   colour exactly. Below about 1.10 two colours stop being separable at all, so
   the next step down is not a fainter edge: it is an edge that is in this file
   and not on the screen.

   SO THERE IS NO SMALLER NUMBER TO REACH FOR. If this is ever wanted quieter
   again, the honest choices are to take the border off and let the fill hold
   the shape on its own, or to leave it alone. Writing 6% or 4% here would only
   produce a button claiming an outline it does not really have, and would read
   in this file as though somebody had thought about it.

   Written as a color-mix rather than a hex code so the relationship holds by
   itself if either end is ever changed. */
.btn.page-rail-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface-3);
  color: var(--text-muted);
  border-color: color-mix(in srgb, var(--text-muted) 8%, var(--surface-3));
}

/* One step firmer under the pointer, rather than the full --neutral-hover the
   other grey buttons take — from this fill that would be a lurch. The lettering
   holds at 4.74:1 on the darker ground, and the edge stays clearly firmer than
   its resting 9% so that pointing at it still does something visible. */
.btn.page-rail-toggle:hover {
  background: var(--surface-2);
  border-color: color-mix(in srgb, var(--text-muted) 14%, var(--surface-2));
}

/* Dropped from the right-hand end of the button strip, and absolutely
   positioned so it adds no width to that strip and cannot push a button onto
   another line. Anchored to the strip rather than to the button, because the
   strip's right edge is always the content's right edge — so the panel is
   always on screen, whatever else is in the row. */
.page-rail-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 15;
  min-width: 220px; max-width: 320px;
  max-height: 60vh; overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  padding: 5px;
  text-align: left;
}
.page-rail-menu[hidden] { display: none; }

/* The exact opposite of the query below, written as its negation rather than
   as "1399px and under".

   max-width: 1279px and min-width: 1280px look like they meet, and they do
   not: a viewport 1399.5px wide satisfies NEITHER. That is not a curiosity —
   Windows at 125% or 150% scaling hands out fractional widths as a matter of
   course. Measured at that width, both the rail and the button it stands in
   for were on screen at once, which is the one arrangement neither is for.

   Negating the query leaves no gap to fall into: one of the two matches at
   every width there is. */
@media not all and (min-width: 1280px) { .page-rail { display: none; } }
/* Above 1280px the rail itself is on screen, so the button that stands in for
   it has no work to do and goes.

   WRITTEN AS .btn.page-rail-toggle, and it has to be. The rule that dresses
   this button is .btn.page-rail-toggle — two classes — and it sets display:
   inline-flex to lay the label out beside its mark. A media query adds nothing
   to specificity, so a bare .page-rail-toggle here is one class against two and
   simply loses.

   It did lose. The button sat on wide screens next to the rail it duplicates,
   and pressing it did nothing at all: the menu underneath is hidden by a rule
   that DOES win, because .page-rail-menu has no competing display of its own.
   A visible control that answers nothing, and one that told a screen reader it
   had expanded something while the panel stayed shut.

   So the two classes are matched deliberately. Trimming the .btn off reads like
   tidying and puts the button back. */
@media (min-width: 1280px) { .btn.page-rail-toggle, .page-rail-menu { display: none; } }

/* Below the app's own phone line, no contents list at all — see the note at the
   top of this section. The strip is hidden too, but only where this button is
   the only thing in it; a page with real buttons keeps them. */
@media (max-width: 620px) {
  /* .btn.page-rail-toggle for the reason given at the 1280px rule: the button
     is laid out by a two-class selector, and one class here loses to it.

     It was hidden anyway on most pages — but only because the whole strip
     around it is hidden, which the line below only does where this button is
     the only thing in it. On a page whose strip holds real buttons the strip
     stays, and the button was coming back with it, offering a contents list
     that does not exist at this width. */
  .btn.page-rail-toggle,
  .page-rail-menu,
  .page-actions[data-holds-contents-only] { display: none; }
}

/* A contents list is for finding your way about a screen. On paper the whole
   thing is already in front of you. */
@media print { .page-rail, .btn.page-rail-toggle, .page-rail-menu { display: none; } }

/* --- Support access banner -----------------------------------------------
   Shown on every page while a platform operator is looking inside a customer
   company. Deliberately loud: it must never be ambiguous whose data is on
   screen, or that the session is read-only. */
.support-banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  padding: 9px 18px;
  background: var(--warn-bg); color: var(--warn);
  border-bottom: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
  font-size: 12px;
  position: sticky; top: 0; z-index: 25;
}
.support-banner strong { font-weight: 600; }
.support-reason { display: block; opacity: .85; }

/* Tick-box column on a list you can act on in bulk. */
.col-tick { width: 1%; padding-right: 0; }
.col-tick input { width: 13px; height: 13px; margin: 0; cursor: pointer; }
.col-tick input:disabled { cursor: not-allowed; opacity: .35; }

/* --- Table column widths -------------------------------------------------
   Applied via the `className` option on a table column. A timestamp must never
   wrap: left to itself the browser squeezes that column down to nothing and
   spreads the free space across the wordy ones instead.

   Only the timestamp is constrained. Leaving the wordy columns to size
   themselves keeps the table inside the page rather than scrolling sideways.

   The floor is the timestamp's own width plus its padding and no more —
   measured on the page rather than guessed at. It used to be 172px, half again
   as wide as the date it holds, which left the column padded out with empty
   space and the Company beside it squeezed to three words a line. */
.col-when { white-space: nowrap; width: 1%; min-width: 124px; }
.col-who  { white-space: nowrap; }

/* The company a message came from. Given a floor of its own so a real name
   fits on a line or two rather than being wrung out one word at a time in
   whatever space is left over. A very long name still wraps; it is not pinned
   to a fixed width. */
.col-company { min-width: 130px; white-space: normal; }

/* As narrow as its contents allow, and never broken across two lines.

   THE 172 ABOVE IS FOR A DATE AND A TIME — "02/09/2026, 03:46" — and it is a
   floor, so a column holding anything shorter is held open to that width and
   sits in a pool of empty space. A date alone, or a version number, needs a
   fraction of it. Same nowrap, same "take only what you need" width, without a
   floor written for a longer string. */
.col-snug { white-space: nowrap; width: 1%; }

/* ------------------------- Rule builder ----------------------------------
   Rows of "field / operator / value" joined by all-of or any-of, in the shape
   people already know from database filters. Each rule is a card; the row
   wraps rather than scrolling sideways on a narrow screen. */

.rule-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 12px;
  background: var(--surface-3);
}

.rule-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  margin-bottom: 8px;
}

/* The joiner column. Every row and every nested group starts with one of
   these, so "Where / And / And" lines up straight down the left edge the way
   it does in a database filter. */
.rule-joiner-slot {
  flex: 0 0 62px; display: flex; align-items: center; min-height: 30px;
}
.rule-joiner {
  font-size: 11.5px; color: var(--text-muted); padding-left: 2px;
}
/* Both the word and the picker live in the slot; only one shows at a time. */
select.rule-joiner-select {
  width: 100%; padding: 3px 20px 3px 6px; font-size: 11.5px;
  background-position: right 4px center;
}

.rule-label { flex: 1 1 180px; min-width: 140px; }

.rule-rows { display: flex; flex-direction: column; gap: 6px; }

.rule-condition {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
}
/* Form fields are full width everywhere else, which would put each part of a
   condition on its own line. Inside a rule row they share one line instead,
   and sit a size smaller than a normal field so a long rule stays readable. */
.rule-condition select,
.rule-condition input[type="text"] {
  width: auto; padding: 4px 8px; font-size: 11.5px;
}
.rule-condition select { padding-right: var(--select-arrow-gap); }
.rule-condition select[data-field-picker] { flex: 1 1 165px; min-width: 135px; max-width: 235px; }
.rule-condition select[data-operator-picker] { flex: 0 1 125px; min-width: 100px; }
.rule-condition select[data-value-input] { flex: 1 1 145px; min-width: 110px; }
.rule-condition input[type="text"] { flex: 1 1 100px; min-width: 78px; }

/* A value box that takes SEVERAL answers.
 *
 * <select multiple> is drawn by the browser as a stack of every option, always
 * open. Five statuses made a rule row four lines tall; ten would make it nine.
 * There is no styling around that — it is what the control IS.
 *
 * So the select stays, hidden, because it is what submits and it is what
 * somebody with scripting off actually uses. In front of it goes a face one row
 * high that opens a list when pressed, with a tick against each answer chosen.
 * app.js builds it; none of this shows until it does.
 */
.multi-picker { position: relative; flex: 1 1 145px; min-width: 110px; }

/* Kept in the page rather than removed: it carries the name, holds the answers
   and is what the form sends. Hidden the accessible way — display:none would
   still submit, but this way the native control can be reached by anything
   that needs it. */
.multi-picker-native {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* Dressed to look exactly like the dropdown beside it, chevron and all, since
   that is what it stands in for. */
/* ONE LOOK FOR BOTH, and that is the point of the shared selector rather than
   two rules that happen to agree today. There are exactly two menus on this
   site you open to choose SEVERAL things — the value box in a rule condition,
   and the parts of pay an action is worked out on — and they should not be
   telling somebody they are different kinds of thing. */
.multi-picker-face,
.action-parts-button {
  text-align: left; cursor: pointer;
  padding: 4px 8px; padding-right: var(--select-arrow-gap);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
  font-family: inherit; font-size: 11.5px; letter-spacing: -.008em;
  line-height: 1.45;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.2 5 4.8 9 1.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--select-arrow-inset) center;
  background-size: 10px 6px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
/* Where each one sits is its own business: the value box fills its column in a
   rule row, the parts button sits inside a sentence and must not push it.

   AND THE FACE STANDS AS TALL AS A DROPDOWN. It is the Employee box in a row of
   filters — beside a year, a status and two dates — and it was five pixels
   shorter than every one of them, which in a row of otherwise level boxes reads
   as something half-drawn. It now takes a dropdown's own padding, size and line
   height, so the row is level.

   Only the face. The parts button shares the look above but sits inside a
   sentence, where a taller box would push the words apart.

   Written as two classes on purpose. The rule above is one, and one against one
   is settled by whichever was written later in the file — the trap that made
   the month arrows draw as empty circles twice. Two wins wherever it sits. */
.multi-picker > .multi-picker-face {
  width: 100%;
  padding: 7px 10px; padding-right: var(--select-arrow-gap);
  font-size: 12.5px; line-height: 16px;
}
.action-parts-button { display: inline-flex; align-items: center; max-width: 200px; }
.action-parts-button > span {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.multi-picker-face:hover,
.action-parts-button:hover { border-color: var(--text-faint); }
.multi-picker-face[aria-expanded="true"],
.action-parts-menu.is-open .action-parts-button { border-color: var(--accent); }

/* Nothing chosen yet reads as a prompt rather than as an answer. */
.multi-picker-face[data-empty] { color: var(--text-muted); }

/* The list. Absolute, so opening one does not shove the rule below it down the
   page; capped in height and scrolling, so ten answers are a list rather than a
   wall. */
.multi-picker-menu,
.action-parts-menu.is-open .action-parts,
.action-parts-menu:not(.is-open) .action-parts:not([hidden]) {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0;
  display: flex; flex-direction: column; flex-wrap: nowrap; align-items: stretch;
  min-width: 100%; max-width: 280px;
  max-height: 232px; overflow-y: auto;
  gap: 0; padding: 4px;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); box-shadow: var(--shadow);
}
.multi-picker-menu[hidden] { display: none; }

/* A search line inside the menu.

   Only on the boxes that ask for one: a list of five residency statuses does
   not need searching. The line stays still and the names scroll under it, so
   the scrolling moves off the menu and onto the names - a search box that
   scrolls out of view while you are typing into it is worse than none. */
.multi-picker-menu-searchable { overflow: hidden; }
.multi-picker-rows { display: flex; flex-direction: column; }
.multi-picker-menu-searchable > .multi-picker-rows { max-height: 186px; overflow-y: auto; }
.multi-picker-search { padding: 1px 1px 5px; }
/* An ordinary text box, only quieter, to sit at the size the rows are. */
.multi-picker-search-box { padding: 5px 8px; font-size: 11.5px; letter-spacing: 0; }

/* Said rather than left blank: an empty list after typing reads as broken. */
.multi-picker-none {
  margin: 0; padding: 6px 8px;
  font-size: 11.5px; color: var(--text-faint);
}

.multi-picker-option,
.action-part {
  position: relative;
  display: flex; align-items: center; gap: 7px;
  width: 100%; text-align: left; cursor: pointer;
  padding: 5px 7px; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--text);
  font-family: inherit; font-size: 11.5px; line-height: 1.45;
  white-space: nowrap;
}
.multi-picker-option:hover,
.action-part:hover { background: var(--hover); }
/* The box is still there and still what submits — it is simply not what you
   look at any more, the tick is. Kept focusable rather than display:none, so
   the row can still be reached and toggled from the keyboard. */
.action-part input {
  position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0;
}
.action-part:focus-within { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The tick keeps its room whether it is drawn or not, so the wording of every
   row starts at the same place and the list reads as a column. */
.multi-picker-tick,
.action-part-tick {
  flex: 0 0 auto; width: 12px; height: 12px;
  background-repeat: no-repeat; background-position: center; background-size: 12px 12px;
}
.multi-picker-option[aria-selected="true"],
.action-part input:checked ~ .action-part-label { color: var(--accent); }
/* Drawn from the box's own :checked state on this side, which is why the parts
   menu still ticks correctly with scripting off. */
.multi-picker-option[aria-selected="true"] .multi-picker-tick,
.action-part input:checked ~ .action-part-tick {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%230071e3' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.4 4.9 8.8 9.5 3.4'/%3E%3C/svg%3E");
}
.multi-picker-label,
.action-part-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }

.rule-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
/* Save sits under the Add button with room of its own, so it reads as the end
   of the card rather than another button in the row above. */
.element-save { margin-top: 16px; }

/* Every small button inside a rule — "+ Add Condition", "Remove Rule" and the
   "Remove" on each condition row — is sized here so they cannot drift apart. */
.rule-actions .btn,
.rule-head .btn,
.rule-condition .btn,
.action-line .btn { padding: 4px 10px; font-size: 11.5px; }

/* Sits between one rule and the next, saying how they join. */
.rule-or {
  margin: 10px 0; display: flex; justify-content: center;
}
.rule-or select { width: auto; min-width: 72px; }

.rule-head [data-rule-label] { font-size: 12.5px; }
.rule-head .push-right { margin-left: auto; }

/* One "Rule and Action": its rules and the action they trigger, boxed
   together so it is obvious where one ends and the next begins. */
.rule-set {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  margin-bottom: 12px;
  background: var(--surface);
}
.rule-set-head {
  /* The name box carries a label above it, so the row aligns to the bottom
     and the buttons still sit level with the box itself. */
  display: flex; align-items: flex-end; gap: 9px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: var(--radius) var(--radius) 0 0;
}
.rule-set-naming {
  flex: 1 1 240px; min-width: 160px;
  display: flex; flex-direction: column; gap: 3px;
}
.rule-set-label {
  font-size: 10.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-faint);
}
input.rule-set-name { width: 100%; font-weight: 600; }
.rule-set-body { padding: 12px; }

/* Locked until Edit is pressed. `inert` already blocks clicks and tabbing in
   modern browsers; the pointer rule covers the rest, and the tint is what
   makes "you cannot type here yet" visible rather than merely true. */
.rule-set-body[inert] { pointer-events: none; }
.rule-set-body[inert] input,
.rule-set-body[inert] select {
  background-color: var(--surface-2); color: var(--text-muted);
  border-color: var(--border);
}

/* The name box sits ON the header, which is itself grey. Sharing that grey
   made the box vanish into it, so this one takes the brighter tone: still
   plainly not editable, but still plainly a box. */
input.rule-set-name[readonly] {
  background: var(--surface-3); color: var(--text-muted);
  border-color: var(--border);
}
.rule-set-body[inert] .btn { opacity: .45; }

.rule-set-buttons { display: flex; gap: 6px; margin-left: auto; }

/* The fold control: a small arrow in the top corner of the block, pointing
   down while open and right while folded. */
.rule-set-toggle {
  flex: 0 0 auto; align-self: flex-start;
  display: grid; place-items: center;
  width: 22px; height: 22px; padding: 0;
  border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--text-faint);
  cursor: pointer;
}
.rule-set-toggle:hover { color: var(--text); background: var(--hover); }
.rule-set-toggle svg { transition: transform .15s ease; }
.rule-set-toggle[aria-expanded="false"] svg { transform: rotate(-90deg); }

/* Every sentence the page writes about a Set is italic, wherever it appears —
   folded here, live inside an open Set, and in the Summary card at the top —
   so a description never reads as something you can type into. */
.rule-set-summary {
  margin: 0; padding: 9px 12px;
  font-size: 12px; font-style: italic; color: var(--text-muted);
  border-top: 1px solid var(--border);
}

/* --- The Action -----------------------------------------------------------
   Written as a sentence rather than a stack of labelled boxes, so the controls
   sit on one line and wrap like words when there is no room. */

.action-line {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-bottom: 8px;
}
/* The spans that group a box with its word are not boxes of their own: their
   contents sit straight on the sentence line. Nesting them would make each a
   flex item wide enough to hold its input's natural size — about twenty
   characters — which is enough to push the rest of the sentence onto another
   row. Hiding one still hides everything inside it. */
.action-fixed, .action-percent, .action-of { display: contents; }
/* Sized exactly as a condition row above, so the two read as one interface
   rather than two. Any change to .rule-condition belongs here too. */
.action-word { font-size: 11.5px; color: var(--text-muted); }
.action-line select,
.action-line input[type="text"] {
  width: auto; padding: 4px 8px; font-size: 11.5px;
}
.action-line select { padding-right: var(--select-arrow-gap); }
.action-direction { flex: 0 0 88px; }
.action-party { flex: 0 1 126px; }
.action-method { flex: 0 1 126px; }
.action-rounding { flex: 0 1 200px; min-width: 140px; }
/* The ceiling picker and the amount it holds to, sized like the rest of the
   line so a long sentence still wraps tidily rather than stretching. */
.action-cap { flex: 0 1 190px; min-width: 150px; }
.action-cap-amount { display: inline-flex; align-items: center; gap: 4px; }
/* Where the percentage starts counting from, sized the same way: it sits in
   the same sentence and reads as one clause of it. */
.action-floor { flex: 0 1 190px; min-width: 150px; }
.action-floor-amount { display: inline-flex; align-items: center; gap: 4px; }

/* A picker and the figure it governs are one phrase, so they wrap as one.
   Loose on the line, a long row broke between "only the part above" and its
   own box, leaving the words on one line and the amount on the next. */
.action-pair {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 1 auto; min-width: 0;
}

/* The parts of pay: a menu you open, with a tick against each part.

   With scripting off the button never appears and the list stays open on the
   line, which still works — it simply takes more room. */
.action-parts-menu { position: relative; display: inline-flex; flex: 0 1 auto; }

/* Closed, the list is folded away. Everything about how it LOOKS when open is
   said once, up beside the value box it now matches. */
.action-parts {
  display: inline-flex; flex-wrap: wrap; align-items: center;
  gap: 3px 10px;
}
/* Scripting off: the button is never shown, so the list stays on the line —
   ticks and all, since those are drawn from the boxes themselves. */
.action-parts-menu:has(> [data-parts-toggle][hidden]) .action-parts {
  position: static; flex-direction: row; flex-wrap: wrap; align-items: center;
  max-height: none; overflow: visible;
  padding: 0; min-width: 0; border: 0; box-shadow: none; background: none;
}
.action-parts-menu:has(> [data-parts-toggle][hidden]) .action-part {
  width: auto; padding: 0;
}
/* Why the list of elements is empty, said inside the menu itself: a short
   list with no reason for it reads as a broken page. */
.action-part-note {
  display: block; max-width: 34ch;
  font-size: 11.5px; line-height: 1.45; color: var(--text-faint);
}
.action-money { flex: 0 0 92px; width: 92px; min-width: 0; }
.action-rate { flex: 0 0 62px; width: 62px; min-width: 0; }

/* The tick boxes that reveal the optional parts. */
[data-actions-holder] .action-line + .action-line { margin-top: 6px; }

.action-switches {
  display: flex; flex-wrap: wrap; gap: 14px;
  margin: 10px 0;
}
.action-switch {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text); cursor: pointer;
}
.action-switch input { margin: 0; }

.action-panel {
  margin: 0 0 10px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-3);
}
.action-panel .action-line:last-child { margin-bottom: 0; }
.action-sub { display: grid; gap: 7px; margin-top: 4px; }
.action-radio {
  display: flex; align-items: flex-start; gap: 7px;
  font-size: 12px; line-height: 1.45; cursor: pointer;
}
.action-radio input { margin: 3px 0 0; flex: 0 0 auto; }
.action-radio .muted { font-size: 11.5px; }

/* The Summary card: one paragraph per Rules and Actions Set, with the Set it
   belongs to named above the sentence.

   Kept deliberately tight. It is a card you read at a glance and scroll past,
   so the paragraphs carry no padding of their own — only a small gap and a
   hairline between them — and the text sits a size below the body copy. */
.element-summary {
  /* 18px between paragraphs, split either side of the hairline so the line
     sits midway rather than hugging the paragraph above it. */
  margin: 0 0 9px; padding: 0 0 9px;
  font-size: 11.5px; line-height: 1.4; color: var(--text);
  font-style: italic;
  border-bottom: 1px solid var(--border);
}
.element-summary:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: 0; }
.element-summary strong {
  display: block; margin-bottom: 4px;
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  /* The sentence beside it is italic; the label naming the Set is not. */
  font-style: normal;
  color: var(--text-faint);
}
.element-summary.is-empty { color: var(--text-muted); }

/* The card holding them is tighter than a form card for the same reason. */
.card-body:has(> [data-summary]) { padding: 11px 17px; }

/* This card's title has no line of explanation under it, so it needs less room
   beneath than a title that does. The heading's own bottom margin exists to
   part it from that missing subtitle, and only made the header lopsided. */
.card:has(> .card-body > [data-summary]) > .card-head { padding-bottom: 10px; }
.card:has(> .card-body > [data-summary]) > .card-head h2 { margin-bottom: 0; }

/* The faintest grey in the palette, so the card reads as something to be read
   rather than filled in — every other card on this page is white and holds
   fields. The header takes the same tint rather than staying white, or the
   two halves would look like different cards. */
.card:has(> .card-body > [data-summary]) { background: var(--surface-3); }
.card:has(> .card-body > [data-summary]) > .card-head { background: transparent; }

/* What the block will do, in plain English, kept up to date as you type. */
.action-reading {
  margin: 12px 0 0; padding: 9px 11px;
  font-size: 12px; font-style: italic; line-height: 1.5; color: var(--text-muted);
  background: var(--surface-2); border-radius: var(--radius-sm);
}
.action-reading:empty { display: none; }

.rule-set-part {
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-faint); margin: 0 0 4px;
}

/* The line explaining how rules are read. .muted carries no bottom margin
   anywhere, which left the first rule sitting flush against it. */
.rule-set-body > p.muted { margin-bottom: 11px; }

/* The fold holding the pre-rules settings. Open on elements without rules,
   closed once rules have taken over. */
.classic-settings { margin: 14px 0; }
.classic-settings > summary {
  cursor: pointer; font-size: 12px; color: var(--text-muted);
  padding: 8px 0; border-top: 1px solid var(--border);
}
.classic-settings[open] > summary { margin-bottom: 10px; }

@media (max-width: 620px) {
  .rule-joiner-slot { flex-basis: 100%; min-height: 0; }
  .rule-condition select, .rule-condition input[type="text"] { flex: 1 1 100%; }
}

/* The approver's note. Free text, so it wraps rather than stretching the row,
   and keeps a floor so it is never squeezed down to one word per line. */
.col-note { min-width: 240px; max-width: 380px; white-space: normal; }

/* The one-line summary of a message. It was taking whatever space was left over
   after every other column, which on the crowded Messages list was almost none
   — a sentence wrung out to one word a line. A floor lets it read across two
   lines instead. */
.col-summary { min-width: 200px; }

/* When leave was taken. Two dates and a dash, with the half-day mark and
   whatever reason was given underneath.

   The range is held on ONE line by the span rather than by a width set here.
   It was breaking after the dash and reading as three lines of date, in a
   99px column that needed 142px. A number typed in here would have been a
   guess at one date format: this company writes 05/10/2026, another writes
   5 Oct 2026, and the column has to fit whichever it is.

   The floor and ceiling are for what sits BELOW the range. A reason is free
   text, so it wraps — but not down to one word per line, and not so wide
   that it starves the columns beside it. Same bargain as the note column. */
.col-dates { min-width: 176px; max-width: 260px; white-space: normal; }
.leave-dates { white-space: nowrap; }

/* The Status column on the residency card, and the Add box under the table.
   One width for both, so the box you type a new status into is the same
   length as the boxes holding the ones already there and the column reads as
   continuing below the table.

   Set here rather than left to the table, which would otherwise size that
   column to whatever the longest status happens to be called — and the Add
   box, being in a form of its own underneath, could not follow it.

   The width named here is the BOX, not the column. A table cell adds the
   17px gutter either side, so the column has to ask for the box plus that —
   set them to the same number and the boxes in the table come out 34px
   narrower than the one below, which is the wrong way round from the bug it
   was meant to fix. */
:root { --residency-name-width: 309px; }
.col-residency-name { width: calc(var(--residency-name-width) + 34px); }

/* The holidays table, measured rather than guessed at.

   A date is ten characters in every format the app writes — dd/mm/yyyy,
   mm/dd/yyyy, yyyy-mm-dd — plus the button that opens the calendar. The column
   was taking 419px to hold that and the occasion beside it had 375px, which is
   backwards: the name of a holiday is the part that varies.

   170px is the number, and it has 10px of slack in it: at 160 the date still
   fits and at 140 it is clipped.

   Actions shrinks to what it holds for the same reason. Left to itself it took
   300px for one small button, and every pixel of that came out of the occasion.
   With both pinned the occasion goes from 375px to 710px. */
/* Only while the table is still a table. Under 620px every table on the site
   becomes stacked cards, and there "table, thead, tbody, tr, td" sets width to
   100% — which a class beats, being the more specific selector. Left
   unguarded these two squeezed the stacked Date row to 170px and collapsed the
   stacked Actions row to 5px, crushing the buttons in it. */
@media (min-width: 621px) {
  /* 182 rather than 170: the date box inside sits 34px narrower than its column
     once the cell padding is off, and at 170 it had 88px for its text. That is
     enough for DD/MM/YYYY at 82 and not enough for DD MMM YYYY at 92 — so a
     company writing its dates 16 Aug 2026 lost the end of the hint. The twelve
     pixels come out of Occasion, which has the whole rest of the row. */
  .col-holiday-date { width: 182px; }
  .col-holiday-actions { width: 1%; white-space: nowrap; }
}
.filters .field:has(#residency_name) { width: var(--residency-name-width); }
.filters .field:has(#residency_name) input { width: 100%; }

@media (max-width: 620px) {
  /* Tables fold into stacked cards on a phone, so fixed widths do not apply. */
  .col-when, .col-who, .col-snug, .col-company {
    width: auto; min-width: 0; white-space: normal;
  }
  .col-note, .col-dates, .col-summary { min-width: 0; max-width: none; }
}

/* --- Editable grid (the People page) -------------------------------------
   A table whose cells are inputs, for editing every employee in one pass. */
/* The dense grids — the ones with fields in their cells — give the fields the
   room instead of the padding. */
.grid-table td { padding: 5px 11px; }
.grid-table th { padding-left: 11px; padding-right: 11px; }
.cell-input {
  width: 100%; min-width: 5.5rem;
  padding: 4px 7px;
  font-size: 12px;
  /* Colour only: the shorthand would throw away a dropdown's arrow with it. */
  background-color: var(--surface-3);
}
.cell-num { max-width: 4.5rem; text-align: right; font-variant-numeric: tabular-nums; }
/* A dropdown in a cell sizes to its own longest choice instead of to the
   column, which is otherwise only as wide as the word it usually shows and
   would clip "Not tracked" down to "N...". */
.cell-select {
  width: auto; min-width: 0; max-width: none; font-size: 12px;
  padding: 4px var(--select-arrow-gap) 4px 8px;
}
.col-name { white-space: nowrap; font-weight: 500; }
/* A line for something switched off: its words step back, while its badges
   and its buttons keep their own colours so they stay readable and usable. */
tbody tr.row-off td { color: var(--text-faint); }
/* A line that is not saved yet: tinted, so it is plain which lines are new and
   that nothing has been kept until the page is saved. */
tbody tr.row-new td { background: var(--surface-2); }

/* The waiting line's wording leans, so the line reads as an invitation rather
   than as a row somebody started filling in and abandoned.

   SCOPED TO THIS ROW ON PURPOSE. Every name box in the table carries a
   placeholder — a real leave type whose name is cleared shows "e.g. Study
   leave" — so italicising placeholders generally would put the same slant on
   real rows and take away the very difference this is for.

   It ends by itself. Pressing Add drops the row-new class and the placeholder
   together, so a committed line is upright like its neighbours with nothing
   here needing to know about it. */
tbody tr.row-new .cell-input::placeholder { font-style: italic; }

/* A table waiting for Edit. `inert` already blocks clicks and tabbing; the
   tint is what makes "you cannot type here yet" visible rather than merely
   true, and matches how a locked Rules and Actions Set reads. */
[data-table-lock][inert] { pointer-events: none; }
[data-table-lock][inert] .cell-input,
[data-table-lock][inert] input,
[data-table-lock][inert] select {
  /* The tinted background is what says "locked"; the words stay black, so a
     line in use reads as plainly as it does anywhere else on the site. */
  background-color: var(--surface-2); color: var(--text);
  border-color: var(--border);
}
[data-table-lock][inert] .btn { opacity: .45; }

/* A field carries its own colour rather than its cell's, so a switched-off
   line has to be told separately to step back — and told with enough weight
   to outrank the locked-table rule above, which would otherwise blacken it. */
tbody tr.row-off .cell-input,
[data-table-lock][inert] tr.row-off .cell-input { color: var(--text-faint); }

/* Wide enough for a real name or job title rather than clipping after a few
   characters. A longer value still ends in an ellipsis instead of colliding
   with whatever sits next to it. */
.col-text { min-width: 136px; }
/* Sized from the select's own natural width. A <select> reserves space for its
   arrow internally, on top of any padding, so this needs more room than the
   option text alone suggests. */
.col-manager { min-width: 196px; }

/* A <select> draws its own dropdown arrow inside the box. Without room
   reserved for it the option text runs underneath the arrow. */
select.cell-input {
  padding-right: var(--select-arrow-gap);
  text-overflow: ellipsis;
}
.cell-input { text-overflow: ellipsis; }
@media (max-width: 620px) {
  .cell-num { max-width: none; text-align: left; }
  .col-name { white-space: normal; }
}

/* Spacing helpers, because inline style attributes are blocked by the
   Content-Security-Policy and are silently ignored. */
.stack-sm { margin-top: .5rem; }
.stack-md { margin-top: 1rem; }
.stack-lg { margin-top: 1.6rem; }

.row-remove {
  width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-faint);
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.row-remove:hover {
  color: var(--bad); border-color: var(--bad); background: var(--bad-bg);
}

@media (max-width: 620px) {
  .item-row { grid-template-columns: 1fr 96px 30px; gap: 5px; margin-bottom: 9px; }
}

.form-grid { display: grid; gap: 0 14px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
/* A card that lists what is there and offers a way to add another.
   Two things are going on in the numbers. A table ending a card is pulled
   flush with its edge by .table-wrap's -17px, which has to be given back
   before anything can follow it; and the space itself is padding rather than
   margin, which would collapse with the fieldset's own and leave the form
   sitting hard against the last row. */
.card-add { margin-top: 17px; padding-top: 18px; }

/* A line of explanation under a table. Same problem as .card-add above: the
   table hangs 17px past the card body, and that has to be given back before
   the words have any room of their own. */
.after-table { margin-top: 25px; }

/* The row that adds one: boxes and the button that sends them, ending on the
   same line. Laid out as three columns rather than a flex row so the file box
   can take whatever width is left over — it has no text of its own to measure,
   only a button and, once something is chosen, the name of one file. */
.card-add .filters {
  margin-bottom: 0;
  display: grid; align-items: end; gap: 9px;
  /* max-content, not auto: an auto track soaks up the leftover width and
     would stretch the button across the rest of the row. */
  grid-template-columns: minmax(0, 480px) max-content;
}
.card-add .filters .field { flex: 0 0 auto; min-width: 0; }
.card-add .filefield { width: 100%; }
/* One file at a time here, and it stays on the row with the button: a long
   name is shortened with an ellipsis rather than dropping to a second line
   and making the box twice as tall. */
.card-add .filefield,
.card-add .filefield-list { flex-wrap: nowrap; min-width: 0; }
.card-add .filepill { min-width: 0; }
.card-add .filepill-name { max-width: none; }
/* Three columns need a wide card. On a phone they stack instead. */
@media (max-width: 620px) {
  .card-add .filters { grid-template-columns: 1fr; }
}
/* The note under the file box hangs below the row rather than sitting in it.
   Left in the flow it would make that one field taller than its neighbours,
   and a row aligned on its boxes would stop ending on a single line. */
.card-add .filters .field { position: relative; }
.card-add .filters .field .hint {
  position: absolute; top: 100%; left: 0; margin-top: 3px; white-space: nowrap;
}
.card-add .filters { padding-bottom: 15px; }
/* Every box in this row is the height of an ordinary text box, so the labels
   above them sit on one line. The file box is normally taller because it has
   to hold a pill for each chosen file; here it holds one at most, so it can
   come down to the same height as everything else. */
.card-add .filefield { min-height: 31px; padding: 2px 8px 2px 2px; }
.card-add .filefield-button { padding: 3px 12px; }
.form-actions {
  display: flex; gap: 9px; align-items: center; flex-wrap: wrap;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border);
}
/* Sits at the far end of the row, away from Save. On a narrow screen the row
   wraps and the gap disappears, so it gets a little space of its own instead. */
.form-actions-end { margin-left: auto; }
@media (max-width: 560px) { .form-actions-end { margin-left: 0; margin-top: 4px; } }
fieldset { border: none; padding: 0; margin: 0 0 16px; }
fieldset legend {
  font-size: 10.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-faint); padding: 0; margin-bottom: 8px;
}

.filters {
  display: flex; gap: 9px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 14px;
}
.filters .field { margin-bottom: 0; min-width: 140px; }
/* One field wider than the rest. The employee box says "Everyone, or type a
   name", which does not fit the row's usual minimum - and a box that tells
   you it can be typed into and then cuts the sentence in half has told you
   nothing. The row already wraps, so the extra width costs nothing. */
.filters .field-roomy { min-width: 210px; }

/* A filter row whose box carries a hint under it. The hint hangs BELOW the row
   rather than sitting inside the field, so the field stays the height of its
   box and the Search button stays level with the box instead of dropping to
   the hint's line. The same trick the Add forms use for the note under a file
   box; here the hint is one line, so a little room reserved below is enough. */
.filters.filters-note { padding-bottom: 22px; }
.filters.filters-note .field { position: relative; }
.filters.filters-note .field .hint {
  position: absolute; top: 100%; left: 0; margin-top: 3px; white-space: nowrap;
}
@media (max-width: 620px) {
  /* On a phone the row stacks, so the hint goes back into the normal flow under
     the box where a long line can wrap freely rather than being held to one and
     running off the edge, and the button sits below it. Nothing floats over the
     table beneath. */
  .filters.filters-note { padding-bottom: 0; flex-direction: column; align-items: stretch; }
  .filters.filters-note .field { position: static; }
  .filters.filters-note .field .hint { position: static; white-space: normal; }
  .filters.filters-note button[type="submit"] { align-self: flex-start; }
}

/* A box that means nothing until another choice is made. Greyed and not
   typeable, so it does not invite a figure the app is going to ignore. The
   label fades with it: a bright label over a dead box reads as a fault. */
input:disabled, textarea:disabled, select:disabled {
  background: var(--surface-2);
  color: var(--text-muted);
  cursor: not-allowed;
}
/* Read-only is not the same as switched off: the box still sends its value,
   so a record does not lose what is in it. It should still look settled
   rather than waiting to be typed in.

   TEXT AREAS TOO. This named inputs only, because until Home address on My
   Profile there was no settled text area anywhere on the site. On a shut card
   the four boxes went grey and the address stayed white, which reads as one
   box you can still type in on a card you cannot. */
.field input[readonly],
.field textarea[readonly] { background: var(--surface-2); color: var(--text-muted); }

/* Except the box in front of a date picker, which is readonly for a different
   reason and was wearing the wrong clothes for it.

   It is readonly so you use the calendar rather than typing a date the app
   then has to guess the shape of. That is a way IN, not a value that has
   settled — and greyed it was pixel-identical to a switched-off box, same
   background and same text colour, differing only in the mouse cursor. A
   control you can use should not look like one you cannot.

   ONLY THE PICKERS INSIDE A FIELD, which is every one that is a filter or a
   form question. The date cells on a locked card — Public Holidays — are
   .cell-input in a table cell and never matched the rule above at all, so they
   go on reading as shut; their card is inert as well. Checked rather than
   assumed. If a locked card ever puts a date inside a .field, it would look
   editable while being inert, and this is the rule to come back to. */
.field .datefield input[readonly] { background: var(--surface); color: var(--text); }

/* A figure that is worked out rather than typed: somebody's age, or what they
   have been paid so far this year. Neither is stored, so neither can be
   changed here — but they belong beside the fields they are worked out from,
   and looking nothing like them made them read as an afterthought.
   Same label, same shape, same size as a box that can be typed in, wearing the
   grey a read-only box already wears on this card. */
.field-derived {
  width: 100%; margin: 0; padding: 7px 10px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text-muted);
  font-size: 12.5px; letter-spacing: -.008em;
  /* Set to the pixel rather than a ratio: this box has to come to exactly the
     height a text box does, so a row of them lines up along the bottom. */
  line-height: 15px; min-height: 31px;
}
.field-derived .hint { display: block; margin-top: 2px; }
.field-off label { color: var(--text-muted); opacity: .6; }
/* A search box holds a phrase — "taxi to the airport" — so it is given more
   room than a dropdown whose choices are all short. It still gives way on a
   narrow screen rather than pushing the row apart. */
.filters .field input[type=search] { width: 260px; max-width: 100%; }
/* For a row holding one choice: the field takes the width its longest option
   needs instead of the whole card. `width: auto` hands the sizing to the
   browser, which measures the options itself — no number here to go stale
   when the wording changes. The cap stops a very long option from pushing the
   button off the row. */
/* A row of its own — a card's heading with its choices beside it — sets its
   own spacing, rather than living on the sliver of space browsers leave
   between one box and the next. Rows that are also .filters keep the spacing
   that class already gives them. */
.filters-tight:not(.filters) {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
/* No floor on the width. A wide filter row gives every field the same minimum
   so the row lines up, but a tight row is the opposite idea: each control takes
   the space it needs and no more. A short dropdown left in a 140px box put 41
   pixels of nothing between it and the next label. */
.filters-tight .field { flex: 0 0 auto; max-width: 100%; min-width: 0; }
/* A dropdown standing on its own, with no label wrapper, takes what it needs.
   That is the rule the note above describes and it is unchanged. */
.filters-tight > select { width: auto; max-width: 340px; }

/* A dropdown WITH a label fills its field instead, and the reason is the other
   half of the same idea: no holes in the row.

   A field is as wide as its widest child. Where the label is shorter than the
   dropdown — “Approval”, “Decision by” — the dropdown is the widest child, so
   filling the field changes nothing at all. Four of the six tight rows are like
   that and were always right.

   The two on Payroll Settings are not. “When to create each month’s drafts” is
   201px above a dropdown reading “Off - Manual” at 113px, so the field took the
   label’s width, Save sat after the FIELD, and 89px of nothing opened between
   them — 98px on one card, 117px on the other. width:auto cannot close that:
   the space is not in the dropdown, it is beside it.

   It also stops the dropdown resizing while you use it. Left to size itself it
   is 113px on “Off - Manual” and 168px on “Day 28 of each month”, so the button
   moved as you chose. */
.filters-tight .field select { width: 100%; max-width: 340px; }
/* A text box has no content to measure — the browser would give it a default
   of roughly twenty characters — so it gets a width chosen to hold a real
   email address, and shrinks with the card on a narrow screen. Boxes standing
   on their own, without a label wrapper, are sized the same way: the row is
   what matters, not how it was built. */
.filters-tight .field input[type="email"],
.filters-tight .field input[type="text"],
.filters-tight > input[type="email"],
.filters-tight > input[type="text"] { width: 340px; max-width: 100%; }

/* Except a box for digits. The width above holds a real email address, which
   is far more room than "1,000.00" needs — and a box much wider than anything
   that can go in it reads as though something longer is expected. Picked out
   by inputmode rather than a class of its own: a box that asks the keyboard
   for a number pad has already said what goes in it. */
.filters-tight .field input[inputmode="decimal"],
.filters-tight > input[inputmode="decimal"] { width: 130px; }

/* ------------------------------ Notices ---------------------------------- */

.notice {
  border-radius: var(--radius-sm); padding: 9px 13px; margin-bottom: 14px;
  font-size: 12px; border: 1px solid transparent;
}
.notice-success { background: var(--good-bg); color: var(--good-ink); }
.notice-error   { background: var(--bad-bg);  color: var(--bad); }
.notice-warn    { background: var(--warn-bg); color: var(--warn); }
.notice-info    { background: var(--surface-2); color: var(--text); }
.notice a { color: inherit; text-decoration: underline; }
.notice strong { font-weight: 600; }
/* A note that FOLLOWS a form needs a gap above it. A notice carries a margin
   below and none above — right where several stack, wrong where one lands
   directly under the row of buttons that ends a form, which is what the two
   import mappers do. Written as "after a form" rather than on .notice itself,
   so a run of stacked notices keeps the single 14px between them instead of
   doubling it. */
form + .notice { margin-top: 20px; }

/* ---------------------- The bar that takes itself away -------------------- */
/* A success, or an ordinary note, is lifted out of the page by app.js and
   dropped in here: it floats above whatever is on screen for a few seconds and
   then goes.
   Floating rather than sitting in the page is the point of it. It is read
   wherever somebody happens to be scrolled to, and it neither pushes the page
   down when it arrives nor lets it spring back when it leaves — so a save
   never moves what somebody was looking at.
   Warnings and errors are never put here. Those stay in the page, at the top,
   until they have been dealt with. */

.flash-stack {
  position: fixed; z-index: 50;
  /* Public and mobile pages have no permanent sidebar, so their true centre is
     the centre of the window. Signed-in desktop pages get their own precise
     sidebar offset immediately below. */
  top: 14px; left: 0; right: 0; margin-inline: auto;
  width: min(30rem, calc(100% - 28px));
  display: flex; flex-direction: column; gap: 8px;
  /* The box itself is only a place to stand. Clicks pass straight through it
     to the page underneath; the messages in it take their own. */
  pointer-events: none;
}
/* The message said out loud and shown nowhere, for anybody reading the page
   with a screen reader. It is empty until app.js writes into it, and it takes
   no room at all: a region has to already be on the page for a change inside
   it to be announced. */
.flash-said {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
}

.flash-stack .notice {
  margin: 0; pointer-events: auto;
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 11px 11px 14px;
  border-color: rgba(0, 0, 0, .09);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .16);
  transition: opacity .22s ease, transform .22s ease;
}
/* On its way in, and on its way out again. Both ends are the same, so it
   arrives and leaves by the same short move. */
.flash-stack .notice.is-coming,
.flash-stack .notice.is-going { opacity: 0; transform: translateY(-9px); }

/* The × for anybody who has read it and wants it gone now. */
.flash-close {
  margin: -2px -2px 0 auto; flex: 0 0 auto;
  border: 0; background: transparent; color: inherit;
  font-size: 15px; line-height: 1; padding: 3px 5px;
  border-radius: var(--radius-sm); cursor: pointer; opacity: .55;
}
.flash-close:hover { opacity: 1; background: rgba(0, 0, 0, .06); }
.flash-close:focus-visible { opacity: 1; outline: 2px solid currentColor; outline-offset: 1px; }

/* ---------------------------- Confirm dialog ----------------------------- */
/* Our own "are you sure?" box, drawn by app.js. The browser's built-in
   confirm() is suppressed in some browsers, which silently cancelled actions. */

dialog.confirm-dialog {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  padding: 22px;
  max-width: 25rem;
  width: calc(100% - 2rem);
  box-shadow: 0 10px 40px rgba(0, 0, 0, .22);
}
dialog.confirm-dialog::backdrop {
  background: rgba(0, 0, 0, .38);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
/* Heading row: icon plus a single word, so the nature of the question reads
   before any of the detail does. */
.confirm-head { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.confirm-icon { display: grid; place-items: center; color: var(--text-muted); }
.confirm-title { margin: 0; font-size: 15.5px; font-weight: 600; }

.confirm-dialog-warn .confirm-icon { color: var(--warn); }
.confirm-dialog-warn .confirm-title { color: var(--warn); }
.confirm-dialog-warn { border-top: 3px solid var(--warn); }

.confirm-message { margin: 0 0 18px; font-size: 12.5px; line-height: 1.5; }
.confirm-message p { margin: 0 0 .7em; }
.confirm-message p:last-child { margin-bottom: 0; }
.confirm-message strong { font-weight: 600; color: var(--text); }
.confirm-actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }

/* THE SAME QUESTION, AS A PAGE.
 *
 * What somebody sees when the dialog above cannot be drawn — scripting off, or
 * a script that never arrived. It is the same question, so it is shaped like
 * the same thing: about the width of the box, and wearing the warning colour
 * along its top the way confirm-dialog-warn does.
 *
 * Narrow on purpose. Three short paragraphs across the full width of a desktop
 * page read as a notice somebody left lying about, not as a question being put
 * to you.
 */
.confirm-page {
  max-width: 34rem;
  border-top: 3px solid var(--warn);
}
.confirm-page .card-title { color: var(--warn); }
.confirm-page p { margin: 0 0 10px; line-height: 1.55; }
.confirm-page .confirm-actions { margin-top: 16px; }

/* --------------------------- Definition lists ---------------------------- */


.details {
  display: grid; gap: 0 20px;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); margin: 0;
}
.details > div { padding: var(--details-row-pad) 0; border-bottom: 1px solid var(--border); }
.details dt { font-size: 10.5px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; }
/* A field nobody has filled in. Shown rather than hidden, but clearly lighter
   so a filled record still reads as filled at a glance. */
.not-set { color: var(--text-faint); font-style: italic; }
.details dd { margin: 1px 0 0; font-size: 12px; }
/* --- A pair that reads as a sentence, not as a form ------------------------
   The ordinary details list stacks its label above its value, which is right
   for a record somebody fills in: the label names a box, and the box may be
   empty or hold a long answer.

   These do not. "Created" and a date is one short fact, and stacked it took two
   lines to say a thing that fits comfortably on one. Side by side it reads the
   way it would be spoken.

   Baseline, not centre: the label is small uppercase and the value is ordinary
   text, so lining up their MIDDLES leaves the label floating slightly high.
   Lining up the feet of the letters is what makes them look like one line. */
.details-inline > div { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.details-inline dd { margin: 0; }
/* One size for both halves of the pair. Stacked, a label a size smaller than
   its value reads as a caption above it; side by side on one line, two sizes
   read as a mistake — and the smaller one sits visibly lower, because letters
   of different sizes lined up by their feet can do nothing else.

   Bold, because on one line the label is the only thing telling the two halves
   apart. Stacked, being above the value was enough.

   Written with both class names so it wins. Plain ".details dt" is set again
   further down this file and once more inside the phone block, and a rule with
   one class name would quietly lose to whichever came last. */
.details.details-inline dt { font-size: var(--fs-sm); font-weight: 600; }
/* A row holding something to fill in sits a little lower under its label than
   a row holding something to read. */
.details dd:has(input),
.details dd:has(select) { margin-top: 4px; }

/* ------------------------------ Sign-in ---------------------------------- */

body.centred {
  display: grid; place-items: center; min-height: 100vh; padding: 24px;
  background: var(--surface-2);
}

/* --- The public site's header, on the sign-in page ------------------------- */
/*
   Somebody reading the Pricing page and pressing Log in should not feel they
   have left. So the sign-in page carries the same bar: the same wordmark, the
   same four links, in the same place.

   THE MARKUP IS COPIED, NOT SHARED, and the reason is worth knowing before
   anybody tries to tidy it. public/site.css restyles body, h1, a and .btn
   globally — it is a whole design, not a component — so linking it here would
   take the sign-in form apart. These few rules are the alternative: written
   against the app's own variables, under names nothing else uses, so the copy
   costs one header's worth of CSS rather than a second stylesheet.

   The one difference from the site's own bar: no Log in button on the right.
   It would point at the page it is already on.
*/

/* With the bar, the card centres in what is left underneath rather than in the
   whole window — otherwise the header pushes it off-centre and the page looks
   like it slipped. */
body.centred.has-public-bar {
  grid-template-rows: auto 1fr;
  place-items: stretch;
  padding: 0 24px 24px;
}
/* Clear of the bar above it. A margin rather than padding on the row, and an
   equal one top and bottom, so a SHORT card — the sign-in one — is still
   centred: the space belongs to the card, so centring accounts for it. A tall
   card, like sign-up, simply makes the row grow and the page scroll. */
body.centred.has-public-bar > .auth-card,
body.centred.has-public-bar > .auth-split { place-self: center; margin-block: 40px; }

.public-bar {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 14px 20px;
  /* The site's own column: 1120px wide with 28px either side. Matching it is
     what puts this wordmark in the same place as the one a visitor just came
     from, rather than a few pixels along. */
  width: 100%; max-width: 1120px; margin: 0 auto; padding: 22px 28px;
  border-bottom: 1px solid var(--border);
}

.public-bar-brand { display: flex; align-items: center; text-decoration: none; }
/* Sized by width, like the app's own wordmark, so a redrawn logo of different
   proportions still lands the same size. */
.public-bar-mark {
  display: block; width: 116px; aspect-ratio: 640 / 135;
  background: url("/logo.png") no-repeat center / contain;
}

.public-bar-nav {
  display: flex; align-items: center; gap: 26px;
  margin-left: 34px; margin-right: auto;
}
/* .93rem and a dark ink, both taken from the site's own bar rather than from
   what looked about right — the links sit a couple of pixels from the ones a
   visitor just clicked, and a lighter grey would read as a different bar. The
   underline on hover is the same accent the site uses to mark the page you are
   on; here no page is, because the sign-in page is not one of the four. */
/* 14.9px, NOT .93rem, which is what the site's own bar says.
   rem is relative to the root, and the two roots are not the same: the site
   leaves it at the browser's 16px, this app sets its own smaller one. Copying
   .93rem across gave 12.56px here against 14.88px there — the same number
   meaning two different sizes, which is the trap. */
.public-bar-nav a {
  font-size: 14.9px; color: var(--text); text-decoration: none;
  /* Spelled out for the same reason the size is: the two stylesheets set
     different line heights on body, and inheriting this app's made every link
     2px shorter than the site's and the whole bar 3px shallower — small, and
     exactly the sort of thing that reads as "this is a different page". */
  line-height: 1.68;
  padding: 3px 0; border-bottom: 1px solid transparent;
  transition: border-color .16s ease;
}

/* The button at the end of the bar, at the size the site's own masthead uses.

   .btn-sm is the app's small button — 12px in 4px/12px padding — and beside a
   bar whose links were deliberately set to the site's own 14.9px it reads a
   size too small. These numbers are site.css's .btn plus .btn-small, spelled
   out for exactly the reason the link size above is: the two stylesheets set
   different bases, so matching by class name would not land in the same place.

   .public-bar .btn is two classes, so it also beats the .btn-sm override in the
   narrow-screen block further down without depending on which came first. */
.public-bar .btn {
  font-size: 13.6px; line-height: 1.25; padding: 7px 15px; gap: 8px;
}
.public-bar-nav a:hover {
  text-decoration: none;
  border-bottom-color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .public-bar-nav a { transition: none; }
}

/* The same fold the site's own bar makes: the links drop to a line of their
   own under the wordmark rather than squeezing beside it. */
@media (max-width: 720px) {
  .public-bar { padding: 18px 20px; }
  .public-bar-nav {
    order: 3; width: 100%; margin-left: 0; margin-right: 0; gap: 20px;
  }
  .public-bar-nav a { font-size: 14.4px; }
}
.auth-card {
  width: 100%; max-width: 340px;
  background: var(--surface); border-radius: var(--radius); padding: 24px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 8px 30px rgba(0, 0, 0, .07);
  border: 1px solid var(--border);
}
/* The logo on the pages shown before sign-in. Larger than the one in the
   sidebar: here it is the only thing identifying the product, rather than a
   label above a menu. */
.auth-brand { margin-bottom: 16px; }
.auth-brand-logo { display: block; width: 116px; height: auto; }

.auth-card h1 { font-size: 17px; }

/* Sign-up asks for six things and a choice, where signing in asks for two.
   Same card, more room; the sign-in page does not pass wide and is
   untouched. */
.auth-card-wide { max-width: 460px; }

/* Sign-up is two cards side by side: the form, and what it comes to.

   The summary is the narrower of the two and does not stretch, because a column
   of short rows looks stranded in a wide box. align-items:start keeps it at the
   top rather than centring it against a form five times its height. */
.auth-split {
  display: grid;
  grid-template-columns: minmax(0, 460px) minmax(0, 300px);
  gap: 24px;
  align-items: start;
  width: 100%;
  max-width: 784px;
}
/* Under the width of both plus the gap there is no room for two, and a summary
   squeezed to nothing is worse than one sitting under the form it describes. */
@media (max-width: 860px) {
  .auth-split { grid-template-columns: minmax(0, 1fr); max-width: 460px; }
}

.auth-summary h2 { font-size: 15px; margin: 0 0 12px; }
.summary-rows { margin: 0; display: flex; flex-direction: column; gap: 8px; }
.summary-row { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }
.summary-row dt { font-size: 12.5px; color: var(--text-muted); }
.summary-row dd {
  margin: 0;
  font-size: 12.5px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* The line showing the arithmetic sits back from the facts above it: it is
   there to be checked, not read. */
.summary-working dt,
.summary-working dd { color: var(--text-muted); font-size: 11.5px; }

.summary-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin: 14px 0 16px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.summary-total-label { font-size: 12.5px; color: var(--text-muted); }
.summary-total-figure {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

/* Disabled because there is nothing behind it yet. Saying so in the colour and
   the cursor is better than a button that looks live and does nothing. */
.auth-summary .btn[disabled] { opacity: 0.55; cursor: not-allowed; }
.summary-note { margin: 10px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--text-muted); }

/* The plan chooser. A fieldset so the three are announced as one question,
   with its own borders taken off — they fight the cards inside it. */
.tiers { border: 0; padding: 0; margin: 0 0 4px; min-width: 0; }
.tiers legend {
  padding: 0; margin-bottom: 8px;
  font-size: var(--text-sm); font-weight: 600; color: var(--text-label);
}
.tier {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 13px; margin-bottom: 8px; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius);
  transition: border-color .16s ease, background-color .16s ease;
}
.tier:last-of-type { margin-bottom: 0; }
.tier:hover { border-color: var(--border-strong); }
/* The chosen one, keyed to the input rather than a class, so it is right
   without a line of script. */
.tier:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.tier:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.tier input { margin: 3px 0 0; flex: 0 0 auto; accent-color: var(--accent); }
.tier-body { display: flex; flex-direction: column; gap: 2px; }
.tier-name { font-size: var(--text-sm); font-weight: 600; }
/* The price sits on the same line as the period, a step quieter, so the
   choice reads as "yearly, and here is what that costs". */
.tier-price { font-weight: 500; color: var(--text-muted); margin-left: 4px; }
.tier-blurb { font-size: 12px; line-height: 1.45; color: var(--text-muted); }

/* The form starts a clear step below whatever introduces it.

   It used to start flush: the line "Use the work email address your HR team set
   up for you" and the words "Email address" underneath it had nothing between
   them at all, so the sentence read as though it belonged to the box.

   16px, which is the figure this card already keeps twice — under the wordmark
   and above the closing line. Using it a third time brackets the form with the
   same space above and below rather than introducing a number of its own. */
.auth-card form { margin-top: 16px; }

/* The single action in these cards sits in the middle of its row. A card this
   narrow reads as a column of centred things — the fields, then the button —
   and left-aligned it hangs off one end.

   display:flex rather than inline-flex so the element is block-level and the
   auto margins have something to divide; width:fit-content so it does not then
   stretch across the card. .btn already centres its own contents, so an icon
   inside one still sits right.

   Direct children only, which is what keeps this to the two cards that have a
   single action — Log in and Create my company. Change password puts its
   button in a .form-actions row and is left alone. */
.auth-card form > .btn {
  display: flex; width: fit-content; margin-inline: auto;
}

.auth-foot { margin-top: 16px; font-size: var(--text-sm); color: var(--text-muted); text-align: center; }

/* ------------------------------ Payslip ---------------------------------- */
/* Stays on white paper in both themes — it is the printed artefact.
   Sections already run down the page: Employee, Gross salary, Allowances,
   Deductions, then the totals. */

.print-body { background: var(--surface-2); padding: 16px; }
.payslip {
  max-width: 780px; margin: 0 auto; background: #fff; color: #1d1d1f;
  padding: 26px 28px; border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  font-size: 11.5px; line-height: 1.45; letter-spacing: -.006em;
}
.payslip-head {
  display: flex; justify-content: space-between; gap: 18px; flex-wrap: wrap;
  border-bottom: 1px solid #d2d2d7; padding-bottom: 12px; margin-bottom: 16px;
}
.payslip-head h1 {
  font-size: 10px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: #6e6e73; margin: 0 0 4px;
}
.payslip-co { font-size: 11.5px; color: #6e6e73; }
.payslip-co strong { font-size: 12.5px; color: #1d1d1f; font-weight: 600; letter-spacing: -.012em; }
.payslip-meta { text-align: right; font-size: 11px; color: #1d1d1f; }
.payslip-meta strong {
  display: block; font-size: 9px; font-weight: 600; color: #86868b;
  text-transform: uppercase; letter-spacing: .04em;
}
/* The label above is already a block, so the markup's <br> would double the gap. */
.payslip-meta br { display: none; }
/* Spacing between the two meta blocks, so it does not depend on an inline
   style attribute (this app's CSP does not allow unsafe-inline styles). */
.payslip-meta > div + div { margin-top: 8px; }

.payslip table { font-size: 11.5px; color: #1d1d1f; font-variant-numeric: tabular-nums; }
.payslip table th {
  background: transparent; color: #86868b; padding: 0 0 5px;
  border-bottom: 1px solid #d2d2d7;
}
.payslip table td { padding: 5px 0; border-bottom: 1px solid #f0f0f2; color: #1d1d1f; }
.payslip table tbody tr:hover { background: transparent; }
.payslip .table-wrap { margin: 0; overflow: visible; }
.payslip .table-total td { border-top: 1px solid #d2d2d7; border-bottom: none; font-weight: 600; }

.payslip-section { margin-bottom: 15px; }
/* Twelve, asked for, and larger than the 11.5px lines underneath it — so a
   section heading on a payslip is now the biggest thing on the page after the
   net pay figure. It reads as a heading rather than a caption, which is the
   point; it also means the payslip is taller, and on paper the gap is wider
   still because everything AROUND these drops to 10.5px when printed while
   this stays at 12. */
.payslip-section h2 {
  font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
  color: #86868b; font-weight: 600; border-bottom: none;
  padding-bottom: 0; margin-bottom: 5px;
}
/* The employer-contributions block sits after the net-pay line and needs a
   clear gap; the template asks for this with an inline style, which the CSP
   may strip, so it is set here too. */
.payslip-net + .payslip-section { margin-top: 22px; }

.payslip-net {
  display: flex; justify-content: space-between; align-items: baseline;
  background: transparent; border-top: 1px solid #1d1d1f;
  padding: 11px 0 0; border-radius: 0; margin-top: 14px;
  font-size: 15.5px; font-weight: 600; letter-spacing: -.016em;
  font-variant-numeric: tabular-nums;
}
.payslip-foot {
  margin-top: 20px; font-size: 9px; color: #86868b;
  border-top: 1px solid #f0f0f2; padding-top: 10px;
}
/* align-items, and it is load-bearing rather than tidiness. A row of flex
   items with nothing said about their height are all stretched to match the
   tallest — so the DRAFT label beside the buttons was drawn as a tall blue
   block rather than a pill, its lettering floating in the middle of it. The
   other two rows of this kind, .card-actions and .page-actions, both centre
   their contents already; this one was the odd one out. */
.print-actions {
  max-width: 780px; margin: 0 auto 12px;
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
}
.payslip .details dt { color: #86868b; }
.payslip .details dd { color: #1d1d1f; }
.payslip .details > div { border-bottom-color: #f0f0f2; }

/* ---------------------------------------------------------------------------
   The help page.

   The only page in the app that is mostly prose, so it is the only one that
   needs paragraph and list rules. Everything else is a form or a table.

   The prose used to stop at 68 characters, which is the width a line of text
   is easiest to read at. Asked for full width instead: half an empty card
   beside every paragraph read as a page that had failed to fill itself in,
   and on this page that impression costs more than the extra line length.
   The card is what holds the text in now.
   --------------------------------------------------------------------------- */

.help-topic .card-body > p {
  margin: 0 0 10px;
  font-size: 13px; line-height: 1.62; color: var(--text);
}
.help-topic .card-body > ul {
  margin: 0 0 10px; padding-left: 18px;
  font-size: 13px; line-height: 1.62; color: var(--text);
}
.help-topic .card-body > ul li { margin-bottom: 5px; }
.help-topic .card-body > ul li:last-child { margin-bottom: 0; }
.help-topic .card-body > :last-child { margin-bottom: 0; }

/* A heading landing flush against the top of the window reads as though the
   page has been cut off, so an anchor stops a little short of it. */
.help-topic { scroll-margin-top: 16px; }

/* The contents list, grouped by the same words the left-hand menu uses: You,
   Your Team, Administration, Support — behind a General group for the one
   topic that is about the menu rather than in it.

   A GRID RATHER THAN COLUMNS, which the flat list used. Flowing groups as
   columns starts each heading wherever the group above it happened to end,
   so the headings stagger down the card and stop reading as headings. A grid
   puts every one on the same line and lets Administration simply be a taller
   cell — which it always will be, holding most of the page.

   It still collapses to one column on a phone, and still without a media
   query: auto-fit keeps as many columns as fit at a readable width. */
.help-jump { display: flex; flex-direction: column; gap: 14px; }
.help-jump-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(175px, 1fr));
  gap: 14px 22px;
  align-items: start;
}
.help-jump-heading {
  margin: 0 0 6px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-muted);
}
.help-jump-list { margin: 0; padding: 0; list-style: none; }
.help-jump-list li { margin-bottom: 5px; font-size: 13px; }
.help-jump-list li:last-child { margin-bottom: 0; }

/* Searching the page.

   The box sits above the contents list and takes its place while there is
   something typed: browsing by category and reading results are two answers
   to the same question, and showing both at once would only ask the reader to
   work out which half to look at. */
.help-search { margin: 0 0 14px; }
.help-search-label {
  display: block; margin-bottom: 5px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-muted);
}
.help-search-box {
  width: 100%; max-width: 420px; padding: 7px 10px;
  font: inherit; font-size: 13px; color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
}
.help-search-box:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.help-search-count { margin: 6px 0 0; font-size: 12px; }

.help-results { margin: 0; padding: 0; list-style: none; }
.help-result + .help-result { margin-top: 4px; }
/* The whole row is the target, not just the sentence: a result is one thing
   to press, and a link the width of its own text is a smaller target than the
   row it sits in looks like it should be. */
.help-result a {
  display: block; padding: 8px 10px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  text-decoration: none; color: inherit;
}
.help-result a:hover { background: var(--surface-2); }
.help-result a:focus-visible {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.help-result-said {
  display: block; font-size: 13px; line-height: 1.55; color: var(--text);
}
.help-result-said mark {
  background: var(--accent-soft); color: inherit;
  border-radius: 2px; padding: 0 1px;
}
/* Where the sentence came from: its section, then the category that section
   sits under — the same two words the contents list would have used to get
   there by hand. */
.help-result-where {
  display: block; margin-top: 2px;
  font-size: 11px; color: var(--text-muted);
}
.help-result-where strong { color: var(--accent); font-weight: 600; }

/* A sub-heading inside a help topic. Rules and Actions is the only topic long
   enough to need breaking up; everything else is short enough to read straight
   through, and a heading over three paragraphs is furniture for its own sake. */
.help-topic .card-body > h3 {
  margin: 18px 0 8px;
  font-size: 13px; font-weight: 600; color: var(--text);
}
.help-topic .card-body > h3:first-child { margin-top: 0; }

/* A worked example: what somebody typed, and how the app read it back.
   The two lines only mean anything COMPARED, so they are set apart from the
   prose and labelled, rather than left as two more paragraphs to line up by
   eye. Monospaced, because a bracket that does not sit under the word it
   encloses is the one thing this example cannot afford to get wrong. */
.help-topic .card-body > .help-example {
  margin: 0 0 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
}
.help-topic .help-example p {
  margin: 0 0 6px; font-size: 12.5px; line-height: 1.6; color: var(--text);
}
.help-topic .help-example p:last-child { margin-bottom: 0; }
.help-topic .help-example .lead {
  display: block; color: var(--text-muted); font-weight: 600;
  font-size: 10.5px; letter-spacing: 0.07em; text-transform: uppercase;
}
.help-topic .help-example .said {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}
/* The example that is a warning rather than an illustration. Red, because it
   is showing a rule quietly catching fewer people than it looks like it does,
   which is the one mistake on this page that costs somebody money. */
.help-topic .card-body > .help-example.is-warning { border-left-color: var(--bad); }

/* ---------------------------------------------------------------------------
   Reports.

   Two small things only. The report page itself is built entirely out of
   furniture that already exists — a card, a row of filters, a table with a
   ruled-off total — which is the point of having one shell rather than nine
   pages: there is almost nothing here to keep in step with anything else.
   --------------------------------------------------------------------------- */

/* The list of reports: the name, then what it answers, on the same line where
   there is room and on the next line on a phone. */
/* A DETAILS LIST AT THE TOP OF A CARD SAT TOO FAR DOWN. Asked about on the
   payroll item History card, and true wherever this shape occurs: the card body
   keeps its own room above whatever comes first, and then the list's first line
   keeps its room too — so the word "Created" sat 18px under the dividing line
   while the lines below it were 10px apart. Nearly double, for no reason
   anybody chose.

   Pulled up by exactly one row's worth, so the first line starts where the
   card's padding puts everything else and the lines below are untouched. The
   figure comes from the same place the rows take theirs, so one cannot be
   trimmed without the other.

   Only when the list is the FIRST thing in the body. A details list further
   down has something above it it should keep its distance from. */
.card-body > .details:first-child { margin-top: calc(-1 * var(--details-row-pad)); }

/* --- The Reports page: a square for each report -------------------------
   Asked for. It was a list of lines — name, then a sentence beside it —
   inside a card per section. It is now a square card each, with room for a
   drawing on top and the name and the sentence underneath, still laid out
   inside the section's own card.

   AUTO-FILL, NOT A COLUMN COUNT. The four sections hold three, three, seven
   and one report between them; a fixed number of columns would leave Claims
   with one square and six empty cells, and would break at some window width
   nobody thought to try. This asks for "as many as fit, none narrower than
   this", so every section fills its own card at every width. */
.report-grid {
  margin: 0; padding: 0; list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
  gap: 11px;
}

/* SQUARE, and the whole of it is the link.
   aspect-ratio makes the box square from its own width, so the squares stay
   square as the grid reflows rather than being pinned to one size. */
.report-tile {
  aspect-ratio: 1;
  display: flex; flex-direction: column;
  padding: 13px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--text); text-decoration: none;
  /* A long report name or sentence must not push the square out of shape or
     spill over its own edge. It is clipped instead — see the sentence below,
     which is limited to the lines that fit. */
  overflow: hidden;
  transition: border-color .15s ease, background .15s ease;
}
.report-tile:hover {
  background: var(--surface-2); border-color: var(--border-strong);
  text-decoration: none;
}
/* The keyboard needs to see where it is, and a hover colour alone does not
   show that: this is a link, and the ring is what says which one is chosen. */
.report-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* THE ROOM FOR THE DRAWING. A panel of its own so that whatever goes in it —
   the line drawings shipped now, or a picture later — sits in the same place
   and at the same size on all fourteen squares. */
.report-tile-art {
  flex: 0 0 auto; height: 62px;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 10px;
  border-radius: 8px;
  background: var(--surface-2);
  color: var(--accent);
}
.report-tile:hover .report-tile-art { background: var(--surface-3); }
.report-tile-art svg { display: block; }

.report-tile-name {
  font-weight: 600; letter-spacing: -.01em; font-size: var(--fs-base);
  color: var(--text); margin-bottom: 3px;
}
/* Cut off at whatever number of lines is left over rather than allowed to run
   past the bottom edge. Every sentence on this page fits at the sizes the grid
   actually produces — checked — and this is the guard for the day somebody
   writes a longer one. */
.report-tile-blurb {
  font-size: var(--fs-sm); line-height: 1.4; color: var(--text-muted);
  overflow: hidden;
}

/* On a phone the squares would be as wide as the screen and therefore as tall,
   which is a lot of scrolling for fourteen of them. Two to a row instead. */
@media (max-width: 620px) {
  .report-grid { grid-template-columns: repeat(auto-fill, minmax(136px, 1fr)); gap: 9px; }
  .report-tile { padding: 10px; }
  .report-tile-art { height: 48px; margin-bottom: 7px; }
  .report-tile-art svg { width: 24px; height: 24px; }
  /* AND NOT SQUARE HERE, which is a deliberate exception to the whole point of
     these cards.

     Two to a row on a phone leaves about 149px each. A square is then 149px
     tall, and after the drawing and the name there is room for three lines of
     the sentence underneath — so eleven of the fourteen were cut off partway
     through a word. "Every payslip, one row each: basic, overtime, extras,
     deductions, take-" and then nothing.

     The sentence is the only thing that says what a report DOES, and on a touch
     screen there is no pointing at it to see the rest. So the height follows
     the words here. The grid still makes every card in a row the same height,
     so it reads as a grid rather than as a ragged pile.

     The alternative was one square per row: 330px wide and therefore 330px
     tall, fourteen times, which is a very long page to scroll for a menu. */
  .report-tile { aspect-ratio: auto; }
}

.reports-page {
  display: grid;
  gap: 16px;
}
.reports-page .card,
.reports-page .page-head { margin-bottom: 0; }

.reports-hero {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: linear-gradient(135deg, var(--accent-soft), var(--surface) 62%);
  box-shadow: var(--shadow);
}
.reports-hero-copy { max-width: 680px; }
.reports-eyebrow {
  margin: 0 0 5px;
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.reports-hero h1 {
  margin: 0;
  letter-spacing: -.025em;
}
.reports-hero-copy > p:last-child {
  max-width: 66ch;
  margin: 7px 0 0;
  color: var(--text-muted);
  font-size: var(--fs-sm);
}
.reports-period-form {
  flex: 0 0 300px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}
.reports-period-form > label {
  display: block;
  margin: 0 0 6px;
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: 650;
}
.reports-period-controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 7px;
}
.reports-period-controls select { min-width: 0; }
.reports-freshness {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 8px 1px 0;
  color: var(--text-faint);
  font-size: var(--fs-xs);
}
.reports-freshness > span {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--good);
}

.report-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 11px;
}
.report-kpi {
  position: relative;
  min-width: 0;
  display: grid;
  grid-template-columns: 42px minmax(0, 1fr);
  align-items: center;
  gap: 11px;
  min-height: 112px;
  padding: 15px;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  box-shadow: var(--shadow);
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.report-kpi:hover {
  border-color: var(--border-strong);
  color: var(--text);
  text-decoration: none;
  transform: translateY(-1px);
  box-shadow: var(--shadow);
}
.report-kpi:focus-visible,
.reports-attention-item:focus-visible,
.reports-recent-item:focus-visible,
.reports-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.report-kpi-icon,
.reports-recent-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 10px;
  background: var(--accent-soft);
  color: var(--accent);
}
.report-kpi-icon svg,
.reports-recent-icon svg {
  width: 23px;
  height: 23px;
}
.report-kpi-copy {
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding-right: 15px;
}
.report-kpi-label {
  overflow: hidden;
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: 650;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.report-kpi-value {
  overflow: hidden;
  margin-top: 3px;
  color: var(--text);
  font-size: clamp(19px, 1.7vw, 23px);
  font-weight: 720;
  letter-spacing: -.035em;
  line-height: 1.12;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.report-kpi-hint {
  overflow: hidden;
  margin-top: 5px;
  color: var(--text-faint);
  font-size: var(--fs-xs);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.report-kpi-arrow,
.reports-attention-arrow {
  color: var(--text-faint);
  font-size: 16px;
}
.report-kpi-arrow {
  position: absolute;
  top: 50%;
  right: 12px;
  transform: translateY(-50%);
}
.report-kpi-missing { border-style: dashed; }
.report-kpi-missing .report-kpi-value,
.report-kpi-trouble .report-kpi-value {
  font-size: var(--fs-lg);
  letter-spacing: -.01em;
}

.reports-overview-grid {
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, 1.75fr) minmax(280px, .85fr);
  gap: 11px;
  align-items: stretch;
}
.reports-overview-grid-single { grid-template-columns: minmax(0, 1fr); }
.reports-panel {
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--surface);
  box-shadow: var(--shadow);
}
.reports-panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 16px 12px;
  border-bottom: 1px solid var(--border);
}
.reports-panel-head h2,
.reports-library-head h2 {
  margin: 0;
  color: var(--text);
  font-size: var(--fs-lg);
  letter-spacing: -.015em;
}
.reports-panel-head p,
.reports-library-head p {
  margin: 3px 0 0;
  color: var(--text-muted);
  font-size: var(--fs-xs);
}
.reports-panel-head > a {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 650;
  text-decoration: none;
}
.reports-panel-head > a:hover { text-decoration: underline; }
.reports-trend-wrap { padding: 9px 12px 0; }
.reports-trend {
  display: block;
  width: 100%;
  height: auto;
  min-height: 210px;
}
.reports-trend-axis {
  stroke: var(--border-strong);
  stroke-width: 1;
}
.reports-trend-bar { fill: var(--accent); }
.reports-trend-bar-missing {
  fill: var(--surface-2);
  stroke: var(--border-strong);
  stroke-width: 1.5;
  stroke-dasharray: 5 4;
}
.reports-trend-value {
  fill: var(--text-muted);
  font-size: 10px;
  font-weight: 650;
}
.reports-trend-missing-label {
  fill: var(--text-faint);
  font-size: 10px;
  font-weight: 650;
}
.reports-trend-month {
  fill: var(--text-muted);
  font-size: 11px;
  font-weight: 650;
}
.reports-trend-year {
  fill: var(--text-faint);
  font-size: 9px;
}
.reports-chart-note {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  padding: 0 16px 14px;
  color: var(--text-faint);
  font-size: var(--fs-xs);
}
.reports-chart-note > span {
  width: 18px;
  height: 10px;
  border: 1px dashed var(--border-strong);
  border-radius: 3px;
  background: var(--surface-2);
}

.reports-attention-list { display: grid; }
.reports-attention-item {
  display: grid;
  grid-template-columns: 11px minmax(0, 1fr) 14px;
  align-items: center;
  gap: 10px;
  padding: 13px 15px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
  transition: background .15s ease;
}
.reports-attention-item:last-child { border-bottom: 0; }
.reports-attention-item:hover {
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
}
.reports-attention-item strong,
.reports-attention-item small { display: block; }
.reports-attention-item strong {
  font-size: var(--fs-sm);
  line-height: 1.35;
}
.reports-attention-item small {
  margin-top: 3px;
  color: var(--text-muted);
  font-size: var(--fs-xs);
  line-height: 1.4;
}
.reports-attention-mark {
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: var(--text-faint);
}
.reports-attention-warn .reports-attention-mark { background: var(--warn); }
.reports-attention-good .reports-attention-mark { background: var(--good); }

.reports-recent-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.reports-recent-item {
  min-width: 0;
  display: grid;
  grid-template-columns: 42px minmax(0, 1fr) 14px;
  align-items: center;
  gap: 11px;
  padding: 14px 16px;
  border-right: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
}
.reports-recent-item:last-child { border-right: 0; }
.reports-recent-item:hover {
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
}
.reports-recent-copy { min-width: 0; }
.reports-recent-copy strong,
.reports-recent-copy small {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}
.reports-recent-copy strong {
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.reports-recent-copy small {
  display: -webkit-box;
  margin-top: 3px;
  color: var(--text-muted);
  font-size: var(--fs-xs);
  line-height: 1.35;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.reports-library {
  display: grid;
  gap: 12px;
  padding-top: 3px;
}
.reports-library-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
}
.reports-library-search {
  display: flex;
  align-items: center;
  gap: 7px;
}
.reports-library-search input[type="search"] { width: min(260px, 36vw); }
.reports-clear-search {
  color: var(--text-muted);
  font-size: var(--fs-xs);
}
.reports-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}
.reports-chip {
  display: inline-flex;
  align-items: center;
  min-height: 31px;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: 650;
  text-decoration: none;
}
.reports-chip:hover {
  border-color: var(--border-strong);
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
}
.reports-chip-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.report-category-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 11px;
  align-items: start;
}
.report-category-grid > .card { margin-bottom: 0; }
.report-category-grid > .card .card-body { padding: 0; }
.report-category-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 25px;
  height: 25px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: 700;
}
.report-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
}
.report-grid > li + li { border-top: 1px solid var(--border); }
.report-tile {
  aspect-ratio: auto;
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) 15px;
  align-items: center;
  gap: 11px;
  min-height: 68px;
  padding: 10px 13px;
  border: 0;
  border-radius: 0;
  background: transparent;
  overflow: visible;
}
.report-tile:hover {
  background: var(--surface-2);
  border-color: transparent;
}
.report-tile-art {
  width: 38px;
  height: 38px;
  margin: 0;
  border-radius: 9px;
  background: var(--accent-soft);
}
.report-tile:hover .report-tile-art { background: var(--surface-3); }
.report-tile-art svg {
  width: 21px;
  height: 21px;
}
.report-tile-copy {
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.report-tile-name {
  display: block;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.report-tile-blurb {
  display: block;
  margin-top: 2px;
  overflow: hidden;
  color: var(--text-muted);
  font-size: var(--fs-xs);
  line-height: 1.35;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.report-tile-chevron {
  color: var(--text-faint);
  font-size: 15px;
}
.reports-export-note {
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: var(--fs-xs);
}
.reports-export-note strong { color: var(--text); }

@media (max-width: 1060px) {
  .report-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .reports-overview-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 760px) {
  .reports-hero {
    align-items: stretch;
    flex-direction: column;
    padding: 17px;
  }
  .reports-period-form { flex-basis: auto; }
  .reports-recent-grid,
  .report-category-grid { grid-template-columns: minmax(0, 1fr); }
  .reports-recent-item {
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .reports-recent-item:last-child { border-bottom: 0; }
  .reports-library-head {
    align-items: stretch;
    flex-direction: column;
  }
  .reports-library-search input[type="search"] { width: 100%; }
}

@media (max-width: 520px) {
  .report-kpis { grid-template-columns: minmax(0, 1fr); }
  .report-kpi { min-height: 96px; }
  .reports-library-search {
    align-items: stretch;
    flex-wrap: wrap;
  }
  .reports-library-search input[type="search"] { flex: 1 1 180px; }
  .reports-trend-wrap {
    overflow-x: auto;
    direction: rtl;
  }
  .reports-trend {
    min-width: 680px;
    direction: ltr;
  }
  .report-tile { padding: 10px 11px; }
}

@media (prefers-reduced-motion: reduce) {
  .report-kpi { transition: none; }
  .report-kpi:hover { transform: none; }
}

/* The printed sheet. White paper in both themes, like the payslip, because it
   is the thing that comes out of the printer rather than a page on screen. */
.report-print {
  max-width: 980px; margin: 0 auto; background: #fff; color: #1d1d1f;
  padding: 26px 28px; border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  font-size: 12px; line-height: 1.45;
}
.report-print-head {
  border-bottom: 1px solid #d2d2d7; padding-bottom: 12px; margin-bottom: 16px;
}
.report-print-head h1 {
  margin: 0 0 4px; font-size: 17px; font-weight: 600; letter-spacing: -.018em;
}
.report-print-co {
  margin: 0 0 4px; font-size: 10px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: #6e6e73;
}
.report-print-sub { margin: 0; font-size: 11.5px; color: #6e6e73; max-width: 78ch; }
.report-print .table-wrap { border: none; }
.report-print th { color: #6e6e73; border-bottom-color: #d2d2d7; }
.report-print td { border-bottom-color: #f0f0f2; color: #1d1d1f; }
.report-print .table-total td { border-top-color: #d2d2d7; }
/* The row of buttons above the sheet, kept to the sheet's own width. */
.print-body:has(.report-print) .print-actions { max-width: 980px; }

/* A bar chart, drawn out of one rectangle per row.

   NO LIBRARY AND NO SCRIPTING, on purpose — see views/report.js for why the
   bars are SVG rather than divs with a width written into the markup.

   The row is a three-part grid: the label, the bar, the figure. The label
   column is fixed so every bar starts at the same place and the eye can read
   down them; the figure column is fixed so the numbers line up on the right. */
.chart { margin: 0 0 16px; }
.chart-title {
  margin: 0 0 9px; font-size: 12px; font-weight: 600; color: var(--text-muted);
  letter-spacing: .02em;
}
.chart-row {
  display: grid; grid-template-columns: minmax(90px, 150px) 1fr minmax(60px, auto);
  align-items: center; gap: 10px; margin-bottom: 5px;
}
.chart-row:last-child { margin-bottom: 0; }
.chart-label {
  font-size: 12.5px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chart-track {
  display: block; height: 15px; background: var(--surface-2);
  border-radius: 3px; overflow: hidden;
}
/* The SVG fills its track and is stretched to fit, which is exactly what is
   wanted for a plain rectangle and exactly why no text is put inside it. */
.chart-svg { display: block; width: 100%; height: 100%; }
.chart-fill { fill: var(--accent); }
/* A fall rather than a rise. Measured by its size like any other bar, but
   coloured so nobody reads a long bar as good news. */
.chart-fill-down { fill: var(--tint-warn-ink, #b4530a); }
.chart-value {
  font-size: 12.5px; font-variant-numeric: tabular-nums; text-align: right; color: var(--text);
}

@media (max-width: 560px) {
  /* Too narrow for three columns. The label goes above its own bar rather
     than being squeezed to three letters. */
  .chart-row { grid-template-columns: 1fr auto; }
  .chart-label { grid-column: 1 / -1; margin-bottom: 1px; }
}

/* A little chooser that drops out of a button.

   A NATIVE <details>, NOT A SCRIPTED DIALOG. It opens and shuts by itself in
   every browser with no scripting at all, which is the only way a button like
   this can honestly exist on a site that works with scripting switched off.
   Everything below is appearance; none of it is behaviour.

   The summary is dressed as a button, so its default triangle has to go — and
   it has to go twice, once by the standard property and once by the WebKit
   pseudo-element that ignores it. */
.popover { position: relative; display: inline-block; }
.popover > summary { list-style: none; cursor: pointer; }
.popover > summary::-webkit-details-marker { display: none; }
.popover > summary::marker { content: ''; }

.popover-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  width: max-content; max-width: min(280px, calc(100vw - 32px));
  padding: 12px; text-align: left;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: 0 8px 24px rgba(0, 0, 0, .14);
}
.popover-title { margin: 0 0 8px; font-size: var(--fs-sm); font-weight: 600; color: var(--text); }
.popover-panel select { width: 100%; }
.popover-note {
  margin: 8px 0 10px; font-size: var(--fs-xs); line-height: 1.45; color: var(--text-muted);
}

/* On a phone the card head wraps and there is no room to hang a panel off the
   right of a button, so it becomes an ordinary block under it. */
@media (max-width: 620px) {
  .popover { display: block; }
  .popover-panel {
    position: static; width: auto; max-width: none; margin-top: 8px; box-shadow: none;
  }
}

@page { margin: 14mm; }

@media print {
  .print-actions, .no-print { display: none !important; }
  .print-body { background: #fff; padding: 0; }
  .payslip {
    box-shadow: none; border: none; max-width: none; padding: 0; border-radius: 0;
    font-size: 10.5px;
  }
  .report-print {
    box-shadow: none; border: none; max-width: none; padding: 0; border-radius: 0;
    font-size: 9.5px;
  }
  /* A long report runs over several sheets, and a table whose headings only
     appear on the first one is unreadable from the second onwards. */
  .report-print thead { display: table-header-group; }
  /* Printers drop background colours unless asked. A chart printed as a row of
     empty outlines is worse than no chart, so these are asked for by name. */
  .chart-track { background: #ececef; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .chart-fill { fill: #1f6feb; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .chart-fill-down { fill: #b4530a; }
  /* A report that does not add up says so, and it has to still say so on
     paper. Printers drop background colours unless asked, so this is asked for
     by name the way the chart colours above are: a warning printed in the same
     grey as the notes beside it is a warning somebody reads straight past. */
  .report-print .notice-warn {
    background: var(--warn-bg); color: var(--warn); border: 1px solid currentColor;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
  .report-print tr { break-inside: avoid; }
  a[href]:after { content: none !important; }
  body { font-size: 10.5px; background: #fff; }
}

/* -------------------------- The sidebar, as a rail ------------------------ */
/*
   Icons only, the words folded away — and back again while the mouse is on it.
   ABOVE 900px ONLY. Below that the sidebar is already a drawer that slides in
   over the page, which is the same problem answered a better way for a small
   screen — a 62px rail of icons there would cover the page it is meant to
   leave room for.

   THERE IS ONE STATE, SO NOTHING SAYS WHICH ONE IT IS IN. This began as a
   preference: a button under the brand, a cookie remembering the answer, and a
   class on the shell for the pages to read. All of that has gone. The rail
   opening under the pointer turned out to be the whole of what was wanted, and
   a button offering the state you were already in was furniture.

   The words are made invisible, NOT display:none. A menu of twelve unnamed
   pictures is what display:none would leave for anybody using a screen reader,
   and the links would lose their names entirely. Out of sight, they are still
   read, still searched by the browser's find, and still what the title attribute echoes to
   a mouse resting on the icon.

   THE RAIL FLOATS OVER THE PAGE RATHER THAN SITTING BESIDE IT.
   Widening a column that the page is laid out against would reflow everything
   to its right — every time the mouse crossed the rail, and again when it
   left. Text would jump under the cursor on the way past. So the sidebar is
   taken out of the flow and the shell keeps a 62px
   gutter for it: the rail always occupies exactly 62px of the layout, and the
   opened panel is drawn on top of the page instead of shoving it.

   IT ALSO OPENS ON FOCUS, not only on hover. Tabbing into a rail of unlabelled
   icons would be the same problem a mouse has, without the way out.

   The whole of this is :hover and :focus-within — no script, no timers, and
   no state to get stuck, and it works with scripting off.
*/
@media (min-width: 901px) {
  .shell { padding-left: 62px; }
  /* The stack follows .shell only on signed-in pages. Move its left boundary
     past the permanent rail so its automatic margins centre it in the usable
     page area. Public pages have no shell; mobile has no permanent rail. */
  .shell + .flash-stack { left: 62px; }
  .sidebar {
    position: fixed; left: 0; top: 0; z-index: 25;
    width: 62px; height: 100vh;
    /* The words are laid out at full length the moment the panel opens, and
       while it is still narrow they would hang out over the page. Clipped
       sideways they are uncovered by the widening edge instead, so opening
       reads as one movement rather than text arriving and a panel catching up.
       Vertical scrolling is untouched: the base rule’s overflow-y still
       stands, and overflow-x is the only axis named here. */
    overflow-x: hidden;
    /* Nothing is needed for reduced motion. The blanket rule further down
       this file turns every transition off with !important, so a machine
       asked for less movement gets the rail opening at once, as before. */
    transition: width .18s ease, box-shadow .18s ease;
  }
  /* Opened by a mouse resting on it, or by the keyboard arriving inside it.
     Shadowed, because it is now over the page rather than beside it, and
     without one the panel and the page read as one surface. */
  .sidebar:hover,
  .sidebar:focus-within {
    width: 198px;
    box-shadow: 4px 0 24px rgba(0, 0, 0, .10);
  }

  /* --- and everything below is how it looks while it is NOT opened -------- */
  /*
     Hung on :not(:hover):not(:focus-within) rather than written out a second
     time for the opened state. One set of rules that stops applying is a good
     deal harder to get wrong than two sets that have to agree.
  */
  .sidebar:not(:hover):not(:focus-within) .brand {
    /* Top and bottom match the opened bar's — see .brand near the top of this
       file. Only the sides differ, because this is 62px wide. */
    align-items: center; padding: 14px 8px 7px;
  }
  .sidebar:not(:hover):not(:focus-within) .brand-logo,
  .sidebar:not(:hover):not(:focus-within) .brand-company { display: none; }
  .sidebar:not(:hover):not(:focus-within) .brand-mark { display: block; }

  .sidebar:not(:hover):not(:focus-within) .nav { padding: 10px 8px; }
  /* You / Your Team / Administration / Support. The rule under each group
     keeps the grouping visible once the words naming it are gone. */
  .sidebar:not(:hover):not(:focus-within) .nav-section-label {
    height: 1px; margin: 0 6px 6px; padding: 0;
    overflow: hidden; color: transparent;
    background: var(--border);
  }
  .sidebar:not(:hover):not(:focus-within) .nav-section {
    margin-bottom: 10px;
  }

  .sidebar:not(:hover):not(:focus-within) .nav-link {
    justify-content: center; padding: 7px 0; gap: 0;
  }
  /* THE TIGHTEST SPOT IN THE WHOLE FILE, and the numbers are worth writing
     down. The rail is 62px. Take 4px of padding off each side and 54 are left;
     the square mark is 26, the bell 20, and the gap between them 4 — which is
     50, with 4 to spare. There is no room for anything else in here, so
     anything added beside these two has to take its width from one of them.

     The sides are 4px rather than the 8px the opened bar uses, and that is the
     only difference: the top and bottom stay at 14 and 7 so the dividing line
     under the brand does not move when the rail opens. */
  /* BESIDE THE LOGO WHEN THE BAR IS OPEN; UNDER IT WHEN IT IS A RAIL. That is
     not a preference, it is arithmetic, and it is worth writing down because
     the obvious fix is wrong.

     The rail is 62px, but the menu is taller than most windows so a scrollbar
     takes 16 of them, leaving 46 to lay anything out in — and only sometimes,
     because on a tall enough window there is no scrollbar and it is 62 again.
     The square mark is 26 and a bell small enough to sit beside it is 20,
     which with any gap at all is over 46. So side by side in the rail either
     hangs off the edge or shrinks the product's own mark, and it would do
     either of those depending on how tall somebody's window happened to be.

     Under it, both fit comfortably at any width, and the bell is still the
     second thing on the page. */
  .sidebar:not(:hover):not(:focus-within) .brand {
    flex-direction: column; align-items: center; justify-content: center;
    padding: 14px 8px 7px; gap: 6px;
  }
  .sidebar:not(:hover):not(:focus-within) .brand-stack {
    flex: 0 0 auto; align-items: center;
  }
  /* Smaller again in the rail, where every pixel is spoken for. */
  .sidebar:not(:hover):not(:focus-within) .brand-bell {
    width: 20px; height: 20px;
  }
  /* No room for a figure beside a 26px logo in a 62px rail, so it becomes a
     dot: "there is something waiting" survives, which is the same trade the
     counts beside Approvals and Leave already make a few rules down. */
  .sidebar:not(:hover):not(:focus-within) .brand-bell-count {
    top: -2px; right: -2px;
    min-width: 8px; width: 8px; height: 8px; padding: 0;
    font-size: 0; overflow: hidden;
  }
  .sidebar:not(:hover):not(:focus-within) .nav-label,
  .sidebar:not(:hover):not(:focus-within) .sign-out-label,
  .sidebar:not(:hover):not(:focus-within) .who-text {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    padding: 0; border: 0; overflow: hidden; white-space: nowrap;
    clip-path: inset(50%);
  }

  /* The count beside Approvals or Leave becomes a dot on the shoulder of the
     icon: "there is something waiting" survives, the figure does not fit.
     The number is still in the page and still read out — it is the pill that
     is shrunk, not the text removed. */
  .sidebar .nav-link { position: relative; }
  .sidebar:not(:hover):not(:focus-within) .nav-badge {
    position: absolute; top: 3px; right: 11px;
    min-width: 8px; width: 8px; height: 8px; padding: 0;
    line-height: 0; font-size: 0; color: transparent;
    border-radius: 50%;
    box-shadow: 0 0 0 2px var(--sidebar-bg);
  }

  .sidebar:not(:hover):not(:focus-within) .sidebar-foot {
    padding: 10px 8px 12px;
  }
  .sidebar:not(:hover):not(:focus-within) .who {
    justify-content: center; gap: 0;
  }
  .sidebar:not(:hover):not(:focus-within) .sign-out { gap: 0; }
  /* The icon stands in for the words. Opened, the words are back and it goes
     again, so the button reads the same as it does at full width. */
  .sidebar:not(:hover):not(:focus-within) .sign-out-icon {
    display: grid; place-items: center;
  }
}

/* ----------------------------- Responsive -------------------------------- */

@media (max-width: 900px) {
  .shell { flex-direction: column; }
  .topbar { display: flex; }
  .sidebar {
    position: fixed; inset: 0 auto 0 0; z-index: 40; width: 198px;
    transform: translateX(-100%); transition: transform .18s ease;
    box-shadow: 0 0 40px rgba(0, 0, 0, .18); height: 100dvh;
    border-right: 1px solid var(--border-strong);
  }
  body.nav-open .sidebar { transform: translateX(0); }
  body.nav-open::after {
    content: ''; position: fixed; inset: 0; background: rgba(0, 0, 0, .32); z-index: 30;
  }
  /* THE BAR STAYS ON TOP WHILE THE DRAWER IS OPEN.
     The drawer covers the left edge from the very top, and the dark wash
     covers everything — so the menu button, which lives in this bar, ended up
     under both and could not be pressed again to close what it had opened.
     Tapping the page outside worked; nothing said so, and a button that does
     nothing reads as broken.
     Lifted above both, and the drawer's contents are pushed down by the height
     of the bar so its logo is not hidden behind it. That height is measured by
     the script and handed here, rather than written down a second time where
     it would drift. */
  body.nav-open .topbar { z-index: 50; }
  body.nav-open .sidebar { padding-top: var(--topbar-h, 38px); }
  main { padding: 14px 13px 26px; }
  .foot { padding: 12px 13px; }
}

@media (max-width: 620px) {
  html, body { font-size: 12px; }
  h1 { font-size: 18px; }
  h2 { font-size: 13.5px; }
  /* Secondary text is not resized here: --text-sm already governs .muted,
     .hint, .stat-hint, .field label and the rest at every width. The old
     per-class overrides (12 / 11 / 10.5px) had drifted apart and would now
     make this text larger on a phone than on a desktop. */
  main { padding: 11px 11px 24px; }
  .page-head { margin-bottom: 13px; }

  .card { margin-bottom: 9px; border-radius: 10px; }
  .card-head { padding: 10px 13px; }
  .card-body { padding: 13px; }
  .grid { gap: 9px; margin-bottom: 9px; }

  .section-head { margin: 18px 0 9px; }
  .stat { padding: 10px 12px; }
  .stat-value { font-size: 19px; }
  .stat-label { font-size: 10px; }

  input[type=text], input[type=email], input[type=password], input[type=date],
  input[type=number], input[type=month], select, textarea { font-size: 12px; padding: 6px 9px; }
  /* The shorthand above resets padding-right, so restore the room the
     calendar icon and the dropdown arrow need, or the text runs underneath. */
  input[type=date], input[type=month] { padding-right: 2rem; }
  select { padding-right: var(--select-arrow-gap); }
  /* iOS Safari zooms the page whenever a field under 16px takes focus, and
     does not zoom back out. Growing the field to 16px only while it is
     focused keeps the value text small at rest and stops the zoom. */
  input:focus, select:focus, textarea:focus { font-size: 16px; }
  .btn { font-size: 12px; padding: 6px 14px; }
  .btn-sm { font-size: 11.5px; padding: 3px 10px; }
  .badge { letter-spacing: .07em; }
  .day-count { font-size: 18px; }
  .notice { font-size: 12px; padding: 8px 11px; }
  .details dt { font-size: 9.5px; }
  .details dd { font-size: 12px; }

  /* Tables become stacked cards so nothing is cut off on a phone. */
  .table-wrap { margin: 0 -13px -13px; }
  /* 13, to match the padding above - the wider screen's 17 overshot by four
     and clipped the top of the header row. */
  .card-body > .table-wrap:first-child,
  .card-body > [data-table-lock]:first-child > .table-wrap:first-child,
  .card-body > form:first-child > [data-table-lock] > .table-wrap:first-child { margin-top: -13px; }
  table, thead, tbody, tr, td { display: block; width: 100%; }
  thead { display: none; }
  tbody tr {
    border-bottom: 1px solid var(--border); padding: 8px 13px;
  }
  tbody tr:hover { background: transparent; }
  tbody td {
    border: none; padding: 2px 0;
    display: flex; justify-content: space-between; gap: 14px;
    text-align: right; font-size: 12px;
  }
  /* Back to no side padding on a phone: the stacked row holds everything in
     itself here, so 17px would push this one line in past all the others.

     Block and left, because a stacked cell is a label on the left and its
     value on the right - and this row has no label. The message was being
     pushed to the far right of the card as if it were somebody's answer to a
     question, instead of reading as a note across the whole row. */
  td.empty { padding: 4px 0; display: block; text-align: left; }
  tbody td::before {
    content: attr(data-label);
    font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
    color: var(--text-faint); font-weight: 600; text-align: left; flex: 0 0 auto;
  }
  .payslip { padding: 15px; }
  .payslip-net { font-size: 13.5px; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* The workings behind the figures, sitting at the foot of a payslip. It is
   reference material — read when a number needs explaining, not on the way to
   anything — so it is set smaller than the payslip it explains. */
.card-workings .card-body { font-size: var(--text-sm); }
.card-workings table { font-size: var(--text-sm); }
.card-workings th { font-size: 10.5px; }
.card-workings td { padding-top: 8px; padding-bottom: 8px; line-height: 1.45; }
.card-workings .badge { letter-spacing: .07em; }

/* --- The calendar --------------------------------------------------------
   Replaces the browser's own date popup, which cannot be styled because the
   browser draws it outside the page. Built from the same tokens as the rest
   of the app so a date field looks like the app it sits in.

   The field stays an <input type="date"> in the HTML — public/app.js turns it
   into this only once it runs, so with no JavaScript the browser's own
   calendar still works. */

.datefield { position: relative; display: block; }
/* In a row of filters a date box takes only the width it needs: ten characters
   and the calendar button, rather than the browser's roomy default. Without
   lifting the row's usual floor it would still stand 150px wide. */
.filters .field:has(.datefield) { min-width: 0; }
/* WIDE ENOUGH FOR THE PLACEHOLDER, whichever shape of date this company writes.
   At 120px the box gave 72px to the text once the padding and the calendar
   button were taken out, and "DD/MM/YYYY" wants 82 — so the hint every empty
   date filter shows was cut off mid-word.

   Sized on the longest of the four, "DD MMM YYYY" at 91.7px, rather than on the
   one that happened to be on screen: a company that writes its dates 16 Aug
   2026 would otherwise have been left with the same fault after it was fixed.
   142px leaves 94 for the text. */
.filters .datefield { width: 142px; }
/* A month is written out in words, so it needs the room a date in figures
   does not: "September 2026" and the placeholder both have to sit inside. */
.filters .datefield-month { width: 190px; }

/* --- A dropdown you can type into ----------------------------------------
   Built by public/app.js around an ordinary <select>, which stays in the form
   and is still what gets submitted. Without JavaScript none of this exists and
   the select shows as usual. */
.combo { position: relative; display: block; }
/* Out of sight but still in the form, still carrying the value. */
.combo select { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.combo-input { width: 100%; padding-right: 32px; }
.combo-open {
  position: absolute; right: 1px; top: 1px; bottom: 1px; width: 30px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--text-muted); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.combo-open:hover { color: var(--accent); background: var(--surface-2); }
.combo.is-open .combo-open { color: var(--accent); }
.combo-pop {
  position: absolute; z-index: 40; top: calc(100% + 5px); left: 0;
  width: 100%; min-width: 180px; max-height: 260px; overflow-y: auto;
  margin: 0; padding: 4px; list-style: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: 0 4px 20px rgba(0, 0, 0, .1);
}
.combo-option {
  padding: 6px 10px; border-radius: var(--radius-sm);
  font-size: 12.5px; cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.combo-option.is-active { background: var(--surface-2); }
.combo-option[aria-selected="true"] { font-weight: 600; }
/* The heading a run of choices sits under. Quieter than the choices, so the
   eye goes to what can actually be picked. */
.combo-heading {
  padding: 7px 10px 3px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-muted);
}
.combo-option + .combo-heading { margin-top: 2px; border-top: 1px solid var(--border); }
.combo-empty { padding: 6px 10px; font-size: 12.5px; color: var(--text-muted); }
/* The list would be clipped by a card that hides its overflow.

   ONLY WHILE IT IS OPEN. A card that never clips has to round its own heading
   corners, or the heading lays a square of the same colour over them - so the
   card stops clipping for as long as a list is open and goes back to clipping
   the moment it shuts. The tick-list box gets the same treatment, having the
   same problem. */
.card:has(.combo.is-open),
.card:has(.combo.is-open) .card-body,
.card:has(.multi-picker-menu:not([hidden])),
.card:has(.multi-picker-menu:not([hidden])) .card-body { overflow: visible; }
.filters .datefield input { width: 100%; }
.datefield input { padding-right: 36px; }
/* The button that opens it, sitting inside the right-hand end of the box. */
.datefield-open {
  position: absolute; right: 1px; top: 1px; bottom: 1px; width: 34px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--text-muted); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.datefield-open:hover { color: var(--accent); background: var(--surface-2); }
.datefield.is-open .datefield-open { color: var(--accent); }

.datepop {
  position: absolute; z-index: 40; top: calc(100% + 5px); left: 0;
  width: 268px; padding: 11px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: 0 4px 20px rgba(0, 0, 0, .1);
}
/* Near the right edge of a narrow screen the popup would hang off; anchoring
   it to the right of the field keeps it on the page. */
@media (max-width: 560px) { .datepop { left: auto; right: 0; } }

.datepop-head {
  display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
}
.datepop-title { font-size: 13px; font-weight: 600; }
/* The month and the year in the heading are buttons: pressing one opens the
   list of months or of years, so a birthday forty years back is three presses
   rather than four hundred and eighty. They are styled as the words they sit
   among, with only the hover saying they can be pressed. */
.datepop-jump {
  padding: 2px 5px; margin: 0 -1px; cursor: pointer;
  background: none; border: 0; border-radius: var(--radius-sm);
  font-family: inherit; font-size: 13px; font-weight: 600; color: var(--text);
}
.datepop-jump:hover { background: var(--surface-2); color: var(--accent); }
.datepop-jump:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.datepop-step {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--text-muted);
}
.datepop-step:hover { background: var(--surface-2); color: var(--text); }

.datepop-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.datepop-days span {
  text-align: center; font-size: 10.5px; font-weight: 600; letter-spacing: .03em;
  color: var(--text-faint); padding-bottom: 4px;
}
.datepop-day {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  padding: 0; cursor: pointer; font-size: 12.5px; font-family: inherit;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--text); font-variant-numeric: tabular-nums;
}
.datepop-day:hover { background: var(--surface-2); }
/* A day belonging to the month before or after. Shown so the weeks read as
   whole weeks rather than trailing off, and choosable — the last days of last
   month are what somebody at the turn of a month reaches for. Quieter than the
   month on show, so it is plain which days are this one's. */
/* Lighter than --text-faint, which the palette holds at 4.6:1 because the
   small uppercase labels have to be READ. These do not: they are there so the
   weeks read as whole weeks, and being quiet is the job. A number is also a
   shape you recognise rather than a word you decode, and it lifts to full
   strength the moment the pointer is on it — which is when it has to be read. */
.datepop-day.is-outside { color: #acacb1; }
.datepop-day.is-outside:hover,
.datepop-day.is-outside:focus-visible { color: var(--text); }
/* Today is outlined; the chosen day is filled. Both are readable without
   relying on the colour alone — one has a ring, the other a solid block. */
.datepop-day.is-today { border-color: var(--border-strong); font-weight: 600; }
.datepop-day.is-picked {
  background: var(--accent); color: var(--accent-text); border-color: var(--accent);
  font-weight: 600;
}
/* Darker on hover, not lighter — see --accent-dark, which is misnamed. */
.datepop-day.is-picked:hover { background: var(--tint-accent-ink); }

/* The two links under the line at the foot of a date or month panel - Today
   or This month, and Clear. Centred: they are a pair of equals sitting under
   a centred grid, and left-aligned they hung off one side of it. */
.datepop-foot {
  display: flex; justify-content: center; gap: 14px; margin-top: 9px; padding-top: 8px;
  border-top: 1px solid var(--border);
}
.datepop-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit; font-size: var(--text-sm); color: var(--accent);
}
.datepop-link:hover { text-decoration: underline; }

/* The month chooser: the same panel as the calendar, with twelve months in
   place of the days. */
.datepop-months { width: 232px; }
.datepop-monthgrid { grid-template-columns: repeat(3, 1fr); gap: 4px; }
.datepop-month {
  padding: 8px 0; cursor: pointer; font-size: 12.5px; font-family: inherit;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--text);
}
.datepop-month:hover { background: var(--surface-2); }
.datepop-month.is-today { border-color: var(--border-strong); font-weight: 600; }
.datepop-month.is-picked {
  background: var(--accent); color: var(--accent-text); border-color: var(--accent);
  font-weight: 600;
}
.datepop-month.is-picked:hover { background: var(--tint-accent-ink); }

/* A card clips its contents so a table's corners stay inside its rounded
   edge. That also clips a calendar hanging below its field, so while one is
   open the card that holds it stops clipping. Only that card, and only while
   it is open, so table corners are unaffected everywhere else. */
.card:has(.datefield.is-open),
.card:has(.datefield.is-open) .card-body,
.table-wrap:has(.datefield.is-open) { overflow: visible; }


/* --- Putting the payroll elements in a different order ---------------------

   The handle only appears once the browser JavaScript has run and added
   .can-drag, so a page without it never shows a grip that would do nothing.
   The arrows are always there and work either way. */

.order-cell { white-space: nowrap; }

.order-place { display: inline-flex; align-items: center; gap: 6px; }

.drag-grip { display: none; }

.element-order-table.can-drag .drag-grip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  color: var(--text-muted);
  /* The icon is small, but a finger is not. This takes the target to about 26
     square while the bars stay the size of the other icons on the page. */
  padding: 5px;
  /* Stops a finger scrolling the page when it meant to move the row. */
  touch-action: none;
}

/* Every press lands on the handle rather than on the drawing inside it. */
.element-order-table.can-drag .drag-grip svg { pointer-events: none; display: block; }

.element-order-table.can-drag .drag-grip:hover { color: var(--text); }
.element-order-table.can-drag .drag-grip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* An element can do more than one thing — charge the employee and the employer
   both — so each gets its own line rather than running together. */
.works-out-line { display: block; }
.works-out-line + .works-out-line { margin-top: 2px; }

/* A handle with nowhere to go. Shown rather than hidden: a row that is simply
   missing its handle reads as broken, where a greyed one reads as "this does
   not apply here". The title says which. */
.element-order-table.can-drag .drag-grip.is-fixed {
  color: var(--border-strong);
  cursor: default;
}
.element-order-table.can-drag .drag-grip.is-fixed:hover { color: var(--border-strong); }

/* Paused: still listed, still orderable, but plainly not in play. */
.element-order-table tr.row-paused > td { opacity: .62; }
.element-order-table tr.row-paused .drag-grip { opacity: .75; }

.order-number { min-width: 1.1em; text-align: right; font-variant-numeric: tabular-nums; }


/* While a row is being carried, the rest of the table stops trying to be
   helpful — no text selection, no hover highlight following the cursor. */
.element-order-table.is-dragging { user-select: none; cursor: grabbing; }
.element-order-table.is-dragging .drag-grip { cursor: grabbing; }
.element-order-table tr.row-dragging {
  background: var(--surface-2);
  box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
  position: relative;
  z-index: 1;
}

/* A row whose figure will come out as zero. Not colour alone: the sentence
   underneath says what is wrong, so this reads the same to somebody who
   cannot tell the red from the rest. */
.element-order-table tr.row-broken { background: var(--bad-bg); }

/* The warning is a strip under the whole row rather than a note squeezed into
   one column. A sentence about what an element will come to has to be read,
   and one column's width turns it into a narrow ribbon of wrapped words. */
.element-order-table tr.order-warning-row > td {
  /* A little air under the line above, so the sentence is not sitting on it. */
  padding-top: 5px;
  /* No line between a row and its own warning: they are one thing. */
  border-top: none;
}
.element-order-table tr.order-warning-row:hover { background: var(--bad-bg); }

.order-warning { display: block; color: var(--bad); font-size: 12px; line-height: 1.4; }
.order-warning span { display: block; }
.order-warning span + span { margin-top: 3px; }

/* The bar that appears once something has moved and is not saved. */
.order-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  /* The table above is pulled 17px wider than the card on every side so its
     rows reach the edges, and that pull includes 17px off the bottom. Adding
     it back means the 25px here is the gap actually seen, and matches what
     everything else that follows a table gets. */
  margin-top: calc(25px + 17px);
  padding: 10px 12px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface-2);
}

.order-bar-said { margin: 0; font-size: 13px; color: var(--text); }
.order-bar-buttons { display: inline-flex; gap: 8px; }

@media (max-width: 640px) {
  .order-bar { flex-direction: column; align-items: stretch; }
  .order-bar-buttons { justify-content: flex-end; }
}


/* ===========================================================================
   TIGHTER SCALE
   Eighteen font sizes consolidated onto seven steps, with 9px as the floor,
   and structural spacing reduced by roughly a fifth.

   To change the whole app's type scale, edit the seven values below — every
   rule in this block points at them, so nothing else needs touching.

   This is an additive layer: it is appended last, so it wins on source order
   without editing the 168 declarations above it. Those are now inert and can
   be cleaned out in a separate pass.
   =========================================================================== */

:root {
  --fs-xl:    18px;   /* page titles, stat figures, day count */
  --fs-lg:    16px;   /* auth title, popups, print report title */
  --fs-md:    14px;   /* card titles, payslip net, confirm dialog */
  --fs-base:  12px;   /* body text, buttons, form inputs */
  --fs-sm:    11px;   /* nav links, tables, field labels, breadcrumb */
  --fs-xs:    10px;   /* hints, muted text, empty states */
  --fs-micro:  9px;   /* badges, table headers, definition terms — the floor */

  /* ONE HEIGHT FOR EVERYTHING STANDING IN A CARD'S HEADING — buttons and the
     dropdowns beside them — so a row lines up top and bottom.

     WRITTEN AGAINST THE SCALE, not frozen. It was once 30.6px: the height an
     ordinary button measured on the type scale of the day. When that scale
     shrank, the number stayed, and a button in a card heading was held nearly
     half again taller than it wanted to be.

     NOW 28px. It was 32, came down to 26 when these were asked to be smaller,
     and was then asked for at 28 by that number. The type has been untouched
     throughout — what has moved is the room around it, in the block near the
     foot of this file that sets the sizes for that row.

     STILL A SUM AGAINST THE TYPE SCALE, not a plain 28. The height at the top
     of a page is written as a plain number because it answers only to itself;
     this one has a row to line up, and the things in that row are sized in
     letters. Freeze it and the day the scale moves it is 30.6px all over again.

     IT MUST CLEAR THE TALLEST THING THAT STANDS THERE, which is a dropdown at
     about 25.6px — not the buttons, which want only 24.4. Sizing it to the
     buttons is how this went wrong before: the rule whose only job is lining
     the row up left the dropdowns standing proud of it, for months. Everything
     in the row is under this figure, so the floor lifts them all to exactly it.

     WORTH KNOWING, because it is a real cost of the 28. An ordinary button in
     the middle of a page stands 28.4px. These now stand 28. The gap that used
     to say "this acts on one card, that acts on the page" is four tenths of a
     pixel wide, which is to say it is gone. The three sizes are still in order
     and nothing is broken by it; there is simply no longer anything to see.

     AND IT DOES NOT GO BELOW 24px, which is the smallest anything meant to be
     tapped or clicked is allowed to be. */
  --card-action-height: calc(var(--fs-base) + 16px);

  /* AND ONE FOR THE BUTTONS AT THE TOP OF A PAGE. Asked for by name: thirty
     pixels, every one of them, everywhere.

     A PLAIN NUMBER RATHER THAN A SUM, and that is the difference between this
     and the line above. The card figure is worked out from the type scale
     because it exists to clear the tallest control in its row — move the scale
     and the right answer moves with it. This one is not measured from anything.
     It is a decision about how big the main action on a page should be, and a
     decision does not follow the type scale around.

     Stated as a height rather than reached through padding on purpose. A
     button of this size around twelve pixels of writing wants a fraction of a
     pixel of padding a side, which is not a thing to write down, and it would
     come out at a different total again on a phone where the writing steps
     down to eleven. Setting the height says the thing that was actually asked
     for, and the padding below it is only kept small enough not to push past
     it. */
  --page-action-height: 32px;

  /* AND ONE FOR THE BOXES AND DROPDOWNS INSIDE TABLE ROWS. Asked for: a text
     box in a row stood taller than the dropdown beside it, and they should
     match.

     26px, which is the dropdown rounded up from the 25.6 it wants. Sized to
     the dropdown because the dropdown is the one that was already right — see
     the rule near the foot of this file for why the text box was not. */
  --cell-control-height: 26px;

  /* AND ONE FOR THE BUTTONS INSIDE TABLE ROWS — Approve, Reject, Remove, the
     ones that act on the single line they sit on.

     24px, asked for by that number. They stood 20.8, which made them the
     smallest thing on the site and put them UNDER the 24px minimum for
     anything meant to be tapped or clicked. Approve and Reject decide somebody's
     leave and somebody's money; they were the smallest targets on the page.

     Exactly on the minimum rather than above it, because that is the figure
     asked for, and because these deliberately stay the quietest buttons in the
     app — a row of them repeated down forty lines is a different thing from a
     button somebody came to the page to press. */
  --row-button-height: 24px;

  /* THE ROOM ABOVE AND BELOW EACH LINE OF A DETAILS LIST — the Created / Last
     changed pair at the top of a card, the boxes on a payslip.

     Named because two rules must agree on it: the rows themselves, and the
     pull-up that stops the FIRST row doubling with the card's own padding.
     Typed twice they drift, and the symptom is a gap that looks wrong with no
     single number to blame. */
  --details-row-pad: 5px;

  /* THE TITLE AT THE TOP OF A PAGE — "Employees", "Approvals", "Hello, HR".
     Asked to be bigger.

     26px. It was 18, went to 22 when these were asked to be bigger, and was
     then asked for at 26 by that number.

     THE PROBLEM WAS NEVER THAT 18 WAS SMALL, it was that
     everything else had caught up with it: the figure in a summary card, the
     day count, a payslip total, all sat at exactly the same 18px. A page had
     nothing on it that was plainly the biggest thing, so the title read as
     another label rather than as the name of where you are.

     A SIZE OF ITS OWN, not a step of the shared scale. --fs-xl is the top of
     that scale and the stat figures are on it too; growing the scale step
     would have grown them alongside, which is exactly the sameness being
     undone. This variable exists so a page title can move on its own.

     THE ONE PLACE THE LETTERING ITSELF IS THE ANSWER. Every other resize this
     week left the type alone and moved the space around it, because those were
     buttons — a button is a target and its size is how big it is to hit. A
     title is not a target. Its size IS the writing, so here the writing moves. */
  --fs-page-title: 26px;

  /* THE BAR ACROSS THE TOP OF LOG IN AND SIGN UP — Home, Features, Pricing,
     FAQ, and the button at the end of them.

     OUTSIDE THE SCALE ON PURPOSE, and it has to be. This bar exists to match
     the marketing site's masthead, not this app's type. A visitor reaches these
     two pages by clicking Sign up or Log in on the site, and the bar is the one
     thing on screen that is supposed not to change as they cross over. Sized
     off the app's scale it came out at 12px against the site's 14.9px, and the
     links visibly shrank at the moment of arrival — which reads as having
     landed somewhere else.

     THE FIGURES ARE THE SITE'S, COPIED. Not .93rem, which is what the site's
     own stylesheet says: rem is relative to the root, the two roots differ, and
     copying the ratio across gives 12.56px here against 14.88px there. The
     pixel figure is the only one that survives the trip. */
  --fs-public-bar:     14.9px;
  --fs-public-bar-btn: 13.6px;
}

html, body { font-size: var(--fs-base); }
h1 { font-size: var(--fs-xl); }
/* A PAGE TITLE IS BIGGER THAN AN ORDINARY HEADING, and has its own figure
   rather than a step of the shared scale — see --fs-page-title for why. This
   sits with the other headings so all of them can be read in one place; the
   phone-width figure is set by redefining the variable further down, so this
   rule is written once and never restated. */
.page-head h1 { font-size: var(--fs-page-title); }
h2 { font-size: var(--fs-md); }
h3 { font-size: var(--fs-base); }
.muted { font-size: var(--fs-xs); }
.small { font-size: var(--fs-sm); }
.brand-company { font-size: var(--fs-sm); }
.nav-section-label { font-size: var(--fs-micro); }
.nav-link { font-size: var(--fs-sm); }
.nav-badge { font-size: var(--fs-xs); }
.avatar { font-size: var(--fs-micro); }
.who-text strong { font-size: var(--fs-sm); }
.who-text small { font-size: var(--fs-sm); }
.topbar-title { font-size: var(--fs-base); }
.foot { font-size: var(--fs-sm); }
.page-head .muted { font-size: var(--fs-sm); }
.breadcrumb { font-size: var(--fs-sm); }
.card-head-label { font-size: var(--fs-md); }
.section-head h2 { font-size: var(--fs-md); }
.stat-label { font-size: var(--fs-sm); }
.stat-value { font-size: var(--fs-xl); }
.stat-hint { font-size: var(--fs-xs); }
table { font-size: var(--fs-sm); }
thead th { font-size: var(--fs-micro); }
.empty { font-size: var(--fs-xs); }
.badge { font-size: var(--fs-micro); }
.btn { font-size: var(--fs-base); }
/* Its padding is set with the other spacing further down, AFTER .btn sets
   its own — see the note there. */
.btn-sm { font-size: var(--fs-sm); }
.error-popup h2 { font-size: var(--fs-lg); }
.field label { font-size: var(--fs-sm); }
input[type=text], input[type=email], input[type=password], input[type=date], input[type=number], input[type=month], input[type=search], select, textarea { font-size: var(--fs-base); }
.pager-page, .pager-here { font-size: var(--fs-base); }
.cal-head { font-size: var(--fs-micro); }
.cal-number { font-size: var(--fs-base); }
.cal-holiday { font-size: var(--fs-xs); }
.cal-bar { font-size: var(--fs-sm); }
.cal-key { font-size: var(--fs-xs); }
input[type=file] { font-size: var(--fs-base); }
input[type=file]::file-selector-button { font-size: var(--fs-sm); }
input[type=file]::-webkit-file-upload-button { font-size: var(--fs-sm); }
.filefield { font-size: var(--fs-base); }
.filefield-button { font-size: var(--fs-sm); }
.filepill { font-size: var(--fs-sm); }
.hint { font-size: var(--fs-xs); }
.error-text { font-size: var(--fs-sm); }
.day-count { font-size: var(--fs-xl); }
.day-count-warn { font-size: var(--fs-md); }
.checkbox, .field label.checkbox { font-size: var(--fs-sm); }
.pay-section > h3 { font-size: var(--fs-base); }
.pay-line-label { font-size: var(--fs-base); }
.pay-line-note { font-size: var(--fs-xs); }
.pay-total-line .pay-line-what { font-size: var(--fs-base); }
.pay-line-net { font-size: var(--fs-lg); }
.page-rail-head { font-size: var(--fs-micro); }
.page-rail a, .page-rail-menu a { font-size: var(--fs-sm); }
.page-rail-menu a { font-size: var(--fs-base); }
.support-banner { font-size: var(--fs-sm); }
.rule-joiner { font-size: var(--fs-sm); }
select.rule-joiner-select { font-size: var(--fs-sm); }
.rule-condition select, .rule-condition input[type="text"] { font-size: var(--fs-sm); }
.multi-picker-face, .action-parts-button { font-size: var(--fs-sm); }
.multi-picker > .multi-picker-face { font-size: var(--fs-base); }
.multi-picker-search-box { font-size: var(--fs-sm); }
.multi-picker-none { font-size: var(--fs-sm); }
.multi-picker-option, .action-part { font-size: var(--fs-sm); }
.rule-actions .btn, .rule-head .btn, .rule-condition .btn, .action-line .btn { font-size: var(--fs-sm); }
.rule-head [data-rule-label] { font-size: var(--fs-base); }
.rule-set-label { font-size: var(--fs-micro); }
.rule-set-summary { font-size: var(--fs-sm); }
.action-word { font-size: var(--fs-sm); }
.action-line select, .action-line input[type="text"] { font-size: var(--fs-sm); }
.action-part-note { font-size: var(--fs-sm); }
.action-switch { font-size: var(--fs-sm); }
.action-radio { font-size: var(--fs-sm); }
.action-radio .muted { font-size: var(--fs-sm); }
.element-summary { font-size: var(--fs-sm); }
.element-summary strong { font-size: var(--fs-xs); }
.action-reading { font-size: var(--fs-sm); }
.rule-set-part { font-size: var(--fs-sm); }
.classic-settings > summary { font-size: var(--fs-sm); }
.cell-input { font-size: var(--fs-sm); }
.cell-select { font-size: var(--fs-sm); }
fieldset legend { font-size: var(--fs-micro); }
.field-derived { font-size: var(--fs-base); }
.notice { font-size: var(--fs-sm); }
.flash-close { font-size: var(--fs-md); }
.confirm-title { font-size: var(--fs-md); }
.confirm-message { font-size: var(--fs-base); }
.details dt { font-size: var(--fs-micro); }
.details dd { font-size: var(--fs-sm); }
/* These two take the site's figures, not the app's scale — see the note on
   --fs-public-bar for why the bar is the one thing here that does not follow
   the scale everything else on this page does. */
.public-bar-nav a { font-size: var(--fs-public-bar); }
.public-bar .btn { font-size: var(--fs-public-bar-btn); }
.auth-card h1 { font-size: var(--fs-lg); }
.auth-summary h2 { font-size: var(--fs-md); }
.summary-row dt { font-size: var(--fs-base); }
.summary-row dd { font-size: var(--fs-base); }
.summary-working dt, .summary-working dd { font-size: var(--fs-sm); }
.summary-total-label { font-size: var(--fs-base); }
.summary-total-figure { font-size: var(--fs-xl); }
.summary-note { font-size: var(--fs-sm); }
.tiers legend { font-size: var(--fs-xs); }
.tier-name { font-size: var(--fs-xs); }
.tier-blurb { font-size: var(--fs-sm); }
.auth-foot { font-size: var(--fs-xs); }
.payslip { font-size: var(--fs-sm); }
.payslip-head h1 { font-size: var(--fs-xs); }
.payslip-co { font-size: var(--fs-sm); }
.payslip-co strong { font-size: var(--fs-base); }
.payslip-meta { font-size: var(--fs-sm); }
.payslip-meta strong { font-size: var(--fs-micro); }
.payslip table { font-size: var(--fs-sm); }
.payslip-section h2 { font-size: var(--fs-sm); }
.payslip-net { font-size: var(--fs-md); }
.payslip-foot { font-size: var(--fs-micro); }
.help-topic .card-body > p { font-size: var(--fs-base); }
.help-topic .card-body > ul { font-size: var(--fs-base); }
.help-jump-list li { font-size: var(--fs-base); }

.report-print { font-size: var(--fs-sm); }
.report-print-head h1 { font-size: var(--fs-lg); }
.report-print-co { font-size: var(--fs-xs); }
.report-print-sub { font-size: var(--fs-sm); }
.chart-title { font-size: var(--fs-sm); }
.chart-label { font-size: var(--fs-base); }
.chart-value { font-size: var(--fs-base); }
.card-workings .card-body { font-size: var(--fs-xs); }
.card-workings table { font-size: var(--fs-xs); }
.card-workings th { font-size: var(--fs-micro); }
.combo-option { font-size: var(--fs-base); }
.combo-heading { font-size: var(--fs-micro); }
.combo-empty { font-size: var(--fs-base); }
.datepop-title { font-size: var(--fs-base); }
.datepop-jump { font-size: var(--fs-base); }
.datepop-days span { font-size: var(--fs-micro); }
.datepop-day { font-size: var(--fs-base); }
.datepop-link { font-size: var(--fs-xs); }
.datepop-month { font-size: var(--fs-base); }
.order-warning { font-size: var(--fs-sm); }
.order-bar-said { font-size: var(--fs-base); }
@supports (appearance: base-select) {
  select { font-size: var(--fs-base); }
  option { font-size: var(--fs-base); }
  optgroup { font-size: var(--fs-micro); }
}
@media (max-width: 720px) {
  /* The links step down a touch where the bar folds them onto their own line,
     which is the step the site's bar makes at the same width. Written by
     redefining the variable rather than by naming a size, so the rule that uses
     it stays in one place. */
  :root { --fs-public-bar: 14.4px; }
}
@media print {
  .payslip { font-size: var(--fs-micro); }
  .report-print { font-size: var(--fs-micro); }
  body { font-size: var(--fs-micro); }
}
@media (max-width: 620px) {
  html, body { font-size: var(--fs-sm); }
  h1 { font-size: var(--fs-lg); }
  h2 { font-size: var(--fs-base); }
  .stat-value { font-size: var(--fs-lg); }
  .stat-label { font-size: var(--fs-xs); }
  input[type=text], input[type=email], input[type=password], input[type=date], input[type=number], input[type=month], select, textarea { font-size: var(--fs-sm); }
  .btn { font-size: var(--fs-sm); }
  .btn-sm { font-size: var(--fs-sm); }
  .day-count { font-size: var(--fs-lg); }
  .notice { font-size: var(--fs-sm); }
  .details dt { font-size: var(--fs-micro); }
  .details dd { font-size: var(--fs-sm); }
  tbody td { font-size: var(--fs-sm); }
  tbody td::before { font-size: var(--fs-xs); }
  .payslip-net { font-size: var(--fs-base); }
  /* The page title steps down on a phone as it always has, but by redefining
     its own figure rather than by naming a scale step — so the rule that uses
     it stays written in exactly one place. 18px, which is what the title used
     to be on a wide screen.

     INSIDE :root, WHICH IT NEEDS. A custom property is a declaration like any
     other and has to belong to something; written loose inside the media block
     it is not a rule at all, and a browser drops it without complaint — so the
     phone would quietly have kept the wide-screen size. */
  :root { --fs-page-title: 18px; }
}

/* --- structural spacing, about a fifth tighter --------------------------- */
main { padding: 16px 20px 26px; }
.card { margin-bottom: 11px; border-radius: 10px; }
.card-head { padding: 10px 13px; }
.card-body { padding: 13px; }
.grid { gap: 11px; margin-bottom: 11px; }
.stat { padding: 10px 12px; }
thead th { padding: 6px 14px; }
tbody td { padding: 7px 14px; }
.field { margin-bottom: 10px; }
.btn { padding: 6px 14px; }
/* IMMEDIATELY AFTER .btn, and that is the whole point of where it sits.
   .btn-sm is one class, exactly like .btn, so between the two of them it is
   whichever comes last in the file that wins. The original .btn-sm rule is
   1,100 lines above .btn's padding here, so it was losing — and every small
   button in the app was drawn with small text inside a full-sized button.
   3px/10px, which is the figure the phone-width rules have used for these
   all along. */
.btn-sm { padding: 3px 10px; }

/* --- the controls in the corner of a card heading, one size smaller -------
   Asked for. Everything standing in a card's heading — the Edit and Save
   buttons, the month steppers, a dropdown, the month box on the Team Calendar
   — was drawn at the same size as a button in the middle of a page, and looked
   heavier than the job it does.

   THE TYPE IS UNTOUCHED. What comes off is the padding around it, which is the
   same way the buttons inside table rows were brought down. Writing smaller
   would have been the easy version and the wrong one: these are ordinary
   controls, not fine print, and there is already a size below this one that
   shrinks the lettering too.

   6px/14px becomes 4px/12px, and --card-action-height comes down from 32 to 26
   to match. BOTH HALVES ARE NEEDED. The shared height is a floor under every
   control in the row: trim the padding alone and the floor holds them exactly
   where they were, and nothing on screen moves at all.

   The dropdowns and the month box are trimmed from 7px to the same 4px, or
   they would be the ones left standing proud of the new floor — the fault this
   row's shared height exists to prevent, and one it really does walk into: at
   5px a dropdown wanted 27.6px against a 27px floor and stood six tenths of a
   pixel above everything beside it.

   Top and bottom only for those two, never the shorthand: a dropdown keeps
   room on its right for the arrow, and the month box for the calendar button.
   The shorthand would take that room back and the text would run underneath.

   Two classes each, so these settle it against the one-class .btn rules above
   and against the phone-width ones further down, whichever way the file is
   later reordered.

   THE SQUARE MONTH STEPPERS ARE LEFT OUT, and they have to be. Those have no
   padding at all on purpose — they are a chevron centred in a square whose
   side IS the row's shared height, so they follow it on their own. Handed the
   padding above they came out 30px tall against everything else's 27, standing
   proud of the very row this is meant to line up, and 42px of chevron and
   padding was crammed into a 27px square besides. Written as an exception
   rather than by putting their nought back afterwards, so the reason is where
   somebody will read it. */
.card-actions .btn:not(.btn-step),
.card-actions-form .btn:not(.btn-step) { padding: 4px 12px; }
.card-actions select,
.card-actions-form select,
.card-actions-form input { padding-top: 4px; padding-bottom: 4px; }

/* --- and the buttons at the top of a PAGE, one size larger ----------------
   Asked for, and the opposite of the block above on purpose. These are not the
   same thing as the controls in a card heading, and the two were drawn at the
   same size as each other and as every other button on the site.

   A card heading's buttons act on that one card. THESE ARE WHAT THE PAGE IS
   FOR — Add employee, Apply for leave, Submit claims — which is why they are
   the only solid blue left on the site. Something that is the answer to "what
   did you come here to do" should not be the same size as something that edits
   a panel halfway down.

   THEY STAND AT EXACTLY 32px, asked for by that number. They were 28, were
   briefly 34, and sat at 30 before this.

   THE HEIGHT IS SET, NOT ARRIVED AT. The padding is 6px above and below, which
   would leave a 28px button on its own; the height then lifts it to 32. That
   looks like the long way round and it is the only way to be sure of the
   figure: a button this size around 12px of writing wants a fraction of a pixel
   of padding a side — and on a phone, where the writing steps down to 11px, the
   same padding would land somewhere else again. Padding chosen to sit
   comfortably UNDER the height, and the height says what was asked for.

   The 18px at the sides is untouched from when these were made bigger. Only the
   height was asked about, so only the height moved.

   THE LETTERING IS UNTOUCHED, as it was every other time these buttons have
   been resized: what changes is the room around it.

   THE "ON THIS PAGE" BUTTON IS LEFT OUT, and that was asked for too. It is
   written small on purpose — a way of looking around a page rather than a thing
   the page is for, and the quietest control on the screen by a decision
   recorded further up this file. It also shares a row with the Dashboard's two
   blue buttons, where its narrowness is what keeps that row on one line. */
.page-actions .btn:not(.btn-sm) {
  min-height: var(--page-action-height);
  padding: 6px 18px;
}

/* --- the boxes and dropdowns inside table rows, all one height -------------
   Asked for: a text box in a table row stood 30.8px while the dropdown beside
   it stood 25.6, and they should match.

   THE TEXT BOXES HAD NEVER TAKEN THEIR OWN STYLING, and that is the whole of
   it. `.cell-input` sets 4px of padding and the smaller lettering, and it has
   said so for as long as it has existed — but `.cell-input` is one class, and
   the ordinary form-field rule is written as `input[type=text], input[type=…]`,
   which is a tag AND an attribute. That beats a lone class no matter which
   comes first in the file. So every text box in every settings table has been
   quietly drawn with the padding of an ordinary form field.

   THE DROPDOWNS BESIDE THEM WERE FINE, which is exactly why the mismatch was
   visible. The same shared rule reaches a dropdown as a bare `select` — a tag
   on its own, which a class DOES beat. One rule, two elements, two different
   winners, and a row that does not line up.

   This is the third time this shape of fault has bitten this stylesheet, after
   .btn-sm being outweighed by .btn and the card-heading heights. It is always
   the same story: nothing errors, nothing looks broken, something is simply
   the wrong size and stays that way for months.

   `td input.cell-input` is a tag, a class and an ancestor tag, which outweighs
   the shared rule outright rather than by being further down the file. Written
   that way on purpose: this file has twice been reordered underneath rules
   that were winning on position alone.

   TOP AND BOTTOM ONLY, never the shorthand — a dropdown keeps room on its
   right for its arrow and a date box for its calendar button, and the
   shorthand would take that room back and run the text underneath.

   The height is shared with the dropdowns rather than matched by hand, so the
   two cannot drift apart again. */
td input.cell-input,
td select.cell-input,
td textarea.cell-input,
td .cell-select {
  min-height: var(--cell-control-height);
  padding-top: 4px;
  padding-bottom: 4px;
}

/* --- and the buttons inside table rows ------------------------------------
   Asked for at 24px. They were 20.8 — the smallest thing on the site, and
   under the minimum for anything meant to be tapped.

   A HEIGHT, WITH THE PADDING LEFT ALONE, the same way the buttons at the top
   of a page are done. The lettering sits centred in the button whatever the
   padding is, so 3px of padding inside a 24px height and 5px inside no height
   look identical — but only one of them is still 24px on a phone, where the
   lettering steps down and padding-derived heights move with it.

   Scoped to a table row and no further. .btn-sm is also what dresses "On this
   page", the quiet contents button, which is not in a row and is meant to stay
   the smallest thing on the screen. One class would have caught it; the row
   is what was asked about.

   Left BELOW the boxes and dropdowns in the same rows, which stand at 26. That
   is not an oversight: a button that acts on one line should not compete with
   the field it sits beside. */
td .btn { min-height: var(--row-button-height); }

.page-head { margin-bottom: 13px; }
.table-wrap { margin: 0 -13px -13px; }
/* ALL THREE SHAPES, not just the first.
   A table is flushed up against its card heading by taking the body's padding
   back. There have always been three ways a table can sit in a card — straight
   in, inside an Edit/Save lock, or inside a form holding that lock — and the
   original rule named all three. The tighter scale restated only the first, so
   the other two went on pulling the OLD 17px against the new 13px of padding
   and every table in an Edit/Save card was dragged 4px too high, tucking its
   column headings under the heading above them.
   Four cards had it: Claims Categories, Residency Status Options, Default Leave
   Policy and Public Holidays. */
.card-body > .table-wrap:first-child,
.card-body > [data-table-lock]:first-child > .table-wrap:first-child,
.card-body > form:first-child > [data-table-lock] > .table-wrap:first-child {
  margin-top: -13px;
}
.topbar { height: 38px; }
.form-actions { margin-top: 11px; padding-top: 11px; }
fieldset { margin: 0 0 12px; }

/* --- two things deliberately NOT shrunk ---------------------------------- */
@media (max-width: 620px) {
  /* iOS Safari zooms the whole page when a field under 16px takes focus and
     does not zoom back out. This must stay 16px whatever the scale does. */
  input:focus, select:focus, textarea:focus { font-size: 16px; }
  /* A button is already ~29px tall against Apple's 44px touch-target
     guidance, so the hit area holds even though the type shrinks. */
  .btn { padding: 6px 14px; }
}
