/* app.css — one stylesheet, light and dark.
 *
 * Colours are CSS custom properties so the dark theme is a variable swap
 * rather than a second set of rules. Two sets of rules drift; one set with
 * two palettes cannot.
 *
 * Theme resolution, in order:
 *   1. data-theme on <html>, written by app.js from the saved choice
 *   2. the operating system preference, when nothing is saved
 *   3. light
 *
 * No @import, no web fonts, no CDN. style-src is 'self' (R6.11) and a font
 * from someone else's origin is someone else's origin (R10.3).
 */

:root {
  --bg:        #ffffff;
  --bg-soft:   #f6f7f9;
  --bg-raised: #ffffff;
  --fg:        #1a1d21;
  --fg-soft:   #5b6570;
  --line:      #dfe3e8;
  --line-soft: #eceef1;
  --link:      #1a4fa0;
  --link-hover:#123a78;
  --ok-bg:     #e8f6ec;  --ok-fg:   #1c5b2e;  --ok-line: #b7e0c2;
  --err-bg:    #fdecec;  --err-fg:  #8f2020;  --err-line:#f3bdbd;
  /* "Running low" on the dashboard. Its own token, not a tint of the error
   * colour: low stock is a thing to plan around, not a fault, and the two
   * must not read the same at a glance. */
  --warn-fg:   #9a6413;
  --focus:     #1a4fa0;
  --shadow:    0 1px 2px rgba(16,24,40,.06), 0 1px 3px rgba(16,24,40,.08);
  /* The chart and bar colour. It was defined only in the two DARK blocks, so
   * in light mode var(--accent) was undefined: every healthy bar drew with
   * no colour at all and the fortnight chart drew black. Dark mode hid it,
   * which is the mode the screenshots that "looked fine" were taken in. */
  --accent:    #2f6fd6;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:        #14171a;
    --bg-soft:   #1a1e22;
    --bg-raised: #1e2328;
    --fg:        #e7eaee;
    --fg-soft:   #9aa4b0;
    --line:      #2e353c;
    --line-soft: #252b31;
    --link:      #7fb0ff;
    --accent:    #7fb0ff;
    --link-hover:#a6c8ff;
    --ok-bg:     #14301d;  --ok-fg:   #8fd9a6;  --ok-line: #245c35;
    --err-bg:    #351718;  --err-fg:  #f2a0a0;  --err-line:#6a2a2c;
    --warn-fg:   #e0a955;
    --focus:     #7fb0ff;
    --shadow:    none;
  }
}

:root[data-theme="dark"] {
  --bg:        #14171a;
  --bg-soft:   #1a1e22;
  --bg-raised: #1e2328;
  --fg:        #e7eaee;
  --fg-soft:   #9aa4b0;
  --line:      #2e353c;
  --line-soft: #252b31;
  --link:      #7fb0ff;
  --accent:    #7fb0ff;
  --accent:    #7fb0ff;
  --link-hover:#a6c8ff;
  --ok-bg:     #14301d;  --ok-fg:   #8fd9a6;  --ok-line: #245c35;
  --err-bg:    #351718;  --err-fg:  #f2a0a0;  --err-line:#6a2a2c;
  --warn-fg:   #e0a955;
  --focus:     #7fb0ff;
  --shadow:    none;
}

* { box-sizing: border-box; }

html { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

.wrap { max-width: 1100px; margin: 0 auto; padding: 0 16px; }

/* Standalone pages — sign in, set a password, the installer. A form of four
 * fields inside a 1100px column sits against the left edge and reads as a
 * layout bug. These get a narrow centred column instead, and their inputs
 * fill it rather than stopping at the 460px form default. */
.wrap.narrow {
  max-width: 27rem;
  padding-top: 8vh;
}
.wrap.narrow input,
.wrap.narrow select,
.wrap.narrow textarea { max-width: 100%; }
.wrap.narrow button { width: 100%; margin-top: 18px; }
.wrap.narrow h1 { margin-top: 0; }
.wrap.narrow .hint { margin-top: 6px; }

@media (max-width: 480px) {
  .wrap.narrow { padding-top: 4vh; }
}

a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); text-decoration: underline; }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ---------------------------------------------------------------- nav
 *
 * The open/closed state is a hidden checkbox, not a <details>. Current
 * Chrome hides a closed details' content via ::details-content, which a
 * child's `display` cannot override — so the details version worked in
 * older browsers and showed an empty bar in new ones. A checkbox is state
 * this stylesheet fully controls.
 */

nav {
  background: var(--bg-soft);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}

.navbar {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 54px;
  padding-top: 6px;
  padding-bottom: 6px;
  position: relative;
}

/* Off-screen rather than display:none, so it stays focusable by keyboard. */
.navtoggle, .moretoggle {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  pointer-events: none;
}

