/* ChargeCore Funnel CRM — shared styles for all pages.
   Consolidated here so the look stays consistent and changes happen in one place. */

:root {
  --green: #70AD47;
  /* Green as *text*, and darker than the brand fill for one reason: at #5a8c39
     it was 4.00:1 on white and 3.66:1 on --green-tint, so the selected tab, the
     "open" pill and every success message failed WCAG AA. #507b32 is the same
     hue and saturation, three steps down in lightness — 4.98:1 and 4.55:1.
     Every use of this token is text on a light ground, a hover fill behind white
     text, a border or a focus ring, so darkening it improves all of them and
     worsens none. The brand fill below is untouched: white on #70AD47 is
     2.71:1 and that is a decided trade, not an oversight. */
  --green-dark: #507b32;
  --green-tint: #f0f7ea;
  --ink: #222;
  --muted: #6b7280;
  --line: #e5e7eb;
  /* The same grey, dark enough to be a *boundary*. --line is 1.24:1 on white,
     which is fine for a rule between two table rows — a decorative separator —
     and fails the 3:1 WCAG asks of anything that tells you where a control
     starts and ends. Form controls take this one; dividers keep --line. */
  --line-strong: #a8afba;
  --bg: #f6f7f9;
  --danger: #b91c1c;
  --danger-tint: #fde2e1;
  /* Waiting on somebody else is not an error, and it was being told in red.
     A note out for signature, a model the reader guessed at, a serial that came
     back different — all of them are "somebody has to look at this", which is a
     third state the palette did not have. 7.1:1 on its own tint. */
  --warn: #92400e;
  --warn-tint: #fef3c7;
  /* One shape system, and the rule it follows:
     `--radius-pill` for anything whose height is its own label (badges, chips),
     `--radius-lg` for a container that holds controls,
     `--radius` for a control,
     `--radius-xs` for a ring drawn tight around one.
     A container and the control inside it are concentric: 14 outer minus the
     4px of padding between them lands exactly on 10. Four values had drifted
     off this (8, 9, 9, 12) and no longer said which of the four they were. */
  --radius-xs: 4px;
  --radius: 10px;
  --radius-lg: 14px;
  --radius-pill: 999px;
  /* One spacing scale, so six page stylesheets stop inventing their own. */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  --rail-w: 208px;
  /* Open deals read blue, won deals green — the same language the reports
     page uses, so a colour means one thing across the whole app. */
  --open: #2E6FA8;
  --fast: 150ms;
  --shadow: 0 6px 24px rgba(0, 0, 0, .05);
  --shadow-pop: 0 8px 28px rgba(0, 0, 0, .14);
  --ring: 0 0 0 3px rgba(112, 173, 71, .28);
}

* { box-sizing: border-box; }

/* Made for a thumb as well as a pointer.
   `manipulation` drops the ~300ms wait a browser holds every tap for in case a
   second one follows and means "zoom" — on the board, where the work is
   tapping cards and menu items, that delay is the whole feel of the app.
   Double-tap zoom goes with it; pinch does not, so nothing is actually
   unreachable. And the tap highlight is chosen rather than left as the
   platform's translucent blue-grey box, which on a green button read as a
   bruise. */
a, button, [role="button"], summary, label,
input[type="checkbox"], input[type="radio"], select {
  touch-action: manipulation;
}
html { -webkit-tap-highlight-color: rgba(80, 123, 50, .16); }

/* Keyboard focus, everywhere. Inputs had a ring; buttons, links and the menu
   items had nothing, so tabbing through the board was invisible. :focus-visible
   rather than :focus, so a mouse click doesn't leave a ring behind. */
:focus-visible {
  outline: 2px solid var(--green-dark);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: none; }
/* `input { width: 100% }` catches these too, and in a narrow cell it squashed
   a tick box to 20x13 — under even WCAG 2.2's 24px floor, and the wrong shape
   for a thumb. Sized here so the rule above cannot reach them. */
input[type="checkbox"], input[type="radio"] {
  width: 17px; height: 17px; min-width: 17px; margin: 0;
  accent-color: var(--green-dark); cursor: pointer;
}
/* The tick's hit area, not its ink. A 17px box inside a 40px target is the
   standard trade: the control stays visually light and the thumb still lands. */
td.tick, th.tick { position: relative; }
td.tick input[type="checkbox"] { outline-offset: 4px; }
td.tick input[type="checkbox"]::before {
  content: ""; position: absolute; inset: 0; cursor: pointer;
}

/* Someone who has asked their machine for less movement means it. Only the
   loading veil honoured this before. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

/* ---- The rail ----------------------------------------------------------- */
/* Built by mountShell() in app.js from a single NAV list. Signed out there is
   nothing to navigate to, so the login page renders without it. */
body.has-rail {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  min-height: 100vh;
}
/* `width: 100%` is load-bearing. `main` carries `margin: 0 auto`, and auto
   margins switch OFF a grid item's default stretch — so without this the
   content shrink-wraps to its own width instead of filling the column. The
   board hid it (a kanban is naturally wide); tables and forms collapsed. */
body.has-rail > main { grid-column: 2; width: 100%; }

