/* Operator interface.
   =====================================================================
   Dependency-free on purpose. These screens are open all day on modest
   hardware, often on a connection that is somebody else's problem, so
   they carry no web font, no framework and no external request: one
   stylesheet, one script, one icon sprite, all served by the same binary
   as the data.

   Two themes, one set of tokens. The theme is decided by the SERVER from
   a cookie and stamped on <html> before anything paints, so switching it
   never flashes the other theme first — the content policy forbids the
   inline script that trick usually needs.

   Shape rule, applied everywhere: panels 14px · controls 9px · pills and
   avatars fully round. Numbers are always mono with tabular figures, so a
   column of money lines up on the decimal and a changing figure does not
   shift the ones beside it.
   ===================================================================== */

/* Colour is borrowed from DN Panel, the operator's other console, so the two
   feel like one product: a deep navy-ink ground, panels that lift OFF it with a
   real gap and a hairline of light along their top edge, and one vivid violet
   signal. The mistake the first pass made was a dark ground that was too light
   and too grey — nothing could rise above it, so every panel read flat. The fix
   is contrast, not decoration: the ground drops, the panels stay, and the gap
   between them does the work. */

:root {
  color-scheme: light;

  /* Paper and ink. Tinted, never pure: a flat #fff sheet under a table of
     figures glares, and #000 kills the depth the panels rely on. */
  --bg: oklch(96.5% 0.006 275);
  --panel: oklch(99.6% 0.002 275);
  --panel-2: oklch(97.6% 0.005 275);
  --panel-top: oklch(100% 0 0 / 0.7); /* the light edge along a panel's top */
  --ink: oklch(24% 0.03 278);
  --ink-2: oklch(44% 0.02 278);
  --muted: oklch(56% 0.02 278);
  --line: oklch(91% 0.008 278);
  --line-2: oklch(86% 0.01 278);

  /* One signal, the DN Panel violet. It carries the brand; everything else
     that carries colour carries it because the DATA has a state. */
  --accent: oklch(56% 0.21 285);
  --accent-2: oklch(62% 0.20 288); /* a lighter grade, for gradient fills */
  --accent-ink: oklch(99% 0 0);
  --accent-dim: oklch(56% 0.21 285 / 0.10);
  --accent-line: oklch(56% 0.21 285 / 0.34);

  --ok: oklch(58% 0.15 162);
  --ok-dim: oklch(58% 0.15 162 / 0.13);
  --warn: oklch(64% 0.16 72);
  --warn-dim: oklch(64% 0.16 72 / 0.15);
  --bad: oklch(58% 0.21 22);
  --bad-dim: oklch(58% 0.21 22 / 0.13);
  --info: oklch(56% 0.19 250);
  --info-dim: oklch(56% 0.19 250 / 0.13);
  --violet: oklch(56% 0.21 300);
  --violet-dim: oklch(56% 0.21 300 / 0.13);
  --teal: oklch(60% 0.13 190);
  --teal-dim: oklch(60% 0.13 190 / 0.13);

  --shadow: 0 1px 2px oklch(30% 0.03 278 / 0.06), 0 10px 28px oklch(30% 0.03 278 / 0.08);
  --shadow-sm: 0 1px 2px oklch(30% 0.03 278 / 0.07);
  --glow: transparent;

  --r-panel: 14px;
  --r-control: 9px;
  --nav-w: 258px;
  --nav-w-slim: 68px;
  --bar-h: 60px;

  /* The one gap between a control strip — the status tabs, a filter bar, an
     action row, the range toolbar, the metrics strip, a row of stat tiles — and
     the content it governs. One even step above the 16px section gap, so a bar
     reads as controlling what follows rather than sitting level with it. Change
     it here and every strip in the app moves together. */
  --strip-gap: 18px;
  /* The rest of the vertical rhythm, tightest to loosest, one role each — so a
     gap in the sheet says what KIND of gap it is: prose after a paragraph, a
     heading to the content it introduces, one section to the next. Section and
     heading share a value today but are named apart so either can move alone. */
  --gap-text: 12px;
  --gap-heading: 16px;
  --gap-section: 16px;

  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "JetBrains Mono", Consolas, monospace;

  --ease: cubic-bezier(0.2, 0, 0, 1);
}

[data-theme="dark"] {
  color-scheme: dark;
  /* Deep navy-ink, the DN Panel ground. Far darker than the first pass, so the
     panels above it have somewhere to rise from. */
  --bg: oklch(15% 0.022 278);
  --panel: oklch(22.5% 0.022 278);
  --panel-2: oklch(26.5% 0.023 278);
  --panel-top: oklch(100% 0 0 / 0.06); /* the glass highlight along a panel's top */
  --ink: oklch(96% 0.008 278);
  --ink-2: oklch(80% 0.015 278);
  --muted: oklch(64% 0.02 278);
  --line: oklch(31% 0.02 278);
  --line-2: oklch(38% 0.022 278);

  --accent: oklch(68% 0.19 288);
  --accent-2: oklch(74% 0.17 290);
  --accent-ink: oklch(16% 0.03 288);
  --accent-dim: oklch(68% 0.19 288 / 0.20);
  --accent-line: oklch(68% 0.19 288 / 0.45);

  --ok: oklch(76% 0.16 165);
  --ok-dim: oklch(76% 0.16 165 / 0.18);
  --warn: oklch(82% 0.15 80);
  --warn-dim: oklch(82% 0.15 80 / 0.18);
  --bad: oklch(70% 0.18 20);
  --bad-dim: oklch(70% 0.18 20 / 0.18);
  --info: oklch(70% 0.16 250);
  --info-dim: oklch(70% 0.16 250 / 0.18);
  --violet: oklch(72% 0.17 300);
  --violet-dim: oklch(72% 0.17 300 / 0.18);
  --teal: oklch(74% 0.13 190);
  --teal-dim: oklch(74% 0.13 190 / 0.18);

  --shadow: 0 1px 2px oklch(0% 0 0 / 0.45), 0 12px 34px oklch(0% 0 0 / 0.40);
  --shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.40);
  /* A single wash of the accent behind the top-left, the DN Panel galaxy in one
     line: enough for depth, not so much it competes with a table of numbers. */
  --glow: radial-gradient(1100px 620px at 12% -8%, oklch(60% 0.20 288 / 0.14), transparent 62%);
}