.burger {
  display: none;
  margin: 0;
  font-weight: 400;
  cursor: pointer;
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--bg-raised);
  line-height: 1;
  user-select: none;
}
.navtoggle:focus-visible + .burger { outline: 2px solid var(--focus); outline-offset: 2px; }

.panel {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
  min-width: 0;
}

/* inline-flex, not inline-block.
 *
 * As inline-block each item sat on its own line box, and the leading of
 * that line box made the "More" wrapper taller than a plain link — so the
 * label drifted a few pixels below the row. Flex items have no line box
 * between the container and the text, so every item lines up from the same
 * edge whatever is inside it. */
.panel a, .morelabel {
  display: inline-flex;
  align-items: center;
  /* The generic form `label` rule below sets margin and font-weight. A
   * class beats an element selector on `display`, but it does NOT undo
   * properties the class never mentions — so the nav label inherited a
   * 12px top margin and weight 600, which pushed it a few pixels below the
   * row and made it bolder than its neighbours. Reset both here rather
   * than narrowing the form rule, because the next label added to a nav
   * would hit exactly the same trap. */
  margin: 0;
  font-weight: 400;
  padding: 8px 11px;
  border-radius: 7px;
  color: var(--fg);
  white-space: nowrap;
  font-size: 15px;
  line-height: 1.25;
  cursor: pointer;
  user-select: none;
}
.panel a:hover, .morelabel:hover {
  background: var(--bg-raised);
  text-decoration: none;
}
.panel a[aria-current="page"] {
  background: var(--bg-raised);
  box-shadow: inset 0 -2px 0 var(--link);
  font-weight: 600;
}

.more { position: relative; display: inline-flex; align-items: center; }
.morelabel::after { content: " \25BE"; color: var(--fg-soft); }
.moretoggle:focus-visible + .morelabel { outline: 2px solid var(--focus); outline-offset: 2px; }

.subpanel {
  display: none;
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  min-width: 200px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 9px;
  box-shadow: var(--shadow);
  padding: 6px;
  z-index: 30;
}
.moretoggle:checked ~ .subpanel { display: flex; flex-direction: column; }

/* The backdrop that closes the menu when you click anywhere else. Only there
 * while the menu is open, and under the panel so the menu's own links still
 * take the click. */
/* The bar's own links stay above the backdrop — it lives inside the nav, so
 * within the nav they need to outrank it. Clicking another menu item while
 * More is open goes to that page, as it should; clicking the page below
 * just closes the menu. */
.navbar a, .morelabel { position: relative; z-index: 21; }
.moreback { display: none; }
.moretoggle:checked ~ .moreback {
  display: block;
  position: fixed;
  inset: 0;
  /* Under the nav bar (z-index 20 too, but the nav comes later in the
   * document so it wins), and above everything else on the page. The menu's
   * own links and the other nav links stay clickable; a click anywhere on
   * the page below closes the menu. */
  z-index: 19;
  background: transparent;
  cursor: default;
}
.subpanel a { display: block; }
.subdiv { display: block; height: 1px; margin: 5px 4px; background: var(--line); }

.navtail {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
  white-space: nowrap;
}
.who { color: var(--fg-soft); font-size: 14px; }

#theme {
  border: 1px solid var(--line);
  background: var(--bg-raised);
  color: var(--fg);
  border-radius: 7px;
  padding: 7px 10px;
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
}
#theme:hover { border-color: var(--fg-soft); }

/* ------------------------------------------------------------ content */

h1 { font-size: 26px; margin: 28px 0 14px; letter-spacing: -.01em; }
h2 { font-size: 19px; margin: 28px 0 10px; }
p  { margin: 10px 0; }

.hint { color: var(--fg-soft); font-size: 14px; }
p.hint { max-width: 70ch; }

.card {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 16px;
  margin: 14px 0;
  box-shadow: var(--shadow);
}
.card > :first-child { margin-top: 0; }
.card > :last-child  { margin-bottom: 0; }

.ok, .err {
  border-radius: 8px;
  padding: 12px 14px;
  margin: 14px 0;
  border: 1px solid;
}
.ok  { background: var(--ok-bg);  color: var(--ok-fg);  border-color: var(--ok-line); }
.err { background: var(--err-bg); color: var(--err-fg); border-color: var(--err-line); }
.ok > :first-child, .err > :first-child { margin-top: 0; }
.ok > :last-child,  .err > :last-child  { margin-bottom: 0; }
.err ul, .ok ul { margin: 6px 0; padding-left: 20px; }

/* -------------------------------------------------------------- tables
 *
 * Headers used to drift out of line with their rows because action cells
 * hold stacked forms of different heights. vertical-align: top pins every
 * cell to the same line regardless of how tall its neighbour grows.
 */

table {
  width: 100%;
  border-collapse: collapse;
  margin: 12px 0 20px;
  font-size: 15px;
}
th, td {
  text-align: left;
  vertical-align: top;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line-soft);
}
/* A header row is a first row made ONLY of <th>. Label-and-value tables
 * (Verify, order details, the dashboard's money) put a <th> label beside a
 * <td> value in every row, and the old "first row of th" rule styled their
 * first label as a column heading. Two rules, not one list: a browser that
 * does not know :has() drops the whole rule it appears in, and thead th must
 * survive that. */