.rail {
  display: flex; flex-direction: column; gap: 2px;
  background: #fff; border-right: 1px solid var(--line);
  padding: var(--s4) var(--s3);
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.rail .brand {
  font-weight: 800; letter-spacing: 1px; color: var(--green);
  text-decoration: none; font-size: 15px; padding: var(--s2) var(--s3) var(--s5);
}
.rail nav { display: flex; flex-direction: column; gap: 2px; }
.rail .railgap { flex: 1; }

.navitem {
  display: flex; align-items: center; gap: 10px;
  /* 40px tall + full-width target: comfortably past the 44px diagonal a thumb
     needs, and the whole row is clickable rather than just the words. */
  padding: 10px var(--s3); min-height: 40px;
  border: none; background: none; width: 100%;
  border-radius: var(--radius); cursor: pointer;
  color: var(--muted); font-size: 14px; font-family: inherit; text-align: left;
  text-decoration: none; white-space: nowrap;
  transition: background var(--fast), color var(--fast);
}
.navitem svg { width: 18px; height: 18px; flex: none; }
.navitem:hover { background: var(--bg); color: var(--ink); }
.navitem.on { background: var(--green-tint); color: var(--green-dark); font-weight: 600; }
.navitem.primary {
  background: var(--green); color: #fff; font-weight: 600; margin-top: var(--s4);
}
.navitem.primary:hover { background: var(--green-dark); color: #fff; }

/* ---- The bell and its panel --------------------------------------------- */
.navitem.bell { position: relative; }
.navitem.bell.has-unread { color: var(--ink); }
.navitem.bell .badge {
  margin-left: auto; min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--green); color: #fff;
  border-radius: var(--radius-pill); font-size: 11px; font-weight: 700; line-height: 1;
}
/* An author `display` outranks the UA stylesheet's `[hidden] { display: none }`,
   so without this the badge sat there as an empty green dot on nothing. */
.navitem.bell .badge[hidden] { display: none; }
/* Collapsed to icons there is no room beside the label, so it rides the icon. */
@media (max-width: 1100px) {
  .navitem.bell .badge {
    position: absolute; top: 4px; right: 4px; margin-left: 0;
    min-width: 16px; height: 16px; padding: 0 4px;
  }
}

.notifpanel {
  position: fixed; z-index: 60;
  width: min(340px, calc(100vw - 16px)); max-height: min(70vh, 520px);
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 12px 32px rgba(15, 23, 42, .16);
}
.notifhead {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.notiflist { overflow-y: auto; padding: var(--s2); }
.notif {
  display: block; width: 100%; text-align: left;
  border: none; background: none; cursor: pointer; font: inherit;
  padding: var(--s3); border-radius: var(--radius);
  /* A left rule, not a coloured background: unread has to be legible for
     somebody who cannot tell the two fills apart. */
  border-left: 3px solid transparent;
}
.notif:hover { background: var(--bg); }
.notif.unread { border-left-color: var(--green); background: var(--green-tint); }
.notif .what { display: block; font-size: 13px; color: var(--ink); }
.notif .when { display: block; font-size: 11px; color: var(--muted); margin-top: 2px; }
.notifpanel .empty { margin: 0; padding: var(--s4); font-size: 13px; color: var(--muted); }

/* Narrow laptop: icons only. A permanent 208px rail costs a kanban column. */
@media (max-width: 1100px) {
  :root { --rail-w: 60px; }
  .rail { align-items: center; padding: var(--s4) var(--s2); }
  .rail .brand { font-size: 0; padding: var(--s2) 0 var(--s4); }
  .rail .brand::before { content: "CC"; font-size: 15px; }
  .navitem { justify-content: center; padding: 10px; }
  .navitem span { display: none; }
}
/* Phone: back to a horizontal bar across the top, as it was. */
@media (max-width: 760px) {
  :root { --rail-w: auto; }
  body.has-rail { display: block; }
  .rail {
    flex-direction: row; align-items: center; gap: var(--s2);
    height: auto; border-right: none; border-bottom: 1px solid var(--line);
    padding: var(--s2) var(--s3); overflow-x: auto;
  }
  .rail .brand { padding: 0 var(--s2) 0 0; }
  .rail nav { flex-direction: row; }
  .navitem { width: auto; }
  .navitem.primary { margin-top: 0; }
}

/* ---- Header (pages that still use one) ---------------------------------- */
header {
  background: #fff;
  border-bottom: 1px solid var(--line);
  padding: 14px 24px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
header .logo {
  font-weight: 800;
  letter-spacing: 1px;
  color: var(--green);
  text-decoration: none;
}
header .spacer { flex: 1; }
header a:not(.logo), header button {
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 7px 13px;
  cursor: pointer;
  color: var(--ink);
  text-decoration: none;
  font-size: 14px;
  transition: background .12s, border-color .12s;
}
header a:not(.logo):hover, header button:hover {
  background: var(--green-tint);
  border-color: var(--green);
}
/* The primary action keeps its colour in the bar. Needs to name `header` to
   outrank the rule above, which is more specific than `a.newquote` alone. */
header a.newquote { background: var(--green); border-color: var(--green); color: #fff; font-weight: 600; }
header a.newquote:hover { background: var(--green-dark); border-color: var(--green-dark); }
/* The board's nav only exists once you are signed in, so it is a group that can
   be shown and hidden in one go rather than five separate links. */
header nav { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

main { max-width: 1100px; margin: 0 auto; padding: 24px; }
/* Let the browser even the lines rather than leaving one word stranded on the
   last one. Only headings: `balance` is capped at a few lines and costs
   measurable work on a paragraph. */
h1, h2, h3 { text-wrap: balance; }
h1 { font-size: 22px; margin: 0 0 16px; }

/* ---- Page furniture ----------------------------------------------------- */
/* Every page had its own idea of a heading, a filter bar and a table: four
   table styles, two search bars, three page headers, all slightly different.
   These are the one copy. Anything genuinely specific to a page still lives in
   that page's <style> block. */
.pagehead {
  display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap;
  margin-bottom: var(--s5);
}
.pagehead h1 { margin: 0; }
/* No `.sub` here any more: every page carried a sentence beside its title that
   was read once and then skipped forever. The title says what the page is.
   A card's own `.sub` is a different thing and stays — see `.card p.sub`.

   That subtitle also carried `flex: 1`, which is what pushed a header button to
   the far edge. `margin-left: auto` does that job now, and does it without
   depending on a paragraph being there. */
.pagehead .actions {
  display: flex; gap: var(--s2); align-items: center; margin-left: auto;
}

/* The search / filter row, as a card so it reads as one control surface. */
.filters {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--s3) var(--s4); margin-bottom: var(--s4); box-shadow: var(--shadow);
}
/* Checkboxes excluded rather than reset afterwards. The 130px floor exists so
   a filter select is not squeezed to nothing; applied to a tick box it made one
   130px wide, and the `min-width: 0` that used to undo it also undid the 17px
   the checkbox rule sets. One rule owns tick-box sizing now. */
.filters input:not([type="checkbox"]):not([type="radio"]), .filters select {
  width: auto; margin: 0; min-width: 130px;
}
.filters input.search { flex: 1; min-width: 200px; }
.filters label { margin: 0; font-size: 12px; }
.filters button { margin: 0; }
.filters .spacer { flex: 1; }
/* A label and its control travel together — without this the pair sits a line
   below its siblings, because the wrapping span isn't a flex item's child. */
.filters .fgroup { display: flex; align-items: center; gap: 6px; }
.filters .note { color: var(--muted); font-size: 12px; margin-left: auto; }

table.data { width: 100%; border-collapse: collapse; font-size: 13px; }
table.data th, table.data td {
  text-align: left; padding: 9px 8px; border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
/* Sticky heads: a long list scrolls past its own column names otherwise. */
table.data th {
  color: var(--muted); font-weight: 600; font-size: 11px;
  text-transform: uppercase; letter-spacing: .4px;
  position: sticky; top: 0; background: #fff; z-index: 1;
}
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* This is a counting tool, and nothing counted read louder than the date beside
   it — every figure was 13px regular ink, the same as every string. Weight is
   the whole change: it costs no space, it survives at a glance across a room,
   and it is the one thing that makes a stock table look like stock. */
table.data td.num { font-weight: 600; }
/* A quantity typed into a cell is still a quantity. `td.num` right-aligns the
   *cell*, and an input inside it went on rendering hard left in proportional
   figures — so a column of declared quantities, which is exactly what the CIPL
   step exists to compare, could not be read down. */
table.data td.num input {
  text-align: right; font-variant-numeric: tabular-nums;
}
/* A variance is the number this table is opened to find. Short by ten and over
   by ten are different problems, so they are not the same colour. */
.figure-short { color: var(--danger); font-weight: 700; }
.figure-over { color: var(--open); font-weight: 700; }
table.data td.muted { color: var(--muted); }
table.data tbody tr.pick { cursor: pointer; }
table.data tbody tr.pick:hover { background: var(--green-tint); }

.statgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; }
.stat { background: var(--bg); border-radius: var(--radius); padding: var(--s3) var(--s4); }
.stat .n { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.stat .l { color: var(--muted); font-size: 12px; margin-top: 2px; }
/* An empty table is the state you want before a test run — say so quietly. */
.stat.zero .n { color: var(--muted); }

.empty { color: var(--muted); font-size: 14px; text-align: center; padding: 38px 0; }
.hint { color: var(--muted); font-size: 12px; }
/* A hint under a table butts against the last row's border otherwise. Three
   pages solved this three ways — one with an inline style, two not at all. */
.card .hint { margin-top: 10px; }

/* A wide table scrolls inside its own box rather than pushing the page sideways.
   Was page-local in reports.html; the inventory table has eight columns and
   wants it too, so it lives here now. */
.tablewrap { overflow-x: auto; }

/* Row actions. Flexed on a wrapper inside the cell, never on the <td> itself:
   `display: flex` on a table cell throws away `vertical-align: middle`, and the
   actions then sit off the baseline of every other cell in their row. */
.rowactions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }

/* ---- Paged lists -------------------------------------------------------- */
/* The other half of `ccList`. Above the table: search, how many rows, and where
   you are in them. Below it: the two moves. Both are drawn by the control, so a
   card gets them by being handed to it rather than by carrying the markup. */
.listbar {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-bottom: var(--s3);
}
.listbar input.search { flex: 1; min-width: 180px; width: auto; margin: 0; }
.listbar select.rows { width: auto; min-width: 74px; margin: 0; }
/* Same tone and same right-alignment as `.filters .note`, which is the count
   idiom this page already uses in three places. */
.listbar .note {
  color: var(--muted); font-size: 12px; margin-left: auto;
  font-variant-numeric: tabular-nums;
}
.pager {
  display: flex; gap: 8px; justify-content: flex-end; align-items: center;
  margin-top: var(--s3);
}
/* Disabled here and nowhere else in the app: at the end of a list there is no
   next page, and a live-looking button that refuses is the thing this page
   argues against three times over. Greyed *and* unclickable says it plainly. */
.pager button[disabled] {
  opacity: .45; cursor: default; border-color: var(--line);
}
.pager button[disabled]:hover { background: none; border-color: var(--line); }
.chip {
  display: inline-block; font-size: 10px; text-transform: uppercase; letter-spacing: .4px;
  padding: 2px 8px; border-radius: var(--radius-pill); font-weight: 600;
  background: var(--green-tint); color: var(--green-dark);
}
/* #5c636e, not --muted: 4.30:1 on this tint is a miss, and the chip carries a
   warehouse kind somebody reads off a row. */
.chip.off { background: #eef2f7; color: #5c636e; }

/* ---- Cards -------------------------------------------------------------- */
.card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 22px;
  box-shadow: var(--shadow);
  margin-bottom: 20px;
}
.card h2 { font-size: 16px; margin: 0 0 12px; }
.card p.sub { color: var(--muted); margin: 0 0 16px; font-size: 13px; }
/* Prose sets its own measure. A sentence spanning a 1100px card runs to about
   150 characters a line, which is twice what anyone tracks comfortably. */
.card p.sub { max-width: 68ch; }
/* A submit button under a form needs air. Two pages patched this locally and a
   third forgot, so its button sat against the last input's focus ring. */
.card button.primary { margin-top: 14px; }
/* `.filters` is itself a bordered, shadowed white box. Nested inside a card that
   is also one, it reads as a box in a box — so inside a card it keeps the layout
   and drops the chrome. */
.card .filters {
  background: none; border: 0; box-shadow: none;
  padding: 0 0 var(--s3); margin-bottom: var(--s3);
}

/* ---- Forms -------------------------------------------------------------- */
/* `.caption` reads exactly like a label and is not one. A <label> names a
   control you can operate; the version and the currency on a quotation are
   worked out by the server and shown back, so a label there pointed at nothing
   — and, when one was given a `for`, at the wrong control entirely. */
label, .caption { display: block; font-size: 13px; margin: 12px 0 6px; color: var(--muted); }
input, select, textarea {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  font-size: 14px;
  font-family: inherit;
  color: var(--ink);
  background: #fff;
  transition: border-color .12s, box-shadow .12s;
}
/* Stated, not inherited. `line-height: normal` is the browser's own guess and
   it differs by control: a text input came out 37px tall while a select and a
   date input beside it came out 39px, so any row mixing them sat two pixels out
   of line — which is what Ethan saw on the delivery-note header.
   Not textarea: its line spacing is what somebody reads a paragraph through,
   and that is a different question. And not a date or time field, whose inner
   spin controls do their own layout inside the box — given a line-height they
   add it to their own and come out taller than everything else. */
input, select { line-height: 1.35; }
input[type="date"], input[type="time"] { line-height: normal; }
/* A box you cannot type in should look like one. These are read-only rather
   than disabled everywhere they appear — the POD number has to stay selectable
   so somebody can copy it onto an email, and a disabled control invites the
   click and explains nothing — so the grey is what carries "not yours to
   change". The focus ring goes with it: a green ring around a box that will
   not take a keystroke is an invitation to try. */
input[readonly], textarea[readonly] {
  background: var(--bg); color: var(--muted); cursor: default;
}
input[readonly]:focus, textarea[readonly]:focus {
  border-color: var(--line); box-shadow: none;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  /* --green-dark, not --green. The brand fill is 2.71:1 on white and the ring
     behind it composites to 1.29:1, so the focused state of an input was less
     visible than the resting border of the one beside it. */
  border-color: var(--green-dark);
  box-shadow: var(--ring);
}
/* Placeholders were the browser default, which is dark enough to read as a
   filled-in value. Somebody pressed "Add warehouse" expecting the example text
   to submit — a placeholder that looks like data is worse than none.
   3.3:1 on white, chosen between two failures: --muted's 4.83 is what read as a
   value, and the 2.5 of a lighter grey is a hint nobody can read. Placeholders
   are exempt from the 4.5 floor because they are not content, but they still
   have to be legible. `opacity: 1` because Firefox dims them by default.

   Darkened from #868e9c (3.30) to #767e8c (4.05) after the explanatory
   paragraphs came off the inventory page: "usually left blank" and "blank takes
   the oldest" are now the only thing on screen saying how those boxes work, so
   the hint has to survive being read. Still under --muted, so it cannot be
   mistaken for a filled-in value. */
::placeholder { color: #767e8c; opacity: 1; }
textarea { resize: vertical; min-height: 60px; }
/* A one-line box you can pull open. The CIPL's description and model are the
   two values worth reading in full — "Battery Swap Station" and a model with a
   space in it both cut off in a table cell — and a drag handle is the native
   way to say "make me bigger", which beats inventing one. `resize: both`
   because the interesting direction here is width. */
textarea.oneline {
  min-height: 0; height: 38.5px; resize: both; overflow: auto;
  white-space: pre; line-height: 1.35; padding: 8px 11px;
}
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0 14px; }

/* ---- Buttons ------------------------------------------------------------ */
button.primary {
  background: var(--green);
  color: #fff;
  border: none;
  padding: 11px 18px;
  border-radius: var(--radius);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: background .12s, transform .04s;
}
button.primary:hover { background: var(--green-dark); }
button.primary:active { transform: translateY(1px); }
/* A second primary beside the first: same size, different colour, so two ways
   of downloading the same thing do not read as one button duplicated. The dark
   green rather than a new hue — and it is the more legible of the two, at
   4.98:1 against white where the brand green is 2.71:1. Its hover has to be
   darker still, or hovering the green one would land on this exact colour. */
button.primary.alt { background: var(--green-dark); }
button.primary.alt:hover { background: #44692b; }

/* One press from writing. Booking serials in takes two presses — the first
   reads the workbook and writes nothing, the second commits it — and the only
   thing that changed between them was the button's own text, under the cursor,
   where nobody reads it. The light travels the rim so the change of meaning is
   on the control itself and not only in the sentence underneath.

   No wrapper: `isolation` makes the button its own stacking context, so both
   layers below sit inside it, above the button's background and beneath its
   label. `overflow` clips the square that spins to the button's shape. */
button.primary.armed { position: relative; isolation: isolate; overflow: hidden; }
button.primary.armed::before {
  content: ""; position: absolute; z-index: -2;
  top: 50%; left: 50%; width: 240%; aspect-ratio: 1;
  background: conic-gradient(transparent 0 62%,
    rgba(255, 255, 255, .35) 74%, #fff 82%,
    rgba(255, 255, 255, .35) 90%, transparent);
  /* One `transform`, not the individual `rotate`/`translate` properties, which
     nothing else in this file uses. */
  transform: translate(-50%, -50%) rotate(0turn);
  animation: cc-armed 2.6s linear infinite;
}
/* The face, redrawn two pixels in, so only a rim of the gradient shows. */
button.primary.armed::after {
  content: ""; position: absolute; z-index: -1; inset: 2px;
  border-radius: calc(var(--radius) - 2px); background: var(--green);
}
button.primary.armed:hover::after { background: var(--green-dark); }
@keyframes cc-armed { to { transform: translate(-50%, -50%) rotate(1turn); } }

/* Keep the rim, drop the movement — the rule the loading veil already follows.
   Written out rather than left to the blanket reduced-motion rule above, which
   sets `animation-iteration-count: 1` and would freeze the gradient wherever it
   had reached: a lopsided smear of light stuck on one edge. */
@media (prefers-reduced-motion: reduce) {
  button.primary.armed::before {
    animation: none; background: #fff; opacity: .5;
  }
}

button.ghost {
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 12px;
  cursor: pointer;
  font-size: 13px;
  color: var(--ink);
  transition: background .12s, border-color .12s;
}
button.ghost:hover { background: var(--green-tint); border-color: var(--green); }

button.mini {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  background: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  /* 32px tall, up from 24. A row action is the most-tapped control in a table
     and it was the smallest thing on the page; this clears WCAG 2.2's 24px
     floor with room, without the 44px that would double every row's height. */
  min-height: 32px;
  padding: 5px 10px;
  font-size: 12px;
  cursor: pointer;
  color: var(--ink);
  transition: background .12s, border-color .12s;
}
button.mini svg { width: 14px; height: 14px; flex: none; }
button.mini:hover { background: var(--green-tint); border-color: var(--green); }
button.mini.danger { color: var(--danger); }
button.mini.danger:hover { background: var(--danger-tint); border-color: var(--danger); }

button.link {
  background: none; border: none; color: var(--green-dark);
  cursor: pointer; font-size: 12px; padding: 0; text-decoration: underline;
}
/* A link that reads as a button inside the page body. Navigation itself lives
   in the header on every page, so these are for in-page actions only. */
a.btnlink, a.homebtn {
  display: inline-block; border: 1px solid var(--line); color: var(--ink);
  text-decoration: none; padding: 8px 14px; border-radius: var(--radius); font-size: 14px;
  transition: background .12s, border-color .12s;
}
a.btnlink:hover, a.homebtn:hover { background: var(--green-tint); border-color: var(--green); }
a.newquote {
  background: var(--green); color: #fff; text-decoration: none; padding: 8px 14px;
  border-radius: var(--radius); font-size: 14px; font-weight: 600;
}
a.newquote:hover { background: var(--green-dark); }

/* ---- Sub-tabs: switching sections inside one page ---------------------- */
/* Here rather than in a page's own <style> because the CSS and the JS that
   drives it are a matched pair — `.on` mirrors `aria-selected` — and a page that
   copied one without the other would get a strip that looks right and announces
   nothing. The rail learned that lesson the expensive way.

   Pills, not an underline: the rail already spends --green-tint on "selected",
   and an underline would be a second visual grammar for one idea on one screen.
   `.subtab.on` is deliberately the same declaration as `.navitem.on`. */
.subtabs {
  display: flex; gap: 4px; width: max-content; max-width: 100%;
  margin: 0 0 var(--s4); padding: 4px;
  border: 1px solid var(--line); border-radius: var(--radius-lg); background: #fff;
}
.subtab {
  appearance: none; border: 0; background: none; margin: 0;
  font: inherit; font-size: 13px; font-weight: 500; color: var(--muted);
  padding: 7px 16px; border-radius: var(--radius); white-space: nowrap; cursor: pointer;
  transition: background .12s, color .12s;
}
.subtab:hover { color: var(--ink); }
.subtab.on { background: var(--green-tint); color: var(--green-dark); font-weight: 600; }
.subtab:focus-visible { outline: 2px solid var(--green-dark); outline-offset: 2px; }
/* Narrow: a full-width segmented control, so neither tab is a thumb-stretch. */
@media (max-width: 640px) {
  .subtabs { width: auto; }
  /* `min-width: 0` or `flex: 1 1 0` never wins: `white-space: nowrap` sets a
     min-content floor, so the four tabs measured 62/71/83/89px and the strip
     read ragged. */
  /* Wrapping, now there are five. `white-space: nowrap` sets a min-content
     floor no `min-width: 0` can beat, so a fifth tab in one row shrinks each box
     below its own label and the text spills out of it — the same rule that made
     the strip read ragged, biting from the other side. Three then two, every one
     full-size. */
  .subtabs { flex-wrap: wrap; }
  .subtab { flex: 1 1 28%; min-width: 0; text-align: center; padding: 11px 8px; }
}

/* ---- Messages + status pills ------------------------------------------- */
.msg { font-size: 13px; margin-top: 10px; min-height: 18px; }
.msg.ok { color: var(--green-dark); }
.msg.err, .error { color: var(--danger); font-size: 13px; }
/* Four tones, and the bare one is a tone rather than an oversight. A `.pill`
   with no modifier used to render as padded text on nothing, so "out for
   signature" and "verified" were invisible and identical — the two states the
   delivery-notes table is scanned for. Grey is now the resting state and every
   pill has a ground. */
.pill {
  display: inline-block; padding: 2px 9px; border-radius: var(--radius-pill); font-size: 12px;
  background: #eef2f7; color: #5c636e;
}
.pill.on, .status-badge.on { background: var(--green-tint); color: var(--green-dark); }
.pill.wait, .status-badge.wait { background: var(--warn-tint); color: var(--warn); }
/* Red is spent on problems only. It used to paint `draft` and `closed`, which
   are the ordinary states of a note being written and a depot being retired. */
.pill.off, .status-badge.off { background: var(--danger-tint); color: var(--danger); }

/* ---- Toasts (replace alert()) ------------------------------------------ */
#toasts {
  position: fixed; right: 18px; bottom: 18px; z-index: 1000;
  display: flex; flex-direction: column; gap: 10px; max-width: 360px;
}
.toast {
  background: #fff; border: 1px solid var(--line); border-left: 4px solid var(--muted);
  border-radius: var(--radius); box-shadow: var(--shadow-pop);
  padding: 11px 14px; font-size: 13px; color: var(--ink);
  animation: toast-in .16s ease-out;
}
.toast.ok { border-left-color: var(--green); }
.toast.err { border-left-color: var(--danger); }
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* The assistant launcher owns the bottom-right corner, so toasts move over. */
body.has-assistant #toasts { right: auto; left: 18px; align-items: flex-start; }

/* ---- Floating assistant (bottom-right launcher + chat-style panel) ------ */
.assistant {
  position: fixed; right: 18px; bottom: 18px; z-index: 1000;
  display: flex; flex-direction: column; align-items: flex-end; gap: 10px;
}
.assistant-fab {
  position: relative; width: 54px; height: 54px; border-radius: 50%; border: none;
  background: var(--green); color: #fff; font-size: 23px; line-height: 1; cursor: pointer;
  box-shadow: var(--shadow-pop); display: grid; place-items: center;
  transition: background .12s ease, transform .12s ease;
}
.assistant-fab:hover { background: var(--green-dark); transform: translateY(-1px); }
.assistant-fab:focus-visible { outline: none; box-shadow: var(--shadow-pop), var(--ring); }
/* Unread marker: something in the last draft still needs the rep's attention. */
.assistant-fab .badge {
  position: absolute; top: 2px; right: 2px; width: 12px; height: 12px; border-radius: 50%;
  background: var(--danger); border: 2px solid #fff;
}
.assistant-panel {
  width: 380px; max-width: calc(100vw - 36px); max-height: min(72vh, 560px);
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop); display: flex; flex-direction: column; overflow: hidden;
  animation: assistant-in .14s ease-out;
}
@keyframes assistant-in {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
  to { opacity: 1; transform: none; }
}
.assistant-head {
  display: flex; align-items: center; gap: 8px; padding: 12px 8px 12px 14px;
  border-bottom: 1px solid var(--line); background: var(--green-tint);
}
.assistant-head .title { font-weight: 700; font-size: 14px; color: var(--green-dark); flex: 1; }
.assistant-head .close {
  background: none; border: none; color: var(--muted); font-size: 22px; line-height: 1;
  padding: 2px 10px; cursor: pointer; border-radius: var(--radius);
}
.assistant-head .close:hover { background: rgba(0, 0, 0, .06); color: var(--ink); }
.assistant-body { padding: 14px; overflow-y: auto; }
.assistant-body .sub { margin: 0 0 10px; }
.assistant-body textarea { min-height: 76px; }
.assistant-body button.primary { width: 100%; margin-top: 10px; }

/* ---- Confirm dialog (replace confirm()) -------------------------------- */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(17, 24, 39, .38);
  display: flex; justify-content: center; z-index: 1001;
  /* Reaching the bottom of a long dialog used to hand the scroll to the board
     behind it, so the page you came back to was somewhere else. */
  overscroll-behavior: contain;
  /* Centred by the child's own `margin: auto`, NOT by `align-items: center`.
     They look identical until a dialog is taller than the window, and then they
     could not be more different: `align-items: center` overflows a tall child
     equally in both directions and the top half becomes unreachable — there is
     nothing to scroll, the content is simply above the viewport. Updates on a
     busy deal, Documents, and version history all grow with their content, so
     any of them can reach that size on a laptop. `margin: auto` centres when
     there is room and lets the backdrop scroll when there is not. */
  align-items: flex-start;
  overflow-y: auto;
  padding: 24px 16px;
}
.modal {
  background: #fff; border-radius: var(--radius-lg); box-shadow: var(--shadow-pop);
  padding: 22px; width: 380px; max-width: calc(100vw - 32px);
  /* This is what centres it. See the note on `.modal-backdrop`. */
  margin: auto;
}
.modal h3 { margin: 0 0 8px; font-size: 16px; }
.modal p { margin: 0 0 18px; color: var(--muted); font-size: 14px; white-space: pre-line; }
/* The 18px matches `.modal p`'s bottom margin, and collapses with it in the
   dialogs that are only a question and two buttons. It is load-bearing for the
   ones with a field: `input, select, textarea` carry no bottom margin, so
   without this Cancel/Confirm sat directly against the control — underneath its
   3px focus ring, which read as the buttons overlapping the dropdown. */
.modal .row { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }

/* Quotation status chip. Shared so the funnel card and the version history
   colour "sent" and "rejected" the same way. */
.status {
  font-size: 10px; text-transform: uppercase; letter-spacing: .4px;
  padding: 1px 7px; border-radius: var(--radius-pill); font-weight: 600;
}
.status.draft, .status.lead { background: #eef2f7; color: var(--muted); }
.status.sent, .status.accepted { background: var(--green-tint); color: var(--green-dark); }
.status.rejected, .status.expired { background: var(--danger-tint); color: var(--danger); }

/* The changelog, inside a modal. One-off; goes with the release that needed it. */
.modal.changelog { width: 560px; }
.modal.changelog .changes { margin: 0; padding: 0; list-style: none; }
.modal.changelog .changes li {
  font-size: 13px; line-height: 1.55; color: var(--muted);
  padding: 12px 0; border-top: 1px solid var(--line);
}
.modal.changelog .changes li:first-child { border-top: none; padding-top: 4px; }
/* A dated heading per release. Two of them now, newest first — which is what a
   changelog is, and it means somebody who joined last week can still read what
   changed before they arrived. */
.modal.changelog h4 {
  margin: 20px 0 8px; font-size: 11px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .5px;
}
.modal.changelog h4:first-child { margin-top: 0; }
.modal.changelog .changes b { color: var(--ink); }
/* A day's worth of entries is taller than a laptop. The list scrolls inside the
   modal rather than the modal growing off the screen and taking Close with it. */
.modal.changelog .body { max-height: min(60vh, 460px); overflow-y: auto; }

/* Version history listing inside a modal. */
.modal.history { width: 560px; }
.histtable { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 18px; }
.histtable th, .histtable td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--line); }
.histtable th { color: var(--muted); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .4px; }
.histtable td.num, .histtable th.num { text-align: right; font-variant-numeric: tabular-nums; }
.histtable tr.current { background: var(--green-tint); }
.histtable tr.current td:first-child { box-shadow: inset 3px 0 0 var(--green); }
.modal button { padding: 9px 15px; border-radius: var(--radius); font-size: 14px; cursor: pointer; }
.modal button.cancel { background: #fff; border: 1px solid var(--line); }
.modal button.cancel:hover { background: var(--bg); }
.modal button.go { background: var(--green); color: #fff; border: none; font-weight: 600; }
.modal button.go:hover { background: var(--green-dark); }
.modal button.go.danger { background: var(--danger); }

/* Documents filed against a deal (the customer's PO). */
.modal.docs { width: 520px; }
.doclist { margin: 14px 0; border-top: 1px solid var(--line); }
.doclist .doc { display: flex; align-items: center; gap: 10px; padding: 9px 2px; border-bottom: 1px solid var(--line); font-size: 13px; }
.doclist .doc .name { flex: 1; text-align: left; background: none; border: none; padding: 0; cursor: pointer;
                      color: var(--green-dark); font-weight: 600; font-size: 13px; text-decoration: underline; }
.doclist .doc .meta { color: var(--muted); font-size: 12px; white-space: nowrap; }
.doclist .none { padding: 14px 2px; color: var(--muted); font-size: 13px; border-bottom: 1px solid var(--line); }
.uploadbtn { display: inline-block; margin: 0 0 12px; }
.uploadbtn span { display: inline-block; border: 1px dashed var(--line); border-radius: var(--radius);
                  padding: 9px 14px; font-size: 13px; cursor: pointer; color: var(--green-dark);
                  transition: background .12s, border-color .12s; }
.uploadbtn span:hover { background: var(--green-tint); border-color: var(--green); }
/* Lit while a file is over the panel, so the target is somewhere rather than
   nowhere. Same shape as the quotation form's photo box. */
.uploadbtn span.over {
  background: var(--green-tint); border-color: var(--green); color: var(--green-dark);
}

/* The deal's running log: what the client said, and when. The box to write in
   comes first — posting is why the panel was opened; reading back is second. */
.modal.notes { width: 540px; }
.modal.notes textarea { min-height: 72px; }
.noteadd { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.noteadd .spacer { flex: 1; }
.noteadd .hint { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.notelist { margin: 14px 0 4px; max-height: 46vh; overflow-y: auto; border-top: 1px solid var(--line); }
.notelist .note { padding: 10px 2px; border-bottom: 1px solid var(--line); }
.notelist .note .meta { display: flex; align-items: baseline; gap: 8px; color: var(--muted); font-size: 11px; }
.notelist .note .meta .link { margin-left: auto; visibility: hidden; }
.notelist .note:hover .meta .link { visibility: visible; }
.notelist .note .body { font-size: 13px; margin-top: 3px; white-space: pre-wrap; overflow-wrap: anywhere; }
.notelist .none { padding: 16px 2px; color: var(--muted); font-size: 13px; }

/* ---- Loading veil ------------------------------------------------------- */
/* Covers the page while data is in flight, so nobody edits a form that is
   about to be overwritten by the response, or clicks Revise twice. */
.loading-veil {
  position: fixed; inset: 0; z-index: 1100;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
  background: rgba(246, 247, 249, .72);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  animation: veil-in .14s ease-out;
  cursor: progress;
}
@keyframes veil-in { from { opacity: 0; } to { opacity: 1; } }
/* The logo is a 2.8:1 wordmark, so it reads at width — a circle around it
   would shrink it to an illegible smudge. Ring becomes a slim bar beneath. */
.loading-veil .mark { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.loading-veil .mark img {
  width: 168px; max-width: 60vw; height: auto;
  animation: veil-breathe 1.8s ease-in-out infinite;
}
.loading-veil .mark .ring {
  width: 168px; max-width: 60vw; height: 3px; border-radius: var(--radius-pill);
  background: rgba(112, 173, 71, .18); overflow: hidden; position: relative;
}
.loading-veil .mark .ring::after {
  content: ""; position: absolute; inset: 0; width: 40%; border-radius: var(--radius-pill);
  background: var(--green); animation: veil-sweep 1.1s ease-in-out infinite;
}
@keyframes veil-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
@keyframes veil-breathe { 0%, 100% { opacity: 1; } 50% { opacity: .62; } }
.loading-veil .msg { font-size: 13px; color: var(--muted); font-weight: 500; }
/* Respect a reduced-motion preference: keep the veil, drop the movement. */
@media (prefers-reduced-motion: reduce) {
  .loading-veil .mark img, .loading-veil .mark .ring::after { animation: none; }
  .loading-veil .mark .ring::after { width: 100%; opacity: .5; }
}

/* Being asked to wait is not being told you were wrong, and the sign-in form
   was saying it in the same red as a bad password. `--warn` already carries
   exactly this meaning everywhere else in the app: "somebody has to wait for
   something", per the note beside the token. The seconds are the point, so
   they are the biggest thing in the block, and the bar drains so there is
   something to watch rather than a number that only changes once a second. */
.signin-wait {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: var(--radius);
  background: var(--warn-tint);
  color: var(--warn);
  font-size: 13px;
  line-height: 1.45;
  display: grid;
  gap: 8px;
}
.signin-wait .n { font-weight: 700; font-variant-numeric: tabular-nums; }
.signin-wait .bar {
  height: 3px; border-radius: var(--radius-pill);
  background: rgba(146, 64, 14, .18); overflow: hidden;
}
.signin-wait .bar i {
  display: block; height: 100%; border-radius: inherit;
  background: currentColor; transform-origin: left center;
  transition: transform linear;
}
/* Nothing left to wait for. */
.signin-wait.ready { background: var(--green-tint); color: var(--green-dark); }
.signin-wait.ready .bar { display: none; }

.hidden { display: none !important; }
/* Present for a screen reader, absent on screen. For the header of a column that
   holds only buttons: `<th></th>` announces nothing, and a visible "Actions"
   would be a label for something that needs none. Clip rather than
   `display: none`, which would take it out of the accessibility tree too. */
/* Out of the way until it is wanted, and unmistakable once it is. Not
   `.visually-hidden`, because a skip link that stays hidden while focused is
   the same as no skip link. */
.skiplink {
  position: fixed; top: -100px; left: var(--s3); z-index: 2000;
  background: #fff; color: var(--green-dark); font-size: 13px; font-weight: 600;
  padding: 10px 16px; border: 1px solid var(--green-dark);
  border-radius: var(--radius); box-shadow: var(--shadow-pop);
  text-decoration: none; transition: top var(--fast);
}
.skiplink:focus { top: var(--s3); }
main:focus { outline: none; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* A checkbox that reads as a filter rather than a form field. Used by the
   inventory and service filter bars. */
label.tick { display: inline-flex; align-items: center; gap: 6px; margin: 0;
             font-size: 12.5px; color: var(--muted); white-space: nowrap; }
/* No `width: auto` here any more. It ties with `input[type="checkbox"]` on
   specificity and wins on source order, which left the filter tick boxes at
   their 13px intrinsic size while every other tick on the page was 17. The
   checkbox rule near the top is the one that sizes them now. */
label.tick input { margin: 0; }