* { box-sizing: border-box; }

/* A control hidden by the script stays hidden. Setting display on every button
   would otherwise beat the browser's own rule for the hidden attribute, and the
   fallback submit button next to a self-submitting select would sit there for
   ever looking like something to press. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  font: 14.5px/1.5 var(--sans);
  color: var(--ink);
  /* The accent wash sits UNDER the ground colour and does not scroll: it is
     depth, not content, so it stays behind the work while the work moves. */
  background: var(--glow), var(--bg);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
}

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

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

/* Every figure on every screen. Tabular so a column of money lines up and a
   count that ticks up does not nudge what sits beside it. */
.num, .val, .kpi-val, td.num, th.num, code, .mono {
  font-variant-numeric: tabular-nums;
}

/* Thin, themed, and in the accent — the scrollbar belongs to this interface,
   not to the operating system. */
* { scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 99px; }
*::-webkit-scrollbar-thumb:hover { background: var(--accent-line); }

.ico { width: 18px; height: 18px; flex: none; display: block; }
.ico-sm { width: 15px; height: 15px; }

/* --- shell -------------------------------------------------------------
   A fixed rail on the left, a fixed bar on top, and the work in between.
   Both are fixed because an operator scrolls a list of two hundred orders
   and must never lose the stage counts or the action bar to do it. */

.app { min-height: 100%; }

.rail {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--nav-w);
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border-right: 1px solid var(--line);
  z-index: 30;
  transition: width 160ms var(--ease);
}
.rail-head {
  display: flex;
  align-items: center;
  gap: 10px;
  height: var(--bar-h);
  padding: 0 14px;
  border-bottom: 1px solid var(--line);
}
.mark {
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 700; font-size: 12.5px; letter-spacing: .02em;
}
.wordmark { display: flex; flex-direction: column; min-width: 0; }
.wordmark b { font-size: 14px; font-weight: 650; letter-spacing: -.01em; color: var(--ink); }
.wordmark span {
  font-family: var(--mono);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
  white-space: nowrap;
}

/* The rail scrolls, but without a visible bar: it took a column of width that
   the icons then had to dodge, and the rail is short enough that the scroll is
   rarely needed. Still scrollable by wheel and touch. */
.rail-nav { flex: 1; overflow-y: auto; padding: 10px 10px 16px; scrollbar-width: none; }
.rail-nav::-webkit-scrollbar { display: none; }
.nav-group {
  font-family: var(--mono);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--muted);
  padding: 16px 10px 6px;
}
.nav-group:first-child { padding-top: 4px; }

.rail-nav a {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 10px;
  border-radius: var(--r-control);
  color: var(--ink-2);
  font-size: 14px;
  text-decoration: none;
  white-space: nowrap;
  transition: background 120ms var(--ease), color 120ms var(--ease);
}
.rail-nav a:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; }
.rail-nav a.on { background: var(--accent-dim); color: var(--accent); font-weight: 600; }
.rail-nav a.on .ico { color: var(--accent); }
.rail-nav a .ico { color: var(--muted); }
.rail-nav a:hover .ico { color: var(--ink-2); }
/* A count riding the nav entry: what is waiting behind that link. */
.nav-count {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 11px;
  padding: 1px 6px;
  border-radius: 99px;
  background: var(--warn-dim);
  color: var(--warn);
}

.rail-foot {
  padding: 10px;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
}
.rail-foot .lang { flex: 1; min-width: 0; }
.rail-foot .lang select { width: 100%; height: 34px; font-size: 13px; }

/* Collapsed: icons only. The rail keeps its position so muscle memory holds;
   only the labels go. */
.app.slim .rail { width: var(--nav-w-slim); }
.app.slim .rail-nav a { justify-content: center; padding: 9px 0; }
.app.slim .wordmark,
.app.slim .rail-nav a span,
.app.slim .nav-count,
.app.slim .nav-group,
.app.slim .rail-foot .lang { display: none; }
.app.slim .rail-head { justify-content: center; padding: 0; }
.app.slim .rail-foot { justify-content: center; }

.topbar {
  position: fixed;
  top: 0; right: 0;
  left: var(--nav-w);
  height: var(--bar-h);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 20px;
  background: color-mix(in oklab, var(--bg) 82%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  z-index: 20;
  transition: left 160ms var(--ease);
}
.app.slim .topbar { left: var(--nav-w-slim); }

.topbar h1 { font-size: 16px; font-weight: 650; margin: 0; letter-spacing: -.01em; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.icon-btn {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-control);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink-2);
  cursor: pointer;
  padding: 0;
  transition: background 120ms var(--ease), color 120ms var(--ease), transform 90ms var(--ease);
}
.icon-btn:hover { background: var(--panel-2); color: var(--ink); filter: none; }
.icon-btn:active { transform: translateY(1px); }
/* The compact icon button used inside a table row: small enough to sit inside a
   line without towering over it. */
.icon-btn.small { width: 30px; height: 30px; }
.icon-btn.small .ico { width: 16px; height: 16px; }

/* The floating name shown when the collapsed rail is hovered. */
.rail-tip {
  position: fixed;
  transform: translateY(-50%);
  z-index: 60;
  padding: 5px 10px;
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  box-shadow: var(--shadow);
  color: var(--ink);
  font-size: 12.5px;
  white-space: nowrap;
  pointer-events: none;
}
.rail-tip[hidden] { display: none; }
/* Only one of the two sits in the DOM at a time, decided by the theme. */
[data-theme="dark"] .icon-btn .when-light { display: none; }
:root:not([data-theme="dark"]) .icon-btn .when-dark { display: none; }

/* The account button and the menu behind it. One line on the face — avatar,
   name, a chevron — at the same height as the other controls in the bar, so it
   reads as a button rather than a tall pill. */