thead th { border-bottom: 1px solid var(--line); color: var(--fg-soft);
           font-size: 12.5px; font-weight: 600; letter-spacing: .04em;
           text-transform: uppercase; white-space: nowrap; }
tr:first-child:not(:has(td)) th {
  border-bottom: 1px solid var(--line);
  color: var(--fg-soft);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}
tbody tr:hover td { background: var(--bg-soft); }

/* Money and counts line up on the right, header included — a number column
 * whose header sits left reads as a different column. */
.num, th.num, td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

td form + form { margin-top: 6px; }
td .inline { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* --------------------------------------------------------------- forms */

/* A label that SITS ABOVE a field is a caption: bold, spaced, own line.
 * A label that WRAPS a checkbox or radio is a choice the person reads and
 * clicks — bolding it makes an ordinary option look like a heading, and
 * the address list on checkout came out entirely bold because of it.
 * :has() tells the two apart; browsers without it simply keep the caption
 * styling, which is the safe way to be wrong. */
label {
  display: block;
  margin: 12px 0 4px;
  font-size: 14px;
  font-weight: 600;
}
label:has(> input[type=checkbox]),
label:has(> input[type=radio]) {
  font-weight: 400;
  font-size: 15px;
  margin: 6px 0;
  cursor: pointer;
}
td label, .inline label { display: inline-block; margin: 0 6px 0 0; font-weight: 400; }

/* input[type=search] was missing from this list, so every search box on the
 * site fell back to the browser's own — a thin unstyled strip next to
 * properly sized buttons. */
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=search], input[type=tel], input[type=url],
input[type=date], input[type=datetime-local], input[type=file], select, textarea {
  width: 100%;
  max-width: 460px;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 15px;
}
textarea { min-height: 90px; resize: vertical; }
input:hover, select:hover, textarea:hover { border-color: var(--fg-soft); }
input::placeholder, textarea::placeholder { color: var(--fg-soft); opacity: .8; }

/* Inside a table an input must not stretch the column. */
td input[type=text], td input[type=number], td input[type=date], td select {
  width: auto;
  max-width: 100%;
  padding: 6px 8px;
  font-size: 14px;
}

button, .btn {
  display: inline-block;
  padding: 9px 16px;
  border: 1px solid var(--link);
  border-radius: 7px;
  background: var(--link);
  color: #fff;
  font: inherit;
  font-size: 15px;
  cursor: pointer;
}
button:hover { background: var(--link-hover); border-color: var(--link-hover); }

/* A button that is really a link: same affordance, no box. */
button.link {
  background: none;
  border: none;
  color: var(--link);
  padding: 2px 4px;
  text-decoration: underline;
  font-size: inherit;
}
button.link:hover { color: var(--link-hover); background: none; }

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

.grid2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 20px;
}
.grid2 input, .grid2 select { max-width: 100%; }

code {
  background: var(--bg-soft);
  border: 1px solid var(--line-soft);
  border-radius: 5px;
  padding: 2px 6px;
  font-size: 14px;
  word-break: break-all;
}

.spacer { flex: 1 1 auto; }

/* -------------------------------------------------------------- mobile */

@media (max-width: 880px) {
  .burger { display: inline-block; order: 1; }
  .navtail { order: 2; margin-left: auto; }
  .who { display: none; }

  /* Closed by default; the checkbox opens it. Both states are set here, so
   * nothing depends on a browser default. */
  .panel {
    display: none;
    order: 3;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    gap: 0;
    padding: 4px 0 10px;
    border-top: 1px solid var(--line-soft);
    margin-top: 6px;
  }
  .navtoggle:checked ~ .panel { display: flex; }

  .navbar { flex-wrap: wrap; row-gap: 6px; }
  .panel a, .morelabel { padding: 12px 6px; font-size: 16px; }
  .panel > a + a { border-top: 1px solid var(--line-soft); }

  /* In a drawer the group opens in place rather than floating over it. */
  .more { display: block; position: static; }
  .panel a, .morelabel { display: flex; }
  .morelabel { border-top: 1px solid var(--line-soft); }
  .subpanel {
    position: static;
    border: none;
    box-shadow: none;
    background: none;
    padding: 0 0 0 14px;
    min-width: 0;
  }
  .subpanel a { border-top: 1px solid var(--line-soft); }

  h1 { font-size: 22px; margin-top: 20px; }

  /* Wide tables scroll rather than squeezing columns into unreadable
   * slivers. Done here rather than by wrapping every table in every page,
   * so no page had to change. */
  table { display: block; overflow-x: auto; white-space: nowrap; }
  table.stack { white-space: normal; }

  .grid2 { grid-template-columns: 1fr; }
  input[type=text], input[type=email], input[type=password], input[type=number],
  input[type=date], input[type=datetime-local], select, textarea { max-width: 100%; }
}

@media (max-width: 420px) {
  .wrap { padding: 0 12px; }
  .brand { font-size: 15px; }
}

@media print {
  nav { display: none; }
  body { background: #fff; color: #000; }
}

/* Chips — a short code with its own remove control, used where a cell holds
 * a set rather than a value. Previously these ran together as plain text
 * with loose crosses between them, and which cross belonged to which code
 * was genuinely ambiguous. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 4px 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-soft);
  font-size: 14px;
  line-height: 1.2;
  white-space: nowrap;
}
.chip button.link {
  padding: 0 5px;
  text-decoration: none;
  font-size: 16px;
  line-height: 1;
  color: var(--fg-soft);
  border-radius: 999px;
}
.chip button.link:hover { color: var(--err-fg); background: var(--err-bg); }
.chips + .addrow, .addrow { display: inline-flex; align-items: center; gap: 6px; }
.addrow select { max-width: 220px; }

/* The bulk bar, and the "what is missing" note on each row. */
.bulkbar p { margin: 8px 0; }
.bulkbar button { margin-right: 6px; }
.chip label, label.chip { cursor: pointer; padding-left: 9px; font-weight: 400; margin: 0; }
label.chip input { margin: 0 2px 0 0; width: auto; }
.warnish { color: var(--err-fg); font-size: 14px; }
td .rowpick, th #checkall { width: auto; margin: 0; }

/* A catalogue entry: one product, with its pack sizes as options. */
.card.product h2 { margin-top: 0; font-size: 18px; }
.card.product > .hint { margin-top: -4px; }
.buy { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
.buy label { margin: 0; }
.buy select { max-width: 100%; flex: 1 1 22rem; }
.buy input[type=number] { width: 7rem; max-width: 7rem; }
.buy p { flex-basis: 100%; margin: 4px 0 0; }
.buy button { margin: 0; }

@media (max-width: 880px) {
  .buy { gap: 6px; }
  .buy select, .buy button { flex-basis: 100%; }
  .buy button { width: 100%; }
}

/* The catalogue search row: the box grows, the rest keeps its size. */
/* A search bar is the first thing on a long list page, so it is sized to be
 * found and typed in, not squeezed between other controls. */
.searchbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 14px 0 20px;
  padding: 12px 14px;
  background: var(--bg-soft);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
}
.searchbar input[type=search] {
  flex: 1 1 22rem;
  max-width: none;
  margin: 0;
  padding: 11px 14px;
  font-size: 15.5px;
}
.searchbar select { flex: 0 1 15rem; max-width: 15rem; margin: 0; padding: 11px 12px; }
.searchbar button { margin: 0; padding: 11px 22px; }
@media (max-width: 640px) {
  .searchbar { padding: 10px; }
  .searchbar input[type=search], .searchbar select, .searchbar button { flex: 1 1 100%; max-width: none; }
}
.searchbar .clearlink { font-size: 14px; }

/* Present for a screen reader, absent from the page. Not display:none, which
 * removes it from the accessibility tree as well — the point is to keep the
 * label, not to delete it. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (max-width: 880px) {
  .searchbar input[type=search],
  .searchbar select,
  .searchbar button { flex-basis: 100%; max-width: none; width: 100%; }
}

/* A destructive button. Distinct enough to give pause, not so loud that the
 * eye stops seeing it. */
button.danger {
  background: var(--err-bg);
  color: var(--err-fg);
  border: 1px solid var(--err-fg);
}
button.danger:hover { background: var(--err-fg); color: var(--bg); }
.bulkbar hr { border: 0; border-top: 1px solid var(--line); margin: 14px 0 10px; }

/* The size list on a product page. A list, not a dropdown: an out-of-stock
 * size stays readable, and a <select> whose first option is disabled renders
 * completely blank — which made a product look broken rather than sold out. */
.sizes { display: block; }
label.size {
  display: grid;
  /* A column for the size's own picture, between the radio and the words. An
   * element with no grid-area of its own is placed by the browser wherever it
   * likes — which put the picture on a line of its own underneath. */
  grid-template-columns: auto auto 1fr auto;
  grid-template-areas: "radio pic pack price" "radio pic meta stock";
  gap: 2px 12px;
  align-items: center;
  padding: 12px 14px;
  margin: 0 0 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  font-weight: 400;
}
label.size:hover { border-color: var(--fg-soft); }
label.size:has(input:checked) { border-color: var(--accent); background: var(--bg-soft); }
label.size input { grid-area: radio; width: auto; margin: 0; }
.size-pack  { grid-area: pack;  font-weight: 600; }
.size-price { grid-area: price; font-weight: 600; text-align: right; }
.size-meta  { grid-area: meta;  font-size: 14px; color: var(--fg-soft); }
.size-stock { grid-area: stock; font-size: 14px; color: var(--fg-soft); text-align: right; }
label.size-out { opacity: .55; cursor: not-allowed; }

.prose p { margin: 0 0 10px; }
.card.product h2 a { text-decoration: none; }
.card.product h2 a:hover { text-decoration: underline; }

@media (max-width: 560px) {
  label.size {
    grid-template-columns: auto auto 1fr;
    grid-template-areas: "radio pic pack" "radio pic price"
                         "radio pic meta" "radio pic stock";
  }
  .size-price, .size-stock { text-align: left; }
}

/* ── Dashboard ───────────────────────────────────────────────────────────
 * One visual idea throughout: a LEVEL. This business sells fluid in drums
 * and takes money by transfer — a drum has a level, a balance has a level,
 * a day's orders have a height. Bars everywhere, no second chart type.
 *
 * Colour is semantic and rationed. Only two things are ever coloured: what
 * needs a person, and what has run out. Everything moving normally is the
 * one neutral accent. Colouring every bar makes nothing stand out. */

.work {
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 10px;
  padding: 16px 18px;
  margin: 0 0 24px;
}
.work h2 { margin: 0 0 12px; font-size: 17px; }
.worklist { display: flex; flex-wrap: wrap; gap: 12px 30px; }
.workitem { display: flex; align-items: baseline; gap: 10px; }
.workn {
  font-size: 30px; font-weight: 650; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.worklabel { color: var(--fg-soft); }
.workgo { white-space: nowrap; }

.block { margin: 0 0 26px; }
.block h2 { font-size: 16px; margin: 0 0 10px; }

/* The one big figure on the page. Everything else is small on purpose so
 * this reads first. */
.bigline { font-size: 26px; font-weight: 650; margin: 0 0 10px;
           font-variant-numeric: tabular-nums; }
.bigline .cur { font-size: 15px; font-weight: 500; color: var(--fg-soft); }
.bigline .up   { font-size: 15px; font-weight: 600; color: var(--ok-fg); }
.bigline .down { font-size: 15px; font-weight: 600; color: var(--err-fg); }
.bigline .hint { font-size: 14px; font-weight: 400; }

/* Charts are SVG drawn on the server: the CSP blocks a charting library, and
 * a chart that fails to load looks exactly like a chart with no data. */
.chart { overflow-x: auto; }
.chart svg { display: block; min-width: 420px; }
.bar { fill: var(--accent); }
.bar-none { fill: var(--line); }
.axis { fill: var(--fg-soft); font-size: 11px; }

.levels { display: grid; gap: 8px; }
.level {
  display: grid;
  grid-template-columns: minmax(8rem, 14rem) 1fr 2.5rem auto;
  align-items: center;
  gap: 12px;
}
.levellabel { color: var(--fg-soft); }
.levelbar {
  display: block; height: 12px; border-radius: 6px;
  background: var(--bg-soft); overflow: hidden;
}
.levelbar > span { display: block; height: 100%; }
.leveln { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.levelv { text-align: right; font-variant-numeric: tabular-nums; min-width: 5rem; }
.lvl-ok   { background: var(--accent); }
.lvl-att  { background: var(--warn-fg); }
.lvl-bad  { background: var(--err-fg); }
.lvl-none { background: transparent; }

.panes { display: grid; grid-template-columns: 1fr 1fr; gap: 0 32px; }
.panes > section { min-width: 0; }

.signed { margin-top: 26px; padding-top: 12px; border-top: 1px solid var(--line-soft); }

@media (max-width: 760px) {
  .panes { grid-template-columns: 1fr; }
  .workitem { flex-basis: 100%; }
  .level { grid-template-columns: 1fr 2.5rem; gap: 4px 8px; }
  .levellabel { grid-column: 1 / -1; }
  .levelbar { grid-column: 1; }
  .levelv { grid-column: 1 / -1; text-align: left; }
}

/* The brand. ONE mark in the bar, the parent company in the footer, and
 * both stacked on the pages that have no bar at all. A mark may ship a
 * "-dark" file; both are emitted and one is hidden here, rather than
 * recolouring with a filter — invert() on a black-and-blue mark turns the
 * blue orange, and a brand's colour is not ours to change. */
.brand {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  /* The mark needs more room beside it than two links need beside each
   * other. The bar's own 10px gap is right for links and far too tight
   * against a logo, which has no side bearing of its own — it ran straight
   * into the first nav item. A hairline makes the two into two things
   * rather than one crowded row. */
  margin-right: 22px;
  padding-right: 22px;
  border-right: 1px solid var(--line);
  flex: 0 0 auto;
}
.brand:hover { text-decoration: none; }
.mark { display: inline-flex; align-items: center; }
.logo {
  /* A BOX, NOT A HEIGHT. The two marks are very different shapes — one is
   * stacked with a tagline, the other a long wordmark — and forcing both to
   * one height makes the wide one enormous and the tall one's tagline
   * unreadable. Each fits inside the same box instead. */
  max-height: 34px;
  max-width: 160px;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}
.brandname { font-weight: 650; color: var(--fg); }

/* Which variant shows. The default — no preference, no attribute — is light. */
.logo-d { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .logo-l { display: none; }
  :root:not([data-theme="light"]) .logo-d { display: block; }
}
:root[data-theme="dark"]  .logo-l { display: none; }
:root[data-theme="dark"]  .logo-d { display: block; }
:root[data-theme="light"] .logo-l { display: block; }
:root[data-theme="light"] .logo-d { display: none; }

/* The brand on sign-in and code pages. Centred and larger than in the bar:
 * on a page with nothing else on it, it is the only thing saying where you
 * are. The parent company is in the footer, here as everywhere else. */
.lockup {
  display: flex;
  justify-content: center;
  margin: 8px 0 28px;
}
.lockup .logo { max-height: 46px; max-width: 240px; }
.lockup-name { font-weight: 650; text-align: center; margin: 8px 0 28px; }

/* The footer. Quiet, below everything, with the parent company's mark. */
.sitefoot {
  margin-top: 48px;
  padding: 20px 0 28px;
  border-top: 1px solid var(--line);
}
.footrow {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.footmark .logo { max-height: 30px; max-width: 140px; }
.footnote { margin: 0; color: var(--fg-soft); font-size: 14px; }

@media (max-width: 640px) {
  .logo { max-height: 26px; max-width: 120px; }
  .lockup .logo { max-height: 38px; max-width: 190px; }
  /* The bar wraps here, and a divider at the end of a wrapped row is a line
   * hanging in space. */
  .brand { border-right: 0; padding-right: 0; margin-right: 12px; }
  .footrow { gap: 10px; }
}

/* Bar widths as CLASSES, one per percent.
 *
 * The CSP's style-src is 'self' with no 'unsafe-inline', so a browser drops
 * an inline style="width:40%" attribute. The bar then fell back to its
 * natural width — the full track — and every non-zero bar on both
 * dashboards read as 100% whatever its value. It looked right only while
 * there was a single order, because one of one IS 100%.
 *
 * A hundred and one tiny rules is the price of keeping 'unsafe-inline' off,
 * which is the directive that catches an escaping mistake. */
.pw0 { width: 0%; }
.pw1 { width: 1%; }
.pw2 { width: 2%; }
.pw3 { width: 3%; }
.pw4 { width: 4%; }
.pw5 { width: 5%; }
.pw6 { width: 6%; }
.pw7 { width: 7%; }
.pw8 { width: 8%; }
.pw9 { width: 9%; }
.pw10 { width: 10%; }
.pw11 { width: 11%; }
.pw12 { width: 12%; }
.pw13 { width: 13%; }
.pw14 { width: 14%; }
.pw15 { width: 15%; }
.pw16 { width: 16%; }
.pw17 { width: 17%; }
.pw18 { width: 18%; }
.pw19 { width: 19%; }
.pw20 { width: 20%; }
.pw21 { width: 21%; }
.pw22 { width: 22%; }
.pw23 { width: 23%; }
.pw24 { width: 24%; }
.pw25 { width: 25%; }
.pw26 { width: 26%; }
.pw27 { width: 27%; }
.pw28 { width: 28%; }
.pw29 { width: 29%; }
.pw30 { width: 30%; }
.pw31 { width: 31%; }
.pw32 { width: 32%; }
.pw33 { width: 33%; }
.pw34 { width: 34%; }
.pw35 { width: 35%; }
.pw36 { width: 36%; }
.pw37 { width: 37%; }
.pw38 { width: 38%; }
.pw39 { width: 39%; }
.pw40 { width: 40%; }
.pw41 { width: 41%; }
.pw42 { width: 42%; }
.pw43 { width: 43%; }
.pw44 { width: 44%; }
.pw45 { width: 45%; }
.pw46 { width: 46%; }
.pw47 { width: 47%; }
.pw48 { width: 48%; }
.pw49 { width: 49%; }
.pw50 { width: 50%; }
.pw51 { width: 51%; }
.pw52 { width: 52%; }
.pw53 { width: 53%; }
.pw54 { width: 54%; }
.pw55 { width: 55%; }
.pw56 { width: 56%; }
.pw57 { width: 57%; }
.pw58 { width: 58%; }
.pw59 { width: 59%; }
.pw60 { width: 60%; }
.pw61 { width: 61%; }
.pw62 { width: 62%; }
.pw63 { width: 63%; }
.pw64 { width: 64%; }
.pw65 { width: 65%; }
.pw66 { width: 66%; }
.pw67 { width: 67%; }
.pw68 { width: 68%; }
.pw69 { width: 69%; }
.pw70 { width: 70%; }
.pw71 { width: 71%; }
.pw72 { width: 72%; }
.pw73 { width: 73%; }
.pw74 { width: 74%; }
.pw75 { width: 75%; }
.pw76 { width: 76%; }
.pw77 { width: 77%; }
.pw78 { width: 78%; }
.pw79 { width: 79%; }
.pw80 { width: 80%; }
.pw81 { width: 81%; }
.pw82 { width: 82%; }
.pw83 { width: 83%; }
.pw84 { width: 84%; }
.pw85 { width: 85%; }
.pw86 { width: 86%; }
.pw87 { width: 87%; }
.pw88 { width: 88%; }
.pw89 { width: 89%; }
.pw90 { width: 90%; }
.pw91 { width: 91%; }
.pw92 { width: 92%; }
.pw93 { width: 93%; }
.pw94 { width: 94%; }
.pw95 { width: 95%; }
.pw96 { width: 96%; }
.pw97 { width: 97%; }
.pw98 { width: 98%; }
.pw99 { width: 99%; }
.pw100 { width: 100%; }

/* A narrow number box (the per-agent limit), instead of an inline width. */
input.num-sm { width: 5em; }

/* A label-and-value table: every row's label is a <th>, so none of them is a
 * column heading. */
table.kv th { font-weight: 600; color: var(--fg); text-transform: none;
              letter-spacing: 0; font-size: inherit; }

/* A page heading with its main action beside it. */
/* The same space above and below as a plain h1 (28px / 14px). The first
 * version zeroed the h1's margin and gave the row none of its own, so the
 * heading and its button sat right against the nav bar. */
.pagehead { display: flex; align-items: center; justify-content: space-between;
            gap: 12px; flex-wrap: wrap; margin: 28px 0 14px; }
.pagehead h1 { margin: 0; }
a.btn { display: inline-block; text-decoration: none; }
a.btn:hover { text-decoration: none; }
.btn-add { font-weight: 650; padding: 10px 18px; box-shadow: 0 1px 0 rgba(0,0,0,.08); }

/* Jumping to a section on the same page: glide there, stop short of the top
 * edge, and briefly mark the form so the reader sees where they landed. No
 * JavaScript — the page's CSP allows none that is not ours, and none is
 * needed. Someone who has asked for less motion gets the jump without it. */
html { scroll-behavior: smooth; }
:target { scroll-margin-top: 84px; }   /* clear of the sticky nav bar */
#add-product:target + .card, #add-form:target + .card { animation: landed 1.6s ease-out; }
@keyframes landed {
  0%, 30% { box-shadow: 0 0 0 3px var(--accent); }
  100%    { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  #add-product:target + .card, #add-form:target + .card { animation: none; outline: 2px solid var(--accent); }
}

/* A small button inside a table row. */
a.btn-sm { padding: 5px 11px; font-size: 13.5px; white-space: nowrap; }

/* Cancelling a paid order: two choices, each a set of radio buttons. */
.cancelpaid fieldset { border: 1px solid var(--line); border-radius: 8px; padding: 10px 14px 12px;
                       margin: 0 0 14px; }
.cancelpaid legend { font-weight: 650; padding: 0 6px; }
.cancelpaid label.opt { display: block; font-weight: 400; margin: 8px 0; cursor: pointer; }
.cancelpaid label.opt input { margin-right: 6px; }
.cancelpaid input[disabled] + * , .cancelpaid label.opt:has(input[disabled]) { opacity: .55; cursor: default; }
/* The reason box opens only when "Other" is chosen. Wrapped in @supports so
 * a browser without :has() simply shows the box all the time — it must never
 * end up hidden with no way to fill it in. */
@supports selector(:has(*)) {
  .cancelpaid .other-reason { display: none; }
  .cancelpaid:has(#why-other:checked) .other-reason { display: block; }
}

/* A dialog over the page, opened by a link (?edit=…) and closed by one.
 * No script: the CSP allows no inline script, and a dialog that needs
 * script is one that does not open when the script fails to load. */
.modal { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center;
         padding: 16px; }
.modal-back { position: absolute; inset: 0; background: rgba(12, 18, 26, .5); }
.modal-card {
  position: relative;
  width: min(620px, 100%);
  max-height: 88vh;
  overflow: auto;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .35);
  padding: 4px 20px 18px;
}
.modal-card > h2 { margin-top: 14px; }
.modal-card .card { border: 0; padding: 0; background: none; box-shadow: none; }
@media (max-width: 640px) { .modal-card { padding: 4px 14px 14px; } }

/* A value that is missing and matters — said in the table, not left blank. */
.warn-text { color: var(--warn-fg, #a15c00); font-style: italic; }

/* The password box on an agent row: shut until it is needed, so a page of
 * agents is not a page of password fields. */
.pwset { margin-top: 6px; }
.pwset > summary { cursor: pointer; color: var(--link); font-size: 14px; }
.pwset form { margin-top: 8px; max-width: 320px; }
.pwset input { max-width: 300px; }

/* "How should this account start?" — the password boxes appear only when the
 * second choice is picked. CSS only, and wrapped in @supports so a browser
 * without :has() simply shows them, which is harmless: they are ignored
 * unless that radio is chosen. */
.howmade { border: 1px solid var(--line); border-radius: 8px; padding: 10px 14px 12px;
           margin: 0 0 14px; }
.howmade legend { font-weight: 650; padding: 0 6px; }
.howmade label.opt { display: block; font-weight: 400; margin: 8px 0; cursor: pointer; }
.howmade label.opt input { margin-right: 6px; }
.howmade .pwfields { margin-top: 6px; }
@supports selector(:has(*)) {
  .howmade .pwfields { display: none; }
  .howmade:has(#mode-pw:checked) .pwfields { display: block; }
}

/* The contract on an agent row — shut until wanted, like the password box. */
.contract { margin-top: 6px; }
.contract > summary { cursor: pointer; color: var(--link); font-size: 14px; }
.contract form { margin-top: 8px; }
.contract input[type=file] { max-width: 320px; }

/* The variation you are looking at, inside the list of its siblings. */
tr.thisrow > td { background: var(--bg-soft); font-weight: 600; }
.addvar { margin-top: 10px; }
.addvar > summary { cursor: pointer; color: var(--link); font-weight: 650; }
.addvar form { margin-top: 10px; }

/* A destructive link inside a table row: readable, but not the first thing
 * the eye lands on. */
button.link.danger-link { color: var(--danger, #b3261e); }
button.link.danger-link:hover { text-decoration: underline; }

/* Product pictures. Capped so a large upload cannot push the page around,
 * and square-ish without cropping: a drum and a bottle are different shapes
 * and neither should be cut to fit. */
/* A catalogue row: words on the left, the picture on the right. The picture
 * is given real room — a 200 L drum and a 300 mL bottle have to be tellable
 * apart from the list, which is the whole point of having one. */
.card.product.haspic { display: flex; gap: 28px; align-items: flex-start; }
.card.product .pcopy { flex: 1 1 auto; min-width: 0; }
.card.product .pcopy > h2 { margin-top: 0; }
.card.product .ppic  { flex: 0 0 260px; display: block; }
.catimg {
  display: block;
  width: 100%;
  max-height: 260px;
  object-fit: contain;
  border-radius: 10px;
  background: var(--bg-soft);
}
@media (max-width: 760px) {
  /* Narrow: the picture goes back on top, where a phone expects it. */
  .card.product.haspic { display: block; }
  .card.product .ppic { margin-top: 14px; }
  .catimg { max-height: 220px; }
}
.prodimg {
  max-width: 100%;
  max-height: 320px;
  border-radius: 10px;
  background: var(--bg-soft);
}

/* The Details card: fields on the left, the picture in the space beside them.
 * They are two columns and not one form, because an upload needs its own
 * <form> and HTML has no nested ones. On a narrow screen they stack, picture
 * last — the fields are what the page is for. */
.detailswrap { display: flex; gap: 24px; align-items: flex-start; }
.detailsmain { flex: 1 1 60%; min-width: 0; }
.detailspic  { flex: 0 1 320px; min-width: 0; }
.detailspic .prodimg { max-height: 240px; }
.detailspic input[type=file] { max-width: 100%; }
@media (max-width: 860px) {
  .detailswrap { display: block; }
  .detailspic  { margin-top: 18px; }
}

/* The small picture beside each size in the chooser. */
.size-pic {
  grid-area: pic;
  width: 56px; height: 56px; object-fit: contain;
  border-radius: 6px; background: var(--bg-soft);
}

/* The big picture follows the chosen size. Every size's picture is in the
 * page; this shows the one whose radio is checked. Written out for the first
 * twelve sizes rather than generated per product, because the CSP allows no
 * inline <style> and a set with thirteen pack sizes does not exist.
 *
 * Where :has() is missing the pictures are left alone and only the one the
 * server chose is shown — correct, just not live. */
.pvpics { margin: 0 0 14px; }
.pvpic:not(.pv-chosen) { display: none; }
@supports selector(:has(*)) {
  .pvpic { display: none; }
  body:has(.szi1:checked) .pv1 { display: block; }
  body:has(.szi2:checked) .pv2 { display: block; }
  body:has(.szi3:checked) .pv3 { display: block; }
  body:has(.szi4:checked) .pv4 { display: block; }
  body:has(.szi5:checked) .pv5 { display: block; }
  body:has(.szi6:checked) .pv6 { display: block; }
  body:has(.szi7:checked) .pv7 { display: block; }
  body:has(.szi8:checked) .pv8 { display: block; }
  body:has(.szi9:checked) .pv9 { display: block; }
  body:has(.szi10:checked) .pv10 { display: block; }
  body:has(.szi11:checked) .pv11 { display: block; }
  body:has(.szi12:checked) .pv12 { display: block; }
}