.usermenu { position: relative; }
.usermenu > summary { list-style: none; cursor: pointer; }
.usermenu > summary::-webkit-details-marker { display: none; }
.whoami {
  display: flex; align-items: center; gap: 8px;
  height: 34px;
  padding: 0 10px 0 5px;
  border-radius: var(--r-control);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  text-decoration: none;
}
.whoami:hover { background: var(--panel-2); text-decoration: none; }
.usermenu[open] > .whoami { background: var(--panel-2); border-color: var(--accent-line); }
.whoami .chev { color: var(--muted); transition: transform 140ms var(--ease); }
.usermenu[open] > .whoami .chev { transform: rotate(180deg); }
.avatar {
  width: 26px; height: 26px; flex: none;
  display: grid; place-items: center;
  border-radius: 99px;
  background: var(--accent-dim);
  color: var(--accent);
  font-size: 11px; font-weight: 700;
}
.whoami .who-name { font-size: 13px; line-height: 1; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who-role { font-size: 11px; color: var(--muted); }

.usermenu-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 220px;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow), inset 0 1px 0 var(--panel-top);
  z-index: 40;
}
.usermenu-head {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 8px 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
.usermenu-head > span:last-child { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.usermenu-item {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 8px 8px;
  border: none; background: none;
  border-radius: var(--r-control);
  color: var(--ink-2);
  font: inherit; font-size: 13.5px; text-align: left;
  cursor: pointer; text-decoration: none;
  height: auto;
}
.usermenu-item .ico { color: var(--muted); }
.usermenu-item:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; filter: none; }
.usermenu-item.danger { color: var(--bad); }
.usermenu-item.danger .ico { color: var(--bad); }
.usermenu-item.danger:hover { background: var(--bad-dim); color: var(--bad); }
.usermenu form { margin: 0; }

/* The offsets belong to the SHELL, not to the page. The packing sheet is opened
   by somebody with no account and therefore no rail, and an indent the width of
   a rail that is not there would push the sheet off the paper. */
/* The page fills the width the rail leaves, at every size. It used to stop at a
   fixed max-width and sit against the left, leaving a dead band on a wide screen
   or when the browser is zoomed out; now the grids inside stretch to whatever is
   there. A dashboard would rather use the room than leave it blank. */
body.app main.page {
  margin-left: var(--nav-w);
  padding: calc(var(--bar-h) + 22px) 22px 40px;
  transition: margin-left 160ms var(--ease);
}
body.app.slim main.page { margin-left: var(--nav-w-slim); }
body.plain main.page { max-width: 1100px; margin: 24px auto; padding: 0 20px; }
.card-page { max-width: 400px; margin: 9vh auto; padding: 0 20px; }

.foot { display: none; }
body.plain .foot { display: block; text-align: center; color: var(--muted); font-size: 12.5px; padding: 20px 0; }

/* --- type --------------------------------------------------------------- */

h1 { font-size: 19px; font-weight: 650; margin: 0 0 var(--gap-heading); letter-spacing: -.01em; }
h2 {
  font-size: 12px;
  font-weight: 600;
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--muted);
  margin: 26px 0 var(--gap-heading);
}
h3 { font-size: 14px; font-weight: 600; margin: 18px 0 var(--gap-text); }
/* A paragraph carries a deterministic gap below it, not the browser's own, so a
   caption under a heading and the content under the caption keep one rhythm on
   every page. A message banner (.notice/.problem) keeps its own, heavier gap. */
p { margin: 0 0 var(--gap-text); }
.muted { color: var(--muted); font-size: 12.5px; }
/* A reference is read as one thing or not at all: an order code broken across
   two lines cannot be compared against a parcel at a glance. */
code { font-family: var(--mono); font-size: 12.5px; white-space: nowrap; }
code.secret, pre code, td.detail code { white-space: normal; }
code.dim { color: var(--muted); }

/* --- panels ------------------------------------------------------------- */

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  padding: 22px;
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--panel-top);
}
body.plain .card { box-shadow: var(--shadow); }

/* --- messages ----------------------------------------------------------- */

.notice, .problem {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  border-radius: var(--r-control);
  padding: 10px 14px;
  margin: 0 0 var(--gap-section);
  font-size: 13.5px;
  border: 1px solid;
}
.notice { background: var(--ok-dim); border-color: color-mix(in oklab, var(--ok) 35%, transparent); color: var(--ok); }
.problem { background: var(--bad-dim); border-color: color-mix(in oklab, var(--bad) 35%, transparent); color: var(--bad); }

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

label { display: block; margin-bottom: 12px; font-size: 12.5px; color: var(--muted); }
input, select, textarea {
  display: block;
  width: 100%;
  height: 36px;
  margin-top: 5px;
  padding: 0 10px;
  font: inherit;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--r-control);
}
input::placeholder { color: var(--muted); }
input:hover, select:hover { border-color: var(--accent-line); }
input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }

/* Never the browser's own select: its arrow, its metrics and its popup are a
   different design system arriving uninvited. */
select {
  appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6l6 -6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 15px;
}

button, .btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 36px;
  padding: 0 14px;
  font: inherit;
  font-weight: 600;
  font-size: 13.5px;
  color: var(--accent-ink);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: var(--r-control);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: filter 120ms var(--ease), transform 90ms var(--ease), background 120ms var(--ease);
}
button:hover, .btn:hover { filter: brightness(1.07); text-decoration: none; }
/* A control that sinks when pressed. The screen is used a few hundred times a
   day; the press is the acknowledgement. */
button:active, .btn:active { transform: translateY(1px); }
/* A small control is QUIET by default. A row of orders carries four or five of
   them per line, and a filled accent button on each turns a working list into
   a wall of buttons where nothing is more important than anything else. The one
   action that IS the next step marks itself primary. */
button.small, .btn.small {
  height: 30px;
  padding: 0 10px;
  font-size: 12.5px;
  font-weight: 500;
  background: var(--panel);
  color: var(--ink-2);
  border-color: var(--line-2);
}
button.small:hover, .btn.small:hover { background: var(--panel-2); color: var(--ink); filter: none; }
button.small.primary, .btn.small.primary {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: transparent;
  font-weight: 600;
}
button.small.primary:hover { filter: brightness(1.07); }
button.link {
  background: none; border: none; color: var(--muted);
  padding: 0; height: auto; font-weight: 400; font-size: 13px;
}
button.link:hover { color: var(--ink); filter: none; }
.btn.ghost, button.ghost {
  background: var(--panel);
  color: var(--ink-2);
  border-color: var(--line-2);
}
.btn.ghost:hover, button.ghost:hover { background: var(--panel-2); color: var(--ink); filter: none; }
button.primary { background: var(--accent); color: var(--accent-ink); }
button[disabled], .btn[disabled] { opacity: .5; cursor: not-allowed; }

form.inline { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
form.inline input, form.inline select, form.inline textarea { width: auto; margin: 0; }
form.stack { max-width: 400px; }
form.stack input, form.stack select { margin-bottom: 2px; }
.lang-foot { margin-top: 18px; }
/* A plain table-cell, not a flex box. A flex <td> shrinks to its buttons and
   settles at the top of a taller row, so a lone view button rode a few pixels
   above its line; as a cell it takes the row's full height and vertical-align
   places its contents — centred in the overview, top-aligned in the orders
   list where the rows are tall and the buttons belong beside the first line. */
.row-actions { white-space: nowrap; }
/* Scoped under .grid so it outweighs `.grid td { text-align: left }`, which is
   just as specific but comes later in the sheet. */
.grid .row-actions { text-align: right; }
.row-actions > * { margin-left: 6px; }
.row-actions > *:first-child { margin-left: 0; }

/* --- figures ------------------------------------------------------------ */

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; margin-bottom: var(--strip-gap); }
.tile {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--panel-top);
}
.tile .k {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .12em;
}
.tile .val { font-size: 17px; font-weight: 600; }

/* The figures that answer "what does today look like?". One tile per number,
   the number large and mono, the icon carrying the tile's own state colour. */
/* Six figures, never five and a stray. An automatic fit leaves an orphan tile
   on its own row at most desktop widths, and an orphan reads as a mistake.
   The row spans the whole width — including over the side rail — because six
   figures squeezed into two thirds of the screen stop being readable at a
   glance, which is the only thing they are for. */
.kpis { display: grid; grid-template-columns: 1fr; gap: 12px; margin-bottom: var(--strip-gap); }
@media (min-width: 620px) { .kpis { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .kpis { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1500px) { .kpis { grid-template-columns: repeat(6, 1fr); } }
/* The money figures are the long ones; they give way before the layout does. */
.kpi-val { font-size: clamp(19px, 1.45vw, 26px); }
.kpi {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  padding: 15px 16px 14px;
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--panel-top);
  color: var(--ink);
  display: block;
  overflow: hidden;
}
/* A thread of the card's own colour down the left edge, so the six figures read
   as six different things at a glance rather than one grey row. */
.kpi::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--kpi-c, var(--accent));
  opacity: .8;
}
a.kpi:hover { border-color: var(--accent-line); text-decoration: none; transform: translateY(-1px); }
.kpi { transition: transform 120ms var(--ease), border-color 120ms var(--ease); }
.kpi-head { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
/* The icon is the card's colour, solid — not a faint tint. This is the single
   biggest reason the first pass looked washed out next to DN Panel. */
.kpi-badge {
  width: 32px; height: 32px; flex: none;
  display: grid; place-items: center;
  border-radius: 9px;
  background: color-mix(in oklab, var(--kpi-c, var(--accent)) 20%, transparent);
  color: var(--kpi-c, var(--accent));
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--kpi-c, var(--accent)) 30%, transparent);
}
.kpi-k {
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--muted);
}
.kpi-val { font-size: 26px; font-weight: 650; letter-spacing: -.02em; line-height: 1.1; white-space: nowrap; }
.kpi-sub { font-size: 12px; color: var(--muted); margin-top: 3px; }
/* One hue per figure, matching the reference: orders violet, delivered green,
   in-transit amber, returned red, revenue blue, profit teal. */
.kpi.tone-accent { --kpi-c: var(--accent); }
.kpi.tone-ok { --kpi-c: var(--ok); }
.kpi.tone-warn { --kpi-c: var(--warn); }
.kpi.tone-bad { --kpi-c: var(--bad); }
.kpi.tone-info { --kpi-c: var(--info); }
.kpi.tone-violet { --kpi-c: var(--violet); }
.kpi.tone-teal { --kpi-c: var(--teal); }

/* Two columns where the second is a narrower rail of secondary panels. */
.split { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: var(--gap-section); align-items: start; }
@media (max-width: 1100px) { .split { grid-template-columns: minmax(0, 1fr); } }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--panel-top);
  overflow: hidden;
}
.panel-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 18px;
  min-height: 50px;
  border-bottom: 1px solid var(--line);
}
.panel-head h2, .panel-head h3 { margin: 0; }
.panel-head .spacer { margin-left: auto; }
.panel-body { padding: 16px 18px; }
/* A table inside a panel loses its own frame and takes the panel's padding on
   the sides, so its first column lines up with the panel title above it. */
.panel .grid { border: none; border-radius: 0; box-shadow: none; }
.panel .grid th:first-child, .panel .grid td:first-child { padding-left: 18px; }
.panel .grid th:last-child, .panel .grid td:last-child { padding-right: 18px; }

/* A share-of-total bar. No background track: the bar IS the proportion, and a
   grey track behind it only adds a second thing to read. */
.bars { display: flex; flex-direction: column; gap: 9px; }
.bar-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; align-items: baseline; }
.bar-row .bar-k { font-size: 13px; color: var(--ink-2); }
.bar-row .bar-n { font-family: var(--mono); font-size: 13px; font-variant-numeric: tabular-nums; }

/* Seven columns, one per day. The column is orders taken; the line across it is
   the money actually collected at the door that day. Two facts per column, no
   drawing library, nothing to load.

   The bars are SVG rectangles rather than divs with a height percentage,
   because the content policy this service sends forbids inline styles: a bar
   sized with style="height:60%" comes out flat and nobody notices until the
   chart has been lying for a week. Geometry in attributes is not CSS, so it
   survives the policy — and it is exact, where a scale of ready-made height
   classes would round. */
.week { display: flex; align-items: flex-end; gap: 10px; }
.week-day { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; }
.week-plot {
  width: 100%;
  height: 124px;
  display: block;
  border-bottom: 1px solid var(--line);
  overflow: visible;
}
.week-bar {
  fill: color-mix(in oklab, var(--accent) 55%, transparent);
  transition: fill 120ms var(--ease);
}
.week-day:hover .week-bar { fill: var(--accent); }
.week-cod { fill: var(--accent); }
.week-day:hover .week-cod { fill: var(--ok); }
.week-n { font-family: var(--mono); font-size: 12px; font-weight: 600; }
.week-k { font-family: var(--mono); font-size: 10px; color: var(--muted); }
.week-day.is-today .week-k { color: var(--accent); font-weight: 600; }

/* Two-column facts: a label and its value on one line, no table furniture. */
.pairs { display: flex; flex-direction: column; gap: 9px; }
.pairs > div { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
.pairs span { font-size: 12.5px; color: var(--muted); }
.pairs b { font-weight: 600; font-size: 13px; text-align: right; }

.alerts { display: flex; flex-direction: column; gap: 12px; }
.alert-row { display: flex; flex-direction: column; gap: 4px; }
.alert-detail { font-size: 13px; color: var(--ink-2); overflow-wrap: anywhere; }

/* The share bar takes the stage's own colour, so the strip repeats what the
   badges already say rather than introducing a second colour language. Drawn
   with the same SVG trick as the week, and for the same reason. */
a.bar-row { color: inherit; text-decoration: none; }
a.bar-row:hover { text-decoration: none; }
a.bar-row:hover .bar-k { color: var(--ink); }
.bar-line { grid-column: 1 / -1; width: 100%; height: 4px; display: block; }
.bar-line rect { fill: var(--accent); }
.bar-line rect.st-awaiting-confirm, .bar-line rect.st-unreachable { fill: var(--warn); }
.bar-line rect.st-confirmed { fill: var(--ok); }
.bar-line rect.st-pushed-supplier, .bar-line rect.st-shipping { fill: var(--info); }

/* Panels stacked in the same column of the split. */
.panel.stacked { margin-top: var(--gap-section); }

.icon-btn.is-warn { color: var(--warn); border-color: color-mix(in oklab, var(--warn) 40%, transparent); }

input.narrow { width: 5.5em; }

/* --- the phase mark -----------------------------------------------------
   A screen that is planned but not built is SHOWN, carrying the phase it
   arrives in. Hiding it answers "where is the stock screen?" with silence;
   showing it unmarked answers with a lie. */

.phase {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .06em;
  padding: 1px 5px;
  border-radius: 5px;
  border: 1px solid color-mix(in oklab, var(--muted) 40%, transparent);
  color: var(--muted);
  white-space: nowrap;
}

.nav-soon {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 10px;
  border-radius: var(--r-control);
  color: color-mix(in oklab, var(--muted) 88%, transparent);
  font-size: 13.5px;
  white-space: nowrap;
  cursor: default;
}
.nav-soon .ico { color: color-mix(in oklab, var(--muted) 65%, transparent); }
/* The label gives way, never the mark: a phase cut in half reads as a different
   phase, and "P1" where "P12" was meant is a year's difference. */
.nav-soon > span:not(.phase) { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nav-soon .phase { flex: none; margin-left: auto; }
.rail-nav a > span:not(.nav-count) { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.rail-nav a .nav-count { flex: none; }
/* Collapsed: only the icon. The label AND the phase badge both go — a badge
   left behind spills out past a 68px rail and reads as a rendering fault. The
   phase still travels in the icon's tooltip, so the information is not lost. */
.app.slim .nav-soon { justify-content: center; padding: 9px 0; }
.app.slim .nav-soon > span { display: none; }

/* --- the overview -------------------------------------------------------- */

.toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: var(--strip-gap); flex-wrap: wrap; }
.toolbar .spacer { margin-left: auto; }

/* One control, several choices, the current one filled. */
.segmented { display: inline-flex; padding: 3px; gap: 2px; background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-control); }
.segmented a {
  padding: 5px 12px;
  border-radius: 6px;
  font-size: 13px;
  color: var(--ink-2);
  text-decoration: none;
}
.segmented a:hover { color: var(--ink); text-decoration: none; }
.segmented a.on { background: var(--accent); color: var(--accent-ink); font-weight: 600; }

/* The two columns are the same height: the side column stretches to match the
   main one instead of stopping short and leaving a band of empty page beside
   it. The activity feed takes up whatever slack is left and scrolls inside
   itself, so a long history fills the space rather than pushing the page down. */
.dash { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--gap-section); align-items: stretch; }
.dash-main { display: flex; flex-direction: column; gap: var(--gap-section); min-width: 0; }
.dash-side { display: flex; flex-direction: column; gap: var(--gap-section); min-height: 0; }
.dash-side .panel.stacked { margin-top: 0; }
.dash-side .panel.grow { flex: 1 1 0; min-height: 180px; display: flex; flex-direction: column; }
.dash-side .panel.grow .panel-body { flex: 1; overflow-y: auto; }
.row-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gap-section); }
@media (max-width: 1400px) { .dash { grid-template-columns: minmax(0, 1fr); } .dash-side { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--gap-section); } .dash-side .panel.stacked { margin-top: 0; } }
@media (max-width: 1100px) { .row-3 { grid-template-columns: minmax(0, 1fr); } .dash-side { grid-template-columns: minmax(0,1fr); } }

.kpi-line { display: flex; align-items: baseline; gap: 8px; }
.kpi-share { font-family: var(--mono); font-size: 13px; color: var(--muted); }
.kpi-foot { display: flex; align-items: center; gap: 7px; margin-top: 4px; }
.kpi-head .phase { margin-left: auto; }
.kpi.tone-ok .kpi-badge { background: var(--ok-dim); color: var(--ok); }
.kpi.tone-warn .kpi-badge { background: var(--warn-dim); color: var(--warn); }
.kpi.tone-bad .kpi-badge { background: var(--bad-dim); color: var(--bad); }
.kpi.tone-info .kpi-badge { background: var(--info-dim); color: var(--info); }

/* Movement against the period before. Up is not automatically good — a rise in
   returns is a bad week — so the arrow says direction and the colour says
   which direction this particular figure wants to go. */
.delta { display: inline-flex; align-items: center; gap: 2px; font-family: var(--mono); font-size: 12px; font-weight: 600; }
.delta.up { color: var(--ok); }
.delta.down { color: var(--bad); }
.kpi.tone-bad .delta.up { color: var(--bad); }
.kpi.tone-bad .delta.down { color: var(--ok); }

.legend { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--muted); }
.sw { width: 9px; height: 9px; border-radius: 3px; display: inline-block; background: var(--muted); }
.sw-revenue { background: var(--accent); }
.sw-gross { background: var(--ok); }
.sw-orders { background: color-mix(in oklab, var(--accent) 55%, transparent); }

.linechart { width: 100%; height: 148px; display: block; overflow: visible; }
.ln-revenue { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.ln-gross { fill: none; stroke: var(--ok); stroke-width: 2; stroke-dasharray: 4 3; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.ln-area { fill: var(--accent-dim); stroke: none; }
.ln-dot { fill: var(--accent); stroke: var(--panel); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.axis { display: flex; justify-content: space-between; margin-top: 6px; }
.axis span { font-family: var(--mono); font-size: 10px; color: var(--muted); }

.donut-body { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.donut { width: 132px; height: 132px; flex: none; transform: rotate(-90deg); }
.donut .slice { fill: none; stroke-width: 5; }
.donut text { transform: rotate(90deg); transform-origin: 20px 20px; text-anchor: middle; fill: var(--ink); }
.donut-total { font-size: 8px; font-weight: 700; }
.donut-cap { font-size: 3.2px; fill: var(--muted); }
.donut-key { display: flex; flex-direction: column; gap: 7px; font-size: 12.5px; min-width: 0; }
.donut-key > div { display: flex; align-items: center; gap: 6px; }
.donut-key b { font-family: var(--mono); font-weight: 600; }

/* One colour per group, shared by the ring and its key. */
.sl-delivered, .slice.sl-delivered { stroke: var(--ok); background: var(--ok); }
.sl-shipping, .slice.sl-shipping { stroke: var(--info); background: var(--info); }
.sl-waiting, .slice.sl-waiting { stroke: var(--warn); background: var(--warn); }
.sl-returned, .slice.sl-returned { stroke: var(--bad); background: var(--bad); }

.panel-foot { padding: 10px 16px; border-top: 1px solid var(--line); font-size: 11.5px; }

/* A compact table lives inside a narrow panel, so it takes the width it is
   given rather than the floor a full-page table needs. Keeping the floor here
   would scroll a four-column table sideways inside a 300px panel and show only
   the first column, which is the one column that says nothing. */
.grid.tight th, .grid.tight td { padding: 7px 12px; }
.grid.tight { min-width: 0; }
.grid.tight td.num, .grid.tight th.num { font-size: 12.5px; }

.tasks { display: flex; flex-direction: column; gap: 2px; }
.task {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 8px;
  border-radius: var(--r-control);
  color: var(--ink-2);
  font-size: 13px;
  text-decoration: none;
}
a.task:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; }
.task-n {
  min-width: 26px; height: 22px;
  display: grid; place-items: center;
  padding: 0 6px;
  border-radius: 6px;
  font-family: var(--mono); font-size: 12px; font-weight: 600;
  background: var(--panel-2); color: var(--muted);
}
.task-n.tone-ok { background: var(--ok-dim); color: var(--ok); }
.task-n.tone-warn { background: var(--warn-dim); color: var(--warn); }
.task-n.tone-bad { background: var(--bad-dim); color: var(--bad); }
.task-k { flex: 1; min-width: 0; }
.task-go { font-size: 12px; color: var(--accent); }
.task.is-soon { color: var(--muted); }

.feed { display: flex; flex-direction: column; gap: 11px; }
.feed-row { display: flex; gap: 9px; align-items: flex-start; }
.feed-dot {
  width: 22px; height: 22px; flex: none;
  display: grid; place-items: center;
  border-radius: 6px;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  background: var(--panel-2); color: var(--muted);
  text-transform: uppercase;
}
.feed-dot.src-ui { background: var(--accent-dim); color: var(--accent); }
.feed-dot.src-webhook, .feed-dot.src-api { background: var(--info-dim); color: var(--info); }
.feed-dot.src-poll, .feed-dot.src-system { background: var(--ok-dim); color: var(--ok); }
.feed-what { font-size: 12.5px; color: var(--ink-2); overflow-wrap: anywhere; }

.note-form textarea { height: auto; padding: 9px 10px; resize: vertical; font-size: 13px; }
.note-form button { margin-top: 8px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  border-radius: 99px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink-2);
  font-size: 12.5px;
  white-space: nowrap;
  text-decoration: none;
}
a.chip:hover { border-color: var(--accent-line); color: var(--ink); text-decoration: none; }
.chip.is-warn { border-color: color-mix(in oklab, var(--warn) 45%, transparent); color: var(--warn); background: var(--warn-dim); }
@media (max-width: 1100px) { .topbar .chip { display: none; } }

/* --- tables ------------------------------------------------------------- */

/* A table scrolls SIDEWAYS inside its own box, at every width. Letting it push
   the page instead means the rail, the bar and every other column shift left as
   you read across a row — and the alignment between columns is most of what a
   table is for. */
.grid {
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--panel-top);
}
/* The table stays a real table, so a header cell and the cells beneath it share
   one column and line up — splitting the head and body into two separate table
   boxes let each size its columns to its own text, and the header drifted off
   its rows. It fills the space it is given and compresses to a floor; below that
   floor .table-scroll scrolls sideways rather than letting the table push the
   page and shift every other column as you read across a row. */
.table-scroll { display: block; width: 100%; overflow-x: auto; border-radius: var(--r-panel); }
/* A block that follows another with no heading of its own still needs the
   section gap above it, so it does not weld to what precedes it. */
.gap-above { margin-top: var(--gap-section); }

/* The numbered pager under a list: page controls centred, one strip-gap below
   the rows they page through. */
.pager { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: var(--strip-gap); flex-wrap: wrap; }
.pager .muted { font-size: 12.5px; padding: 0 4px; }
.grid th, .grid td {
  text-align: left;
  padding: 9px 13px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
.grid th {
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .12em;
  font-weight: 500;
  color: var(--muted);
  background: var(--panel-2);
  white-space: nowrap;
}
.grid tbody tr:hover { background: var(--panel-2); }
.grid tr:last-child td { border-bottom: none; }

.pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 9px;
  border-radius: 99px;
  font-size: 11.5px;
  font-weight: 500;
  background: var(--panel-2);
  color: var(--muted);
  white-space: nowrap;
}
.pill.ok { background: var(--ok-dim); color: var(--ok); }
.pill.bad { background: var(--bad-dim); color: var(--bad); }
.pill.run { background: var(--warn-dim); color: var(--warn); }

/* --- the pipeline ------------------------------------------------------- */

.tabs { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: var(--strip-gap); }
.tabs a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 11px;
  border: 1px solid var(--line-2);
  border-radius: 99px;
  background: var(--panel);
  color: var(--ink-2);
  text-decoration: none;
  font-size: 12.5px;
}
.tabs a:hover { border-color: var(--accent-line); color: var(--ink); text-decoration: none; }
.tabs a.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.tabs a b { font-family: var(--mono); font-weight: 600; }

.filters { flex-wrap: wrap; }
/* Carries the class it always ships with, so it outweighs `form.inline {
   margin: 0 }` (equal weight, earlier in the sheet) and the gap under the bar
   actually lands — without this the filter bar sat flush on the table. */
/* Spans the full width — display:flex + width beat form.inline's inline-flex —
   and its text inputs stretch to fill it, so the controls are not bunched at
   the left with dead space on the right. flex-wrap (on .filters) drops them to
   the next line on a narrow screen instead of overflowing. */
.filters.inline { display: flex; width: 100%; margin-bottom: var(--strip-gap); }
.filters input[name="q"], .filters input[name="campaign"] { flex: 1 1 200px; }
/* A plain row of status-filter links (the inbound page) is a control strip too,
   and takes the same gap down to the table it filters. */
.statusbar { margin: 0 0 var(--strip-gap); }
.filters input, .filters select { min-width: 150px; }
/* The filter bar sits its buttons in a row with 36px inputs; a 30px .small
   button beside a 36px field reads as short and misaligned, so the bar's own
   buttons take the input height. A .small button in a table row stays compact. */
.filters button.small, .filters .btn.small { height: 36px; padding: 0 14px; }

/* Every cell's content rides the middle of its row, so a one-line figure or a
   button sits level with the centre of a taller neighbouring cell instead of
   clinging to the top. (The rest of the tables already centre this way.) */
.grid.orders td { vertical-align: middle; }
.grid.orders td.pick, .grid.orders th.pick { width: 30px; }
.grid.orders td.pick input, .grid.orders th.pick input { width: auto; height: auto; margin: 0; }
.grid td.num, .grid th.num { text-align: right; white-space: nowrap; }
.grid.orders td.items { font-size: 12.5px; color: var(--ink-2); }
.grid tr.risk { background: var(--warn-dim); }
.warn { color: var(--warn); font-size: 12px; margin-top: 2px; }
.hidden-form { display: none; }

/* One stage, one colour, wherever it appears: the tab strip, the row badge and
   the order page must never disagree about what a stage looks like. */
.pill.st-awaiting-confirm { background: var(--warn-dim); color: var(--warn); }
.pill.st-unreachable { background: var(--warn-dim); color: var(--warn); }
.pill.st-confirmed { background: var(--ok-dim); color: var(--ok); }
.pill.st-pushed-supplier { background: var(--info-dim); color: var(--info); }
.pill.st-shipping { background: var(--info-dim); color: var(--info); }
.pill.st-delivered { background: var(--ok-dim); color: var(--ok); }
.pill.st-returned { background: var(--bad-dim); color: var(--bad); }
.pill.st-cancelled-precall { background: var(--panel-2); color: var(--muted); }
.pill.st-reconciled { background: var(--ok-dim); color: var(--ok); }

.bulkbar {
  position: sticky;
  bottom: 12px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 10px 14px;
  margin-top: var(--strip-gap);
  background: var(--panel);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow);
}

.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: var(--strip-gap); align-items: center; }
/* Every control in an action row is one height, so an input and the button
   beside it read as a pair rather than two things that do not line up. The
   compact size is dropped here: next to a full-height input a 30px button looks
   like a mistake. */
.actions button, .actions .btn, .actions input, .actions select { height: 36px; }
.actions button.small, .actions .btn.small { padding: 0 12px; font-size: 13.5px; font-weight: 600; }
.actions form.grow { flex: 1; }
.actions form.grow input[name=note] { width: 100%; }

/* --- connections and secrets -------------------------------------------- */

.card.issued { margin-bottom: var(--gap-section); border-color: var(--accent-line); }
code.secret {
  display: block;
  padding: 12px 14px;
  background: oklch(21% 0.02 260);
  color: oklch(93% 0.01 260);
  border: 1px solid var(--line-2);
  border-radius: var(--r-control);
  word-break: break-all;
  user-select: all;
  font-size: 13px;
}

.tile.source { flex-direction: row; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: var(--gap-section); }
.tile.source > div:first-child { flex: 1; min-width: 260px; }
.grid td.detail { max-width: 380px; color: var(--muted); font-size: 12.5px; word-break: break-word; }

pre.payload {
  background: oklch(21% 0.02 260);
  color: oklch(93% 0.01 260);
  padding: 14px 16px;
  border-radius: var(--r-panel);
  overflow-x: auto;
  font-size: 12.5px;
  line-height: 1.5;
  max-height: 60vh;
}

/* --- handover and printing ---------------------------------------------- */

.confirm-box {
  border: 1px solid var(--accent-line);
  background: var(--accent-dim);
  border-radius: var(--r-panel);
  padding: 14px 16px;
  margin: var(--gap-section) 0;
  max-width: 640px;
}
.confirm-box p { margin-top: 0; }
.confirm-box input { margin-bottom: 10px; background: var(--panel); }

/* A label is A6 because that is the paper a thermal label printer takes. On
   screen it looks like the sticker it becomes; on paper each one is its own
   page, so a printer with a roll produces one sticker per parcel. It is always
   ink on white, whatever theme the screen is in. */
.label {
  width: 105mm;
  min-height: 148mm;
  padding: 6mm;
  border: 1px solid #000;
  margin: 0 0 10px;
  font-size: 11pt;
  line-height: 1.35;
  page-break-after: always;
  break-after: page;
  background: #fff;
  color: #000;
}
.label-head { display: flex; justify-content: space-between; font-size: 10pt; border-bottom: 1px solid #000; padding-bottom: 2mm; }
.label-carrier { font-weight: 700; text-transform: uppercase; }
.label-track { font-size: 18pt; font-weight: 700; letter-spacing: 1px; margin: 3mm 0; word-break: break-all; }
.label-to { border-top: 1px dashed #000; padding-top: 3mm; }
.label-name { font-size: 13pt; font-weight: 700; }
.label-phone { font-size: 13pt; font-weight: 700; }
.label-address { margin-top: 1mm; }
.label-cod {
  margin: 3mm 0;
  padding: 2mm;
  border: 2px solid #000;
  font-size: 15pt;
  font-weight: 700;
  text-align: center;
}
.label-items { border-top: 1px dashed #000; padding-top: 2mm; font-size: 10pt; }
.label-note { margin-top: 2mm; font-size: 10pt; font-style: italic; }
.label-inspect { margin-top: 2mm; font-weight: 700; text-align: center; border: 1px solid #000; padding: 1mm; }

/* --- narrow screens ----------------------------------------------------- */

/* --- narrow screens -----------------------------------------------------
   Below the desk, the rail becomes a drawer. It is driven by the address
   (:target), not by a script: the drawer is open only while the address names
   it, following any link inside closes it because the next page has no
   fragment, and the whole thing works with nothing running. */

.hamburger, .rail-close, .nav-backdrop { display: none; }

@media (max-width: 860px) {
  .hamburger {
    display: grid;
    place-items: center;
    width: 36px; height: 36px;
    margin-left: -4px;
    border-radius: var(--r-control);
    color: var(--ink-2);
    flex: none;
  }
  .hamburger:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; }

  /* Labels stay. An icon-only rail on a telephone is a guessing game, and the
     drawer has the width to spell things out. */
  .rail {
    width: min(84vw, var(--nav-w));
    transform: translateX(-101%);
    box-shadow: none;
    transition: transform 180ms var(--ease);
  }
  .rail:target { transform: none; box-shadow: var(--shadow); }
  .rail:target ~ .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 25;
    background: oklch(20% 0.02 260 / 0.5);
  }
  .rail-close {
    display: grid;
    place-items: center;
    margin-left: auto;
    width: 32px; height: 32px;
    border-radius: var(--r-control);
    color: var(--muted);
  }
  .rail-close:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; }

  /* Everything the rail used to displace now starts at the edge. */
  .topbar { left: 0; padding: 0 12px; gap: 8px; }
  body.app main.page,
  body.app.slim main.page { margin-left: 0; padding-left: 12px; padding-right: 12px; }

  /* One line, or the bar grows a second row and the page below it jumps. */
  .topbar h1 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .whoami .who-name, .whoami .chev { display: none; }
  .whoami { padding: 0 4px; }
  .usermenu-pop { position: fixed; top: calc(var(--bar-h) + 4px); right: 10px; }

  /* The stage strip scrolls instead of stacking into six rows of pills. */
  .tabs { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px; }
  .tabs a { flex: none; }

  .split { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
  .kpi { padding: 13px 14px 12px; }
  .kpi-val { font-size: 22px; }
  .panel-head { flex-wrap: wrap; }
  .week { gap: 6px; }
  .week-plot { height: 96px; }
  /* On paper a label is A6; on a telephone it is as wide as the telephone. */
  .label { width: 100%; min-height: 0; }
  .actions form.grow { flex: 1 1 100%; }
  main.page { padding-top: calc(var(--bar-h) + 14px); }
}

@media print {
  /* Everything that is not the parcel list or the labels is furniture. */
  .rail, .topbar, .foot, .confirm-box, .screen-only, .notice { display: none !important; }
  body { background: #fff; color: #000; }
  main.page { margin: 0; padding: 0; max-width: none; }
  .label { border: none; margin: 0; }
  /* Paper has no sideways scroll: a table takes the width of the page rather
     than the on-screen floor, which on an A6 sheet would run off the edge. */
  .grid { min-width: 0; }
  .table-scroll { overflow-x: visible; }
  @page { size: A6; margin: 4mm; }
}

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

/* The page schedule: a post's text keeps its line breaks, and the files it
   carries show as small thumbnails rather than file names. */
.post-body { white-space: pre-wrap; max-width: 520px; }
.grid.posts td.post-body a { color: inherit; }
.thumb-sm { height: 44px; width: 44px; object-fit: cover; border-radius: 6px; margin-right: 4px; vertical-align: middle; }
.thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
.thumbs .thumb-sm { height: 120px; width: 120px; }
fieldset.library { border: 1px solid var(--line); border-radius: 8px; padding: 8px; display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 8px; }
fieldset.library legend { padding: 0 4px; font-size: 13px; color: var(--muted); }
.library .thumb { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 96px; font-size: 12px; cursor: pointer; }
.library .thumb img { height: 72px; width: 72px; object-fit: cover; border-radius: 6px; }
.library .thumb input { width: auto; margin: 0; }
.library .thumb .muted { max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
form.post-form { max-width: 560px; }
form.post-form textarea { width: 100%; resize: vertical; }
/* A post is paragraphs, not a one-line field: the boxes that hold one grow
   with their rows instead of taking the single-line height. */
form.stack textarea { height: auto; min-height: 150px; padding: 9px 10px; resize: vertical; line-height: 1.45; }
