/* ==========================================================================
   Control Center — "Manifest"
   Interface for an import operation: supplier → in transit → warehouse → bol LVB.
   Colour carries cargo state and nothing else; every number is set in a tabular
   mono so columns line up like a freight manifest. Every colour is a token in
   :root — no rule below carries a literal — so a theme only redefines tokens.
   ========================================================================== */

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url("/static/fonts/archivo-var.woff2") format("woff2");
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/static/fonts/inter-var.woff2") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/plexmono-400.woff2") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/plexmono-500.woff2") format("woff2");
}

/* --- Tokens --------------------------------------------------------------- */

:root {
  color-scheme: dark;

  /* Surfaces, from deepest to nearest */
  --keel: #0a0f11;
  --hull: #0e1416;
  --well: #101719;
  --deck: #161e21;
  --deck-hi: #1d272b;
  --seam: #232e32;
  --seam-hi: #33434a;

  /* Text */
  --paint: #e4ebea;
  --chalk: #7e9098;
  --chalk-dim: #5c6d75;

  /* Cargo signals — the only colours in the interface */
  --stowed: #4fc1a0;
  --transit: #e0913c;
  --alarm: #f0645c;

  /* The same signals as channel triplets, for washes that need their own alpha.
     "Lift" is the overlay that lightens a surface on hover: white on dark. */
  --lift-rgb: 255, 255, 255;
  --stowed-rgb: 79, 193, 160;
  --transit-rgb: 224, 145, 60;
  --alarm-rgb: 240, 100, 92;
  --chalk-rgb: 126, 144, 152;

  --stowed-wash: rgba(var(--stowed-rgb), 0.11);
  --transit-wash: rgba(var(--transit-rgb), 0.11);
  --alarm-wash: rgba(var(--alarm-rgb), 0.11);
  --chalk-wash: rgba(var(--chalk-rgb), 0.08);

  /* Roles that used to be literals inside rules */
  --on-accent: var(--keel);        /* text on a teal fill: .mark, .primary */
  --nav-ink: #9fb0b8;              /* resting nav label, between chalk and paint */
  --btn-hover: #253136;            /* button hover, one step above deck-hi */
  --accent-hover: #5ed3b1;         /* .primary hover */
  --scrim: rgba(5, 8, 9, 0.7);     /* modal backdrop, nav drawer */
  --shadow-tip: 0 8px 24px rgba(0, 0, 0, 0.5);

  --warn: var(--transit);
  --shadow: 0 18px 44px rgba(0, 0, 0, 0.55);

  --font-display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --font-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --r-panel: 10px;
  --r-control: 6px;

  /* The marketplaces in their own colours, for the channel chip. A logo colour is the same in
     both themes; the chip always carries white text on it. */
  --brand-bol: #0000a4;
  --brand-kaufland: #e10915;
  --brand-shopify: #5e8e3e;
  --on-brand: #ffffff;

  /* Spacing: one gutter for everything directly inside a panel, two gaps, one head padding */
  --gutter: 16px;
  --gap-panel: 14px;
  --gap-field: 12px;
  --pad-head: 13px;
}

/* Light theme — the same ship in daylight. Every surface keeps the cool cyan cast of the dark
   set (no cream), the keel stays the darkest surface (sidebar) and the deck the whitest
   (cards), and the three signals are deepened until an 11px mono pill on its own wash clears
   4.5:1. Dark is the base above; this block only redefines tokens. */
html[data-theme="light"] {
  color-scheme: light;

  --keel: #e3e9e9;
  --hull: #f0f3f3;
  --well: #f7f9f9;
  --deck: #ffffff;
  --deck-hi: #eaf0f0;
  --seam: #d3dcdd;
  --seam-hi: #b3c1c3;

  --paint: #172321;
  --chalk: #4d5f65;
  --chalk-dim: #66777c;

  --stowed: #117a63;
  --transit: #9a530b;
  --alarm: #bf2f27;

  --lift-rgb: 23, 35, 33;
  --stowed-rgb: 17, 122, 99;
  --transit-rgb: 154, 83, 11;
  --alarm-rgb: 191, 47, 39;
  --chalk-rgb: 77, 95, 101;

  --on-accent: #ffffff;
  --nav-ink: #3f5257;
  --btn-hover: #dfe6e7;
  --accent-hover: #0d6650;
  --scrim: rgba(23, 35, 33, 0.42);
  --shadow: 0 18px 44px rgba(23, 35, 33, 0.14);
  --shadow-tip: 0 8px 24px rgba(23, 35, 33, 0.16);
}

/* Antialiased is right for light-on-dark text and thins dark-on-light text on macOS. */
html[data-theme="light"] body {
  -webkit-font-smoothing: auto;
}

* {
  box-sizing: border-box;
}

html {
  background: var(--hull);
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  grid-template-columns: 244px 1fr;
  background: var(--hull);
  color: var(--paint);
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

/* --- Login ---------------------------------------------------------------- */
/* The login page has no rail, so it overrides the two-column body grid. */
body.login-body {
  grid-template-columns: minmax(0, 1fr);
  align-content: center;
  justify-items: center;
  /* Full-bleed on a phone in standalone: keep the card clear of the notch and the home bar. */
  padding: max(var(--gutter), env(safe-area-inset-top)) max(var(--gutter), env(safe-area-inset-right))
    max(var(--gutter), env(safe-area-inset-bottom)) max(var(--gutter), env(safe-area-inset-left));
}

.login-page {
  display: grid;
  gap: var(--gap-panel);
  width: 100%;
  max-width: 320px;
}

/* Same 12px of air a note gets after a field row anywhere else. */
.login-page label + .table-note,
.login-page .table-note + .table-note {
  padding-top: 12px;
}

/* Beats the .table-note colour it shares its element with, which is defined further down. */
.login-page .login-error {
  color: var(--alarm);
}

/* --- Typography ----------------------------------------------------------- */

h1,
h2 {
  margin: 0;
  font-family: var(--font-display);
}

h1 {
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.025em;
}

h2 {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

h3 {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

p {
  color: var(--chalk);
  margin: 5px 0 0;
}

/* Every figure, code and date in the app. Applied by app.js on data cells. */
.num,
.chain-qty,
.chain-value,
.chain-total strong,
.days-pill,
.metric strong,
.status,
.lvb-lead-badge,
.muted-cell,
td input[type="number"],
input[type="number"],
input[type="date"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.num {
  text-align: right;
  white-space: nowrap;
}

th.num {
  text-align: right;
}

/* --- Sidebar -------------------------------------------------------------- */

.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  background: var(--keel);
  border-right: 1px solid var(--seam);
  color: var(--paint);
  padding: 20px 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  overflow: hidden;
}

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 0 6px;
}

.brand strong {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.brand span {
  display: block;
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.02em;
  margin-top: 1px;
}

.mark {
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 8px;
  background: var(--stowed);
  color: var(--on-accent);
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.04em;
}

/* The nav is the part that scrolls, so brand and the theme switch stay pinned. */
nav {
  display: grid;
  gap: 0;
  align-content: start;
  flex: 1 1 auto;
  min-height: 0;
  padding-bottom: 28px;
  scroll-padding-bottom: 28px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  /* Fade the last 28px so a nav taller than the viewport visibly continues; the padding above
     keeps the last button clear of the fade once scrolled to the end. */
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
}

.nav-group {
  display: grid;
  gap: 1px;
}

.nav-group + .nav-group,
nav > a.nav + .nav-group,
nav > .nav-group + a.nav {
  margin-top: 16px;
}

.nav-group-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--chalk-dim);
  padding: 0 10px 6px;
}

/* A group folds. The label is a button with a chevron; the links under it are hidden while the
   group is closed, and the label of the group holding the open page keeps the accent so the
   page is never lost in a closed group. The choice per group is remembered on the device. */
button.nav-group-label {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin: 0;
  border: 0;
  border-radius: var(--r-control);
  background: transparent;
  text-align: left;
  cursor: pointer;
}

/* The chevron moved to the right when the icons arrived. On the left it competed with the icon
   for the same slot and pushed Dashboard and Instellingen, which have no chevron, out of line
   with the groups. On the right it is the only thing in that column, so it reads as one mark
   with one meaning: this row opens. */
button.nav-group-label::after {
  content: "▾";
  margin-left: auto;
  font-size: 9px;
  color: var(--chalk-dim);
}

.nav-group[data-collapsed] > button.nav-group-label::after {
  content: "▸";
}

/* 24-grid icons at the size of the cap height beside them, in the resting label colour so the
   row reads as one thing and the icon never shouts louder than the word. */
.nav-icon {
  width: 15px;
  height: 15px;
  flex: none;
}

/* Dashboard and Instellingen are links, not group buttons, and need the same row: icon, gap,
   label. They keep their own type -- they are destinations, not headings. The left padding
   drops from the .nav 14px to the group label's 10px so the two icon columns are one column.
   Qualified with a.nav because `.nav { display: block }` is declared further down the file and
   would otherwise win on source order, which glued every icon to its label. */
a.nav.nav-top {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-left: 10px;
}

/* A page inside a group lines up with the label above it rather than with its icon, so the
   group reads as the heading of the block under it. 10px label padding + 15px icon + 8px gap. */
.nav-group > a.nav {
  padding-left: 33px;
}

.nav-group[data-collapsed] > a.nav {
  display: none;
}

.nav-group:has(> a.nav.active) > button.nav-group-label {
  color: var(--stowed);
}

button.nav-group-label:hover {
  color: var(--paint);
}

.sidebar-actions {
  margin-top: auto;
  display: grid;
  gap: 8px;
}

/* Theme switch: label left, a two-position slider right. Sun and moon are the two positions;
   the thumb (::before) follows data-theme on <html>, so no JS beyond the click handler. */
.theme-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.theme-row .nav-group-label {
  padding: 0 10px;
}

/* One more row under the theme switch, drawn like the nav links above it. */
.logout-button {
  font-family: var(--font-ui);
  color: var(--chalk);
}

.theme-switch {
  position: relative;
  display: inline-grid;
  grid-template-columns: repeat(2, 30px);
  padding: 2px;
  border: 1px solid var(--seam);
  border-radius: 999px;
  background: var(--well);
}

.theme-switch::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 30px;
  height: 26px;
  border-radius: 999px;
  background: var(--deck-hi);
  border: 1px solid var(--seam-hi);
  transition: transform 0.18s ease;
}

html[data-theme="dark"] .theme-switch::before {
  transform: translateX(30px);
}

.theme-switch button {
  position: relative;
  width: 30px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--chalk-dim);
  display: grid;
  place-items: center;
}

.theme-switch button:hover {
  background: transparent;
  color: var(--paint);
}

.theme-switch button.active {
  color: var(--paint);
}

.theme-switch svg {
  width: 15px;
  height: 15px;
}

/* --- Controls ------------------------------------------------------------- */

button,
input,
select,
textarea {
  font: inherit;
}

/* .kv-portal: "Open bij bol" is a link because it opens the seller account in a new tab, but
   it stands in the panel head as the one filled button of the conversation. */
button,
.kv-portal {
  border: 1px solid var(--seam);
  background: var(--deck-hi);
  color: var(--paint);
  border-radius: var(--r-control);
  padding: 8px 12px;
  font-size: 13px;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

button:hover,
.kv-portal:hover {
  background: var(--btn-hover);
  border-color: var(--seam-hi);
}

button:disabled {
  opacity: 0.42;
  cursor: not-allowed;
}

button:disabled:hover {
  background: var(--deck-hi);
  border-color: var(--seam);
}

.primary {
  background: var(--stowed);
  border-color: var(--stowed);
  color: var(--on-accent);
  font-weight: 600;
}

.primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

/* A link (so it can open in a new tab), drawn like the button it used to be. */
.nav {
  position: relative;
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-control);
  color: var(--nav-ink);
  padding: 8px 10px 8px 14px;
  font-size: 13.5px;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.nav::before {
  content: "";
  position: absolute;
  left: 1px;
  top: 50%;
  width: 2px;
  height: 17px;
  border-radius: 2px;
  background: var(--stowed);
  transform: translateY(-50%) scaleY(0);
  transition: transform 0.14s ease;
}

.nav:hover {
  background: rgba(var(--lift-rgb), 0.04);
  border-color: transparent;
  color: var(--paint);
}

.nav.active {
  background: var(--stowed-wash);
  border-color: transparent;
  color: var(--paint);
}

.nav.active::before {
  transform: translateY(-50%) scaleY(1);
}

/* The ring would otherwise be clipped by the scrolling nav. */
.nav:focus-visible {
  outline-offset: -2px;
}

input,
select,
textarea {
  width: 100%;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
  padding: 8px 10px;
  font-size: 13.5px;
  color: var(--paint);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: border-color 0.12s ease;
}

input:hover,
select:hover,
textarea:hover {
  border-color: var(--seam-hi);
}

input::placeholder,
textarea::placeholder {
  color: var(--chalk-dim);
}

input:read-only {
  color: var(--chalk);
}

select {
  appearance: none;
  padding-right: 26px;
  background-image: linear-gradient(45deg, transparent 50%, var(--chalk) 50%),
    linear-gradient(135deg, var(--chalk) 50%, transparent 50%);
  background-position: right 12px center, right 7px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--stowed);
}

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

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline-offset: 0;
  border-color: var(--stowed);
}

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

/* Keyboard users: first Tab stop jumps past the rail. Hidden until focused. */
.skip-link {
  position: absolute;
  top: -100px;
  left: 8px;
  z-index: 400;
}

.skip-link:focus-visible {
  top: 8px;
}

main:focus {
  outline: none;
}

tr.clickable:focus-visible {
  outline-offset: -2px;
}

main {
  min-width: 0;
  padding: 22px 24px 28px;
}

.topbar {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: flex-start;
  margin-bottom: 20px;
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Carries its own last-sync line so the state is readable without opening settings. */
.sync-button {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  line-height: 1.25;
  text-align: left;
}

.sync-button small {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
}

.sync-button:disabled small {
  display: none;
}

.inventory-discrepancy-col {
  color: var(--warn);
}

/* Laatste pallet aanpassen (bol window and Zending wijzigen, shared since 2026-10-16): the
   button, and under it the preview card with the two ways out of a half-empty pallet side by
   side so the trade-off is visible. Everything on the field gap: the button used to sit on the
   card's rule and Annuleren on the sentence above it. */
.pallet-adjust {
  display: grid;
  gap: var(--gap-field);
  justify-items: start;
}
.pallet-adjust > .pallet-preview {
  justify-self: stretch;
  display: grid;
  gap: var(--gap-field);
  padding: var(--gap-field);
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
  font-size: 12.5px;
  color: var(--chalk);
}
.pallet-option {
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--deck);
  padding: 10px 12px;
  display: grid;
  gap: 3px;
  color: var(--paint);
}

.pallet-option strong {
  margin-bottom: 4px;
}

.reconcile-status {
  font-family: var(--font-mono);
  font-size: 11px;
}

.reconcile-klopt {
  color: var(--stowed);
}

.reconcile-onderweg {
  color: var(--chalk-dim);
}

.reconcile-afwijking,
.reconcile-onbekend {
  color: var(--warn);
}

.status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--chalk);
  background: var(--deck);
  border: 1px solid var(--seam);
  padding: 7px 12px;
  border-radius: 999px;
  font-size: 12px;
  min-width: 120px;
}

.status-dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--stowed);
}

.status.busy {
  color: var(--transit);
  border-color: rgba(var(--transit-rgb), 0.35);
}

.status.busy .status-dot {
  background: var(--transit);
  animation: pulse 1.1s ease-in-out infinite;
}

@keyframes pulse {
  50% {
    opacity: 0.25;
  }
}

.view {
  display: none;
}

.view.active {
  display: block;
  animation: rise 0.16s ease-out;
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
}

/* --- De keten: the supply chain in one line ------------------------------- */

.chain-panel {
  margin-bottom: 14px;
}

.chain-facts {
  color: var(--chalk);
  font-size: 12px;
}

.chain-facts b {
  font-family: var(--font-mono);
  font-weight: 500;
  color: var(--paint);
}

.chain {
  display: flex;
  align-items: stretch;
  padding: 4px 4px 4px 0;
}

.chain-node {
  flex: 1 1 0;
  min-width: 0;
  display: grid;
  align-content: start;
  gap: 3px;
  padding: 14px var(--gutter) 13px;
}

.chain-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--chalk-dim);
}

.chain-qty {
  font-size: 23px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.chain-value {
  font-size: 12.5px;
  color: var(--chalk);
}

.chain-bar {
  display: block;
  height: 3px;
  margin-top: 9px;
  border-radius: 2px;
  background: rgba(var(--lift-rgb), 0.05);
  overflow: hidden;
}

.chain-bar i {
  display: block;
  min-width: 2px;
  height: 100%;
  border-radius: 2px;
  background: var(--paint);
}

.chain-split {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--chalk-dim);
  margin-top: 5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chain-node[data-tone="transit"] .chain-qty {
  color: var(--transit);
}

.chain-node[data-tone="transit"] .chain-bar i {
  background: var(--transit);
}

.chain-node[data-tone="stowed"] .chain-qty {
  color: var(--stowed);
}

.chain-node[data-tone="stowed"] .chain-bar i {
  background: var(--stowed);
}

.chain-arrow {
  flex: none;
  width: 26px;
  align-self: center;
  height: 1px;
  position: relative;
  background: var(--seam-hi);
  margin-top: 6px;
}

.chain-arrow::after {
  content: "";
  position: absolute;
  right: 0;
  top: 50%;
  width: 5px;
  height: 5px;
  border-top: 1px solid var(--seam-hi);
  border-right: 1px solid var(--seam-hi);
  transform: translateY(-50%) rotate(45deg);
}

.chain-total {
  flex: none;
  display: grid;
  align-content: start;
  gap: 3px;
  padding: 14px 4px 13px 20px;
  margin-left: 8px;
  border-left: 1px solid var(--seam);
  min-width: 150px;
}

.chain-total strong {
  font-size: 23px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.chain-total span:last-child {
  font-size: 12.5px;
  color: var(--chalk);
}

/* --- Panels and tables ---------------------------------------------------- */

.metric,
.panel {
  background: var(--deck);
  border: 1px solid var(--seam);
  border-radius: var(--r-panel);
}

.panel {
  overflow: hidden;
}

.form.panel {
  overflow: visible;
}

.panel-head {
  display: flex;
  justify-content: space-between;
  gap: var(--gap-field);
  align-items: center;
  padding: var(--pad-head) var(--gutter);
  border-bottom: 1px solid var(--seam);
}

.panel-head input {
  max-width: 280px;
}

/* Rankings carries one control more than Campagnes (the export), and at 280px the search field
   pushed that onto a second line, doubling the height of the head. 220px keeps the whole bar on
   one line at 1280, like the Campagnes head it should match (Mitch, 2026-09-24). */
#rkSearch {
  width: 220px;
  max-width: 220px;
}

.metrics {
  display: grid;
  grid-template-columns: repeat(5, minmax(130px, 1fr));
  /* The strip owns the three lines -- label, value, footnote -- and every tile borrows them
     through subgrid below. Without that a label wrapping to two lines drops its own number a
     line lower than the one beside it, and a row of tiles reads as crooked. */
  grid-template-rows: auto auto auto;
  gap: var(--gap-field);
  margin-bottom: var(--gap-panel);
}

.metric {
  padding: 8px 12px 9px;
  text-align: center;
}

.metrics > .metric {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  justify-items: center;
  align-content: start;
}

/* A metric strip inside a panel keeps the gutter, like every other panel child. */
.panel > .metrics {
  margin: 12px var(--gutter);
}

.metric span {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.metric strong {
  display: block;
  margin-top: 3px;
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.02em;
}

.table-wrap {
  overflow: auto;
  max-height: calc(100vh - 190px);
  scroll-padding-top: 34px;
  /* A table scrolls inside a page that also scrolls. Sideways stays inside the table: a finger
     that reaches the end of a wide table would otherwise carry on into the page. Up and down
     chain into the page on purpose -- with `contain` on both axes the wheel got stuck in a long
     list and the page could not be scrolled back to the top from there. */
  overscroll-behavior-x: contain;
  overscroll-behavior-y: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

th,
td {
  border-bottom: 1px solid var(--seam);
  padding: 9px 12px;
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
}

th {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  background: var(--deck-hi);
  position: sticky;
  top: 0;
  z-index: 1;
}

tbody tr:last-child td {
  border-bottom: 0;
}

td strong {
  font-weight: 600;
}

tr.clickable {
  cursor: pointer;
}

tr.clickable:hover td {
  background: rgba(var(--lift-rgb), 0.035);
}

.selected-row td {
  background: var(--stowed-wash);
}

.chevron {
  display: inline-block;
  width: 18px;
  color: var(--stowed);
  font-weight: 600;
}

.expanded-row td {
  background: var(--well);
  padding: 0;
  white-space: normal;
}

.nested-table {
  padding: 10px 12px 14px 30px;
  /* The dashboard panel is narrow; wide detail tables scroll rather than clip. */
  overflow-x: auto;
}

/* The order pane is not a .panel, so the strip has to carry the gutter itself: without it the
   tiles sat on the panel border and on the table head. */
[data-ord-pane] > .metrics {
  margin: var(--gap-field) var(--gutter) var(--gap-panel);
}

/* Two columns hold the longest strings on the page -- a customer with a company name, and an
   invoice number next to a button. Kept on one line they push the table ~60px past its wrapper,
   which puts the last column of every open folder behind the horizontal scrollbar. Letting just
   these two wrap brings the table back inside the page at 1280. */
.order-table td:nth-child(3),
.order-table td:nth-child(7) {
  white-space: normal;
}

/* Buttons in a table cell. Joined by a space they break mid-row and drift apart; a flex row
   keeps one gap between them and wraps as a block. It wraps when the column is squeezed -- inside
   a folder, where the table fills the row -- and stays on one line in a register that scrolls
   sideways, so rows there keep their height. */
.cell-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* A note under the CBM field of a shipment line (2026-10-15): the stale-CBM warning with its
   Overnemen button, and the carton-measure check. Small text on the field's own width, the
   button beside the sentence, so the line keeps its height instead of growing three chips. */
.cbm-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.4;
  white-space: normal;
}

.cbm-note .cell-warn {
  background: none;
  font-weight: 500;
}

.cbm-note button {
  padding: 3px 9px;
  font-size: 12px;
}

/* With a note under the CBM field the line grows; every field keeps to the top so the inputs
   stay on one line instead of the others floating to the middle of the taller row. */
tr[data-shipment-line-id] > td {
  vertical-align: top;
}

/* The register is nine columns wide and scrolls sideways in its wrapper anyway. Letting its
   buttons wrap there would only trade the scroll for rows three buttons tall. */
#cinvRows .cell-actions,
#lvbCheckerInvoices .cell-actions {
  flex-wrap: nowrap;
}

/* The reason an upload failed, under the status chip rather than beside it. */
.cell-error {
  display: inline-block;
  max-width: 150px;
  overflow: hidden;
  color: var(--chalk);
  font-size: 11.5px;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* The register carries nine columns; the customer is the one string that may break instead of
   pushing the last column off the page. */
#cinvRows td:nth-child(5) {
  white-space: normal;
  min-width: 150px;
}

/* A note that follows a filter row rather than the head sat on the rule above it. */
.brand-filter + .table-note,
.sales-controls + .table-note {
  padding-top: 12px;
}

/* A folder under an order row: one section per subject, each a heading of its own above its box.
   The heading is the only thing that separates "Regels" from the column names under it, so it
   carries the indent and the air, and its box sits flush against it. */
.detail-section {
  /* The indent is a variable because the sticky pieces inside have to stop at exactly the same
     line. `left: 0` pins them to the scroll container, which sits BEFORE this padding, so they
     snapped flat against the edge the moment the figures were scrolled sideways. */
  --folder-indent: 30px;
  padding: 0 12px 14px var(--folder-indent);
}

.detail-section:first-child {
  padding-top: 12px;
}

/* A row with a three-line first cell (number, chip, note) and a button beside it: the button
   sits on the middle line, not glued to the top edge. Same for every cell in these tables. */
.detail-section td {
  vertical-align: middle;
}

/* A cell holding buttons: the 9px every cell has reads as nothing next to a 36px button, so the
   row sits on the box's edge. Twelve above and below, and the whole row grows with it. */
.detail-section td:has(> .cell-actions) {
  padding-top: 12px;
  padding-bottom: 12px;
}

/* A folder section whose table has its own column chooser: the heading and the Kolommen button
   on one line. A .panel-head would have been the near-copy -- it carries a card's padding and a
   border, neither of which belongs inside an open row -- so this is the heading row on its own,
   with the same 8px the bare h3 keeps under it.

   The folder sits in a cell of a table that scrolls sideways inside its wrapper, so it is as
   wide as that table, not as wide as the screen. A button pushed to the far right would then be
   off-screen until you scrolled the table -- a control you cannot find. It sits next to its
   heading instead, and stays where the section starts while the figures scroll past. */
.inz-section-head {
  position: sticky;
  left: var(--folder-indent);
  display: flex;
  align-items: center;
  gap: var(--gap-field);
  width: max-content;
  margin-bottom: 8px;
}

.detail-section > h3,
.lvb-finish > .detail-section > h4,
.inz-section-head > h3 {
  margin: 0 0 8px;
  color: var(--chalk);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* The row already carries the 8px; the heading inside it must not add a second one. */
.inz-section-head > h3 {
  margin-bottom: 0;
}

/* "Bewerken bij bol" is a link because it navigates, but it stands in a row of buttons and an
   underlined word beside a bordered one reads as a mistake. Only here: elsewhere in the app a
   ghost link is meant to look like a link. */
.inz-section-head > a.ghost {
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  text-decoration: none;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.inz-section-head > a.ghost:hover {
  background: var(--btn-hover);
  border-color: var(--seam-hi);
}

.detail-section > .nested-table {
  padding: 0;
}

/* A section without a table (Weggegeven, and the headless ones that close a folder: the Kaufland
   invoice buttons, the open bol request, what the round skipped): a note and a button row. Both start
   on the heading's own line, not on a panel's gutter -- the section already carries the indent --
   and the button row keeps the field gap to whatever sits above it, the section's own 14px under
   it, so it breathes like the table sections around it. */
.detail-section > .table-note {
  padding: 0;
}

.detail-section > .form-actions {
  padding: var(--gap-field) 0 0;
}

/* The chooser of a folder table, between its heading row and the table itself. It sits in the
   same over-wide cell as the heading, so it sticks to the left and is capped at the width of
   the window rather than of the table -- otherwise its last column of checkboxes is off-screen. */
.detail-section > .sales-columns {
  position: sticky;
  left: var(--folder-indent);
  max-width: 88ch;
  margin-bottom: 12px;
}

/* The campaign folder as bol's campaign page (29 October 2026): the Instellingen facts and the
   inline add forms sit in the same over-wide cell as the chooser, so they stick to the left and
   keep its cap. The facts are the .detail-facts box with two fields in it (the bid field's
   shape); an add form is a field row and its buttons in a box of the same kind. */
.detail-section > .inz-settings,
.detail-section > .inz-add-form {
  position: sticky;
  left: var(--folder-indent);
  max-width: 88ch;
}

.detail-section > .inz-add-form {
  margin-bottom: 12px;
  padding: var(--gap-field);
  background: var(--deck);
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
}

.inz-add-form > .form-row {
  padding: 0;
}

.inz-add-form > .form-actions {
  padding: var(--gap-field) 0 0;
}

.inz-settings dt > label {
  cursor: pointer;
}

/* Uitsluitingen ends on the bin. At the folder's full width that column lands wherever the
   campaign table behind it ends, often off-screen; hugging its three columns keeps the bin
   beside the row it removes. */
.detail-section[data-inz-inner="uitsluitingen"] table {
  width: auto;
}

/* The category dialog's tick boxes keep their own size; the global input rule would stretch
   them, and the focus ring with them, across the cell. */
#inzCategoryRows input[type="checkbox"] {
  width: auto;
}

/* The category name and its path are the one text column; they wrap so the winning bid and
   the bid field stay in view on a phone. */
#inzCategoryRows td:nth-child(2) {
  white-space: normal;
}

/* The filter chips of Uitsluitingen sit in the section head beside its buttons. */
.inz-section-head > .segmented button {
  white-space: nowrap;
}

/* Facts in boxes, one label per value. A name, a street and a country strung together with dots
   read as one sentence; in a grid the eye finds the postcode without reading the rest. */
.detail-facts {
  display: grid;
  /* No hairlines between the facts: a partial last row would leave its grid lines hanging in the
     air, and the labels already separate the values. One box, air inside it. */
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px 20px;
  margin: 0;
  padding: 11px 14px;
  background: var(--deck);
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
}

.detail-facts dt {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.detail-facts dd {
  margin: 3px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.nested-table table {
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  overflow: hidden;
}

.nested-table th {
  position: static;
  background: var(--deck);
}

.inline-loading,
.compact-empty {
  padding: 12px 16px 12px 30px;
  color: var(--chalk);
}

.empty {
  color: var(--chalk);
  padding: 18px;
}

.muted-cell {
  color: var(--chalk-dim);
  font-size: 11.5px;
}

.compact-table table {
  min-width: 640px;
}

/* --- Cargo state chips ---------------------------------------------------- */

.pill,
.status-pill,
.shipment-status {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  background: var(--chalk-wash);
  color: var(--chalk);
  border: 1px solid transparent;
}

.pill {
  background: var(--stowed-wash);
  color: var(--stowed);
}

.status-pill,
.shipment-status {
  background: var(--transit-wash);
  color: var(--transit);
}

.status-pill.done,
.shipment-status.done {
  background: var(--stowed-wash);
  color: var(--stowed);
}

/* Announced in bol's seller account and not yet released: nothing moves yet, so it reads quiet,
   the chalk pair of .status-pill.own, not the transit orange of a shipment on the road. */
.shipment-status.cancelled {
  color: var(--chalk-dim);
  text-decoration: line-through;
}

.shipment-status.announced {
  background: var(--chalk-wash);
  color: var(--chalk);
}

.status-pill.warn {
  background: var(--transit-wash);
  color: var(--transit);
}

/* The third signal on this block: .done and .warn covered stowed and transit, but a campaign
   past its break-even is a shortfall, and that is alarm. Same shape, existing tokens. */
.status-pill.bad {
  background: var(--alarm-wash);
  color: var(--alarm);
}

/* A transfer between our own warehouses. Same shape, chalk instead of transit: it belongs in the
   Onderweg column because that is where the ledger parks it, but it is not supply arriving --
   it is stock we already counted, walking to another room. Reading grey is the whole point. */
.status-pill.own {
  background: var(--chalk-wash);
  color: var(--chalk);
}

/* A pill that leads the number it describes, in a right-aligned column. The same 8px the pace
   chip keeps on LVB and Fulfilment, so the two read as one pattern across the table. */
.status-pill.lead {
  margin-right: 8px;
}

/* Pallets or parcels never reaches bol through the API, so an announced shipment stays flagged
   until the type is set by hand in the seller account. */
/* Chip, link and button stand side by side in one action row, so they share the button's box:
   6px padding + 18.125px line + 1px border. Left to their own heights they sat at 28, 29 and 32
   and the row read as crooked. */
.announcement-chip,
.announcement-open {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  min-height: 32.13px;
  line-height: 18.125px;
}

.announcement-chip {
  padding: 6px 11px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  background: var(--transit-wash);
  color: var(--transit);
}

.announcement-chip.done {
  background: var(--chalk-wash);
  color: var(--chalk);
}

/* Two classes, because the fill note also carries .table-note and that rule sits further down the
   file with the same specificity — a bare .announcement-chip loses its colour to it. */
.announcement-chip.limit {
  background: var(--transit-wash);
  color: var(--transit);
}

.announcement-chip.over {
  background: var(--alarm-wash);
  color: var(--alarm);
}

/* Sits between real buttons, so it has to carry a button's box — a bare underlined link left the
   row ragged. */
.announcement-open {
  margin: 0;
  padding: 6px 9px;
  border: 1px solid var(--transit);
  border-radius: var(--r-control);
  background: transparent;
  color: var(--transit);
  font-size: 12.5px;
  white-space: nowrap;
  text-decoration: none;
}

.announcement-open:hover {
  background: var(--transit-wash);
}

/* Sits between the mail form and the send button, so it needs its own air on both sides: glued
   under the text area it read as part of the form instead of as the thing standing in the way.
   Centred because it is one announcement with two actions under it, not a form row. */
.announcement-gate {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: var(--gutter) 0;
  padding: var(--gap-field) var(--gutter);
  border-radius: var(--r-control);
  background: var(--transit-wash);
  color: var(--transit);
  font-size: 13px;
}

.announcement-gate p {
  margin: 0;
  flex: 1 1 100%;
  text-align: center;
}

.cell-warn {
  background: var(--transit-wash);
  color: var(--transit);
  font-weight: 600;
}

/* Days of cover: figure plus a bar filled against a 60-day horizon. */
.days-pill {
  display: inline-grid;
  gap: 4px;
  min-width: 52px;
  justify-items: stretch;
  text-align: right;
  font-size: 12.5px;
  color: var(--stowed);
}

.days-pill i {
  display: block;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  opacity: 0.55;
}

.days-pill.warn {
  color: var(--transit);
}

.days-pill.danger {
  color: var(--alarm);
}

/* --- Inventory grid: columns tinted by where the goods sit ---------------- */

/* The washes ride on background-IMAGE, never background-color. These classes sit on the th as
   well as the td, and as background-color they REPLACED the opaque header colour the sticky
   `th` rule sets -- measured at 2,4% opacity in the light theme, so the rows scrolled straight
   through the headings. As an image the wash layers over that colour instead of erasing it. */
.inventory-id-col {
  background-image: linear-gradient(rgba(var(--lift-rgb), 0.022) 0 100%);
}

/* Goods in our own hands: Verschillen, Leverancier, Eigen voorraad and 3PL. One wash for the
   four, because they differ in where they stand and not in what they are. */
.inventory-location-col {
  background-image: linear-gradient(rgba(var(--chalk-rgb), 0.05) 0 100%);
}

/* Column washes sit under a whole column, so they run far lighter than a chip. Goods at a
   channel's warehouse -- LVB and Fulfillment -- are the ones that can be sold today. */
.inventory-lvb-col {
  background-image: linear-gradient(rgba(var(--stowed-rgb), 0.075) 0 100%);
}

/* Transit can span several columns, so the wash marks the group, not a rule. */
.inventory-transit-col {
  background-image: linear-gradient(rgba(var(--transit-rgb), 0.05) 0 100%);
}

/* Everything the row adds up to. The deepest wash on the table on purpose: it is the one
   number a reader looks for first, and it sits between the stock it sums and the rates that
   spend it. */
.inventory-total-col {
  background-image: linear-gradient(rgba(var(--lift-rgb), 0.075) 0 100%);
}

/* The two rate columns, Verwacht/dag and Dagen verwacht, as one faint block: the rate that
   leads on every screen. (Until 2026-10-12 Plan/dag and Dagen plan sat between them, bare, so
   the pairs read as a stripe; both now live in the hover cards.) */
.inventory-pace-col {
  background-image: linear-gradient(rgba(var(--lift-rgb), 0.03) 0 100%);
}

/* The LVB figure in the LVB group's own colour, faintly: same subject, other end of the row. */
.inventory-days-col {
  background-image: linear-gradient(rgba(var(--stowed-rgb), 0.035) 0 100%);
}

/* Money, not pieces. */
.inventory-value-col {
  background-image: linear-gradient(rgba(var(--chalk-rgb), 0.028) 0 100%);
}

/* One seam, at the start of a group and nowhere else. */
.inventory-seam {
  border-left: 1px solid var(--seam-hi);
}

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

.form {
  padding-bottom: 14px;
}

.form label {
  display: grid;
  gap: 6px;
  padding: 10px var(--gutter) 0;
  color: var(--chalk);
  font-size: 12px;
}

/* A checkbox with its words on one line; also inside a field row outside a .form (the
   Weggegeven dialog's "Product komt terug"). */
.form .check,
.form-row .check {
  display: flex;
  align-items: center;
  gap: 8px;
}

.form .check input,
.form-row .check input {
  width: auto;
}

/* On the field row's left edge, like the inputs above it (the browser's own margin set it in). */
.form-row .check input {
  margin: 0;
}

.form button {
  margin: 14px var(--gutter) 0;
}

.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 320px));
  gap: var(--gap-field);
  padding: 10px var(--gutter) 0;
}

/* A .form-row is a field row wherever it sits; its labels do not depend on a .form ancestor.
   The reply column on Klantvragen (Prompt, Antwoord) labels its two textareas the same way. */
.form-row label,
.kv-reply label {
  display: grid;
  gap: 6px;
  padding: 0;
  color: var(--chalk);
  font-size: 12px;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px var(--gutter) var(--gutter);
}

.form-actions button {
  margin: 0;
}

.form-note {
  border-top: 1px solid var(--seam);
  margin: 0;
  padding: 12px var(--gutter) var(--gutter);
  color: var(--chalk);
  font-size: 12.5px;
}

.form-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  color: var(--chalk-dim);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  margin: 6px 0 6px;
}

.table-note {
  margin: 0;
  padding: 0 var(--gutter) 12px;
  color: var(--chalk);
  font-size: 12.5px;
}

.panel-head + .table-note {
  padding-top: 12px;
}

/* The same air when the column chooser sits folded between the head and the note (Adverteren):
   a hidden sibling still counts for `+`, so the rule above does not reach the note. */
.sales-columns[hidden] + .table-note {
  padding-top: 12px;
}

/* A note under the table of an open folder keeps its own air. Nothing matches this today: the
   campaign folder lost its notes on 22 September 2026 and it was the only user. Left standing
   because the next folder with a note will want exactly this. */
.detail-section > .nested-table + .table-note {
  padding-top: 12px;
  padding-left: 0;
  padding-right: 0;
}

/* A field row has no bottom padding of its own (rows stack on their top padding), so whatever
   follows one that is not another row — a note, a table, an action row — brings the air. */
.form-row + .table-note,
.shipment-entry-top + .table-note,
.table-wrap + .table-note {
  padding-top: 12px;
}

.settings-subhead + .table-note,
.settings-advanced .nav-group-label + .table-note {
  padding-top: 8px;
}

.form-row + .table-wrap,
.form-row + .line-editor,
.form-row + .panel-actions-inline {
  margin-top: 12px;
}

/* Een pillenbalk gevolgd door een tabel: op Verkoop staat er een tegelstrook achter, die zijn
   eigen marge meebrengt. Direct boven een tabel plakte hij ertegenaan. */
.sales-source + .table-wrap {
  margin-top: 12px;
}

/* Sluit een pillenbalk het paneel af, dan brengt hij zijn eigen onderrand mee -- zoals een
   .table-note dat doet. */
.sales-source:last-child {
  padding-bottom: var(--gutter);
}

/* Een tekstkolom direct achter een getalkolom: het getal staat rechts tegen zijn rand, de tekst
   links tegen de volgende, en de caps-spacing van de kop maakt die 24px optisch krapper dan hij
   is. Alleen op de Financiën-tabellen, waar een bedrag en een toelichting elkaar raken. */
#maandoverzicht td.num + td:not(.num),
#maandoverzicht th.num + th:not(.num),
#leningen td.num + td:not(.num),
#leningen th.num + th:not(.num) {
  padding-left: calc(var(--gutter) * 2);
}

.panel-actions-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 14px var(--gutter) var(--gutter);
  border-top: 1px solid var(--seam);
}

/* "Container: [select]" inside an action row: caption beside the control, on the buttons' line. */
.inline-select {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--chalk);
  font-size: 12px;
}

.inline-select select {
  width: auto;
  min-width: 200px;
}

.panel-actions-inline button {
  margin: 0;
}

.icon-button {
  width: 36px;
  height: 36px;
  padding: 0;
  display: grid;
  place-items: center;
  font-size: 17px;
  color: var(--chalk);
}

.icon-button:hover {
  color: var(--paint);
}

/* --- Line editors --------------------------------------------------------- */

.line-editor {
  padding: 12px 16px 0;
  overflow-x: auto;
  --shipment-cols: minmax(170px, 0.8fr) minmax(260px, 1.3fr) minmax(100px, 0.45fr) minmax(100px, 0.45fr) minmax(100px, 0.45fr) 40px;
}

.line-editor-head,
.purchase-line {
  display: grid;
  grid-template-columns: minmax(260px, 1.4fr) minmax(120px, 0.6fr) minmax(150px, 0.7fr) 40px;
  gap: 8px;
  align-items: center;
  min-width: 620px;
}

.shipment-editor-head,
.shipment-line {
  display: grid;
  grid-template-columns: var(--shipment-cols);
  gap: 8px;
  align-items: center;
  min-width: 860px;
}

.movement-editor-head,
.movement-line {
  display: grid;
  grid-template-columns: minmax(260px, 1fr) minmax(120px, 0.4fr) 40px;
  gap: 8px;
  align-items: center;
  min-width: 460px;
}

.component-editor-head,
.component-line {
  display: grid;
  grid-template-columns: minmax(160px, 1fr) repeat(4, minmax(90px, 0.4fr)) 40px;
  gap: 8px;
  align-items: center;
  min-width: 560px;
}

.component-line {
  margin-bottom: 8px;
}

.component-line button {
  margin: 0;
}

.line-editor-head,
.shipment-editor-head,
.movement-editor-head,
.component-editor-head {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  margin-bottom: 7px;
}

.purchase-line,
.shipment-line,
.movement-line {
  margin-bottom: 8px;
}

.purchase-line button,
.shipment-line button,
.movement-line button {
  margin: 0;
}

.line-editor > button {
  margin: 4px 0 0;
}

/* Rows of form controls line up on their centres, not their tops. */
.editable-line td {
  vertical-align: middle;
}

.editable-line input,
.editable-line select {
  min-width: 120px;
}

.editable-line button {
  padding: 7px 9px;
  white-space: nowrap;
}

/* --- Dashboard ------------------------------------------------------------ */

.dashboard-layout {
  display: grid;
  grid-template-columns: repeat(2, minmax(420px, 1fr));
  gap: 14px;
}

.dashboard-status-block {
  display: grid;
  padding: 6px 0;
}

.panel-head + .dashboard-status-block {
  padding-top: 12px;
}

.status-row {
  display: grid;
  grid-template-columns: minmax(150px, 0.8fr) 1fr;
  gap: var(--gap-field);
  padding: 10px var(--gutter);
  border-bottom: 1px solid var(--seam);
}

.status-row:last-child {
  border-bottom: 0;
}

.status-row span {
  color: var(--chalk);
  font-size: 12.5px;
}

.status-row strong {
  font-weight: 500;
  font-family: var(--font-mono);
  font-size: 12.5px;
}

.status-row.danger strong {
  color: var(--alarm);
}

/* --- Purchase, shipments, costs ------------------------------------------- */

.workflow-stack {
  display: grid;
  gap: var(--gap-panel);
}

.shipment-entry-top {
  display: grid;
  grid-template-columns: minmax(240px, 360px);
  padding: 10px 16px 0;
}

.shipment-entry-top label {
  padding: 0;
}

.shipment-batch-picker {
  padding: 12px 16px 0;
}

/* The picker and the line editor are both painted blocks; without this they touched (0px),
   which the layout test first measured on 2026-10-09 when Zendingen joined the tested views. */
.shipment-batch-picker + .line-editor {
  margin-top: 12px;
}

.batch-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.batch-picker-head span {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.batch-picker-head button {
  margin: 0;
  white-space: nowrap;
}

.batch-dropdown {
  position: relative;
  max-width: 360px;
}

/* Outranks `.form button`, which would otherwise push it 16px out of line. */
.batch-dropdown .batch-dropdown-toggle {
  width: 100%;
  margin: 0;
  text-align: left;
  background: var(--well);
  border-color: var(--seam);
  color: var(--paint);
}

.batch-dropdown-toggle::after {
  content: "▾";
  float: right;
  color: var(--chalk);
}

.batch-choice-list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  display: grid;
  gap: 2px;
  max-height: 260px;
  overflow: auto;
  padding: 6px;
  border: 1px solid var(--seam-hi);
  border-radius: var(--r-panel);
  background: var(--deck-hi);
  box-shadow: var(--shadow);
}

.batch-choice {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 7px 8px;
  border-radius: var(--r-control);
  font-size: 13px;
}

.batch-choice:hover {
  background: rgba(var(--lift-rgb), 0.05);
}

.batch-choice input {
  margin: 0;
  width: auto;
}

.batch-choice small {
  color: var(--chalk);
  font-family: var(--font-mono);
  font-size: 11px;
  white-space: nowrap;
}

/* "Kies uit Cargoplot" in the head of Zending invoeren: the batch dropdown's toggle and list on
   the right of the head, one plain button per shipment instead of a checkbox row. Its own
   wrapper, because the batch dropdown's outside-click handler takes the first .batch-dropdown. */
/* The first cell of the field row on Zending invoeren: its own box, set apart from the fields
   beside it by the accent, because it fills them rather than being one of them. The label
   mirrors `.form-row label` so the row reads as one line of captions. */
.cargoplot-pick {
  position: relative;
  display: grid;
  gap: 6px;
  align-content: start;
}

/* `display: grid` above would otherwise beat the `hidden` attribute and leave an empty cell. */
.cargoplot-pick[hidden] {
  display: none;
}

.cargoplot-pick-label {
  color: var(--chalk);
  font-size: 12px;
}

/* Outranks `.form button`, like the batch toggle. */
.cargoplot-pick .batch-dropdown-toggle {
  width: 100%;
  margin: 0;
  text-align: left;
  background: var(--stowed-wash);
  border-color: var(--stowed);
  color: var(--stowed);
  font-weight: 600;
}

.cargoplot-pick .batch-dropdown-toggle::after {
  margin-left: 8px;
  color: var(--stowed);
}

/* Hangs under the box, as wide as the box and at least 360px; on a phone it keeps the gutter. */
.cargoplot-pick .batch-choice-list {
  right: auto;
  width: max(100%, 360px);
  max-width: calc(100vw - 4 * var(--gutter));
}

.cargoplot-pick .batch-choice {
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  width: 100%;
  margin: 0;
  border-color: transparent;
  background: transparent;
  text-align: left;
}

.cargoplot-pick .batch-choice:hover,
.cargoplot-pick .batch-choice:focus-visible {
  background: var(--btn-hover);
}

.cargoplot-pick .batch-choice small {
  white-space: normal;
}

.shipment-actions,
.transfer-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

.shipment-actions button,
.transfer-actions button,
.transfer-receive-table button,
.announcement-gate button {
  margin: 0;
  white-space: nowrap;
  padding: 6px 9px;
  font-size: 12.5px;
}

/* The 3PL mail button opens the detail's own action row, so it needs the gap under it that the
   table below would otherwise sit flush against. */
.detail-actions {
  margin-bottom: 10px;
}

/* Zending afronden (LVB, 2026-10-15): the dialog's body is one column of three sections, each a
   heading, its content and a button row with the field gap between them. A section in a dialog
   has no folder to indent from, so the open-row indent goes, and a rule separates the steps. The
   heading takes the caps label of a folder heading (.detail-section > h3). Two classes, because
   .mail-form sits further down the file and would put its two columns back. */
.mail-form.lvb-finish {
  grid-template-columns: minmax(0, 1fr);
}

.lvb-finish > .detail-section {
  display: grid;
  gap: var(--gap-field);
  padding: 0;
}

.lvb-finish > .detail-section + .detail-section {
  padding-top: var(--gap-field);
  border-top: 1px solid var(--seam);
}

/* "Laatste pallet aanpassen" is a toggle in the finish window: pressed while the proposal is
   open, the same look as a pressed Tijdlijn tile, so a second click reads as "fold it away". */
.lvb-finish button[aria-pressed="true"] {
  background: var(--deck-hi);
  border-color: var(--seam-hi);
}

.lvb-finish > .detail-section > h4 {
  margin: 0;
}

/* On a phone the four columns of Aantallen bij bol are wider than the dialog; the product name
   is the column that wraps, so the figures keep their line and nothing scrolls sideways. */
.lvb-finish .bol-product-table td:first-child {
  white-space: normal;
}

/* The refusal card inside the bol window: the field gap above, no gutter margins. */
.mail-panel > .bol-inbound-status {
  margin: var(--gap-field) 0 0;
}

/* Annuleren sits alone on the left of the foot; Opslaan keeps the right. */
.modal-actions > .lvb-finish-cancel {
  margin-right: auto;
}

/* The pallet count at bol, inline in the Pallets line. */
.lvb-finish-carriers-field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.lvb-finish-carriers-field input {
  width: 72px;
}

/* The Sturen field in Aantallen: the width of the LVB page's quantity field, the advice in a
   small line under it when it differs. */
.lvb-finish .bol-product-table input[type="number"] {
  width: 100px;
}
.lvb-finish .bol-product-table td > small {
  display: block;
  margin-top: 3px;
  color: var(--chalk);
  font-size: 11px;
}

.lvb-finish .radio-row {
  align-items: center;
}

.lvb-finish > .table-note {
  padding: 0;
  color: var(--transit);
}

.receive-location {
  min-width: 120px;
}

.receive-shipment {
  white-space: nowrap;
  padding: 6px 9px;
}

.shipment-reference {
  min-width: 130px;
  max-width: 190px;
  padding: 6px 9px;
}

.transfer-receive-table {
  display: grid;
  gap: 10px;
}

.transfer-receive-table input {
  width: 120px;
}

.cost-form-row {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  max-width: none;
}

.cost-invoice-lines {
  margin: 10px 0 14px;
  padding: 0 16px;
  overflow-x: auto;
}

.cost-invoice-line {
  display: grid;
  grid-template-columns: minmax(200px, 1fr) minmax(150px, 0.6fr) 32px;
  gap: 12px;
  align-items: end;
  margin-bottom: 4px;
}

.cost-invoice-line label {
  padding: 0;
}

.cost-invoice-line input[name="line_amount_eur"] {
  text-align: right;
}

.cost-invoice-line .remove-line {
  margin: 0;
  padding: 9px;
  width: 32px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cost-invoice-lines > button {
  margin: 6px 0 0 0;
}

.cost-sku-lines {
  margin: 10px 0 14px;
  overflow-x: auto;
}

.cost-sku-head,
.cost-sku-line {
  display: grid;
  grid-template-columns: minmax(150px, 220px) minmax(320px, 1fr) minmax(140px, 180px);
  gap: 10px;
  align-items: center;
  min-width: 720px;
}

.cost-sku-head {
  padding: 0 2px;
}

.cost-sku-line {
  margin-bottom: 8px;
}

.cost-sku-cell {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cost-sku-code {
  grid-column: 1;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 12.5px;
}

.cost-sku-product {
  grid-column: 2;
  color: var(--chalk);
  font-size: 13px;
}

.cost-sku-amount {
  grid-column: 3;
}

.cost-sku-amount input {
  width: 100%;
  min-width: 0;
  text-align: right;
}

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

.report-form-row {
  grid-template-columns: minmax(180px, 240px);
}

.report-metrics {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.snapshot-export-cell {
  display: flex;
  gap: 8px;
  align-items: center;
}

.snapshot-export {
  padding: 6px 10px;
  white-space: nowrap;
  font-size: 12.5px;
}

/* --- LVB ------------------------------------------------------------------ */

.lvb-form-row {
  grid-template-columns: minmax(220px, 320px) minmax(180px, 240px);
}

.lvb-advice-line input[type="number"] {
  width: 100px;
}

/* A product with nothing to top up. Dimmed rather than hidden: the table is the full range now,
   and a greyed row still reads as a product you can decide to send. */
.lvb-no-advice td {
  color: var(--chalk-dim);
}

.lvb-no-advice input[type="number"]:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Brand filter above the advice table. Sits on the panel gutter, like .table-note below it. */
.brand-filter {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  padding: 12px 16px 4px;
}

.brand-filter-label {
  color: var(--chalk-dim);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 11px;
}

.brand-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
}

.brand-option {
  display: flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  font-size: 13px;
  color: var(--paint);
}

.brand-option input[type="checkbox"] {
  flex-shrink: 0;
  width: auto;
  margin: 0;
}

.lvb-lead-badge {
  display: inline-block;
  background: var(--stowed-wash);
  color: var(--stowed);
  border-radius: 999px;
  padding: 2px 9px;
  font-size: 11px;
  font-weight: 500;
}

.remove-manual-row {
  align-self: flex-end;
  margin-bottom: 2px;
  color: var(--chalk);
  font-size: 16px;
  line-height: 1;
  padding: 4px 8px;
}

#lvbTransferResult {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

#lvbTransferResult button {
  margin: 0;
  padding: 6px 10px;
}

/* --- Movements ------------------------------------------------------------ */

.movement-form-row {
  grid-template-columns: repeat(3, minmax(180px, 1fr));
  max-width: none;
}

/* --- Settings ------------------------------------------------------------- */

/* The stack is the form element itself: one run of cards, one save for all of them. Columns
   rather than a grid, because the cards are wildly uneven -- Locaties is 150px and Klantfacturen
   is 1200 -- and a grid locks each pair to the taller of the two, which parked an empty column
   the height of a whole panel next to the short ones. In a column flow the cards fill in. */
.settings-stack {
  column-count: 2;
  column-gap: var(--gap-panel);
}

/* `.form`'s bottom padding belongs to the cards, not to the run around them. */
.settings-stack.form {
  padding-bottom: 0;
}

.settings-stack > .panel {
  break-inside: avoid;
  margin-bottom: var(--gap-panel);
}

/* Two jobs under one name, kept apart: on a label it spans its field row (a textarea, a line of
   free text), on a card in the stack it crosses both columns. A card that crosses also splits the
   run in two, so the cards before and after it balance among themselves. */
.settings-span {
  grid-column: 1 / -1;
}

.settings-stack > .settings-span {
  column-span: all;
  margin-top: var(--gap-panel);
}

.settings-stack > .settings-span:first-child {
  margin-top: 0;
}

/* Two cards that belong side by side at the same height (Voorraadplanning, Inkoopplanning):
   a grid inside a spanner, stretched, so the pair reads as one row. */
.settings-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap-panel);
  align-items: stretch;
}

.settings-stack .panel {
  padding-bottom: var(--gutter);
}

.settings-stack .panel > .table-note:last-child {
  padding-bottom: 0;
}

.settings-form-row {
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  max-width: none;
}

.settings-status-grid {
  display: grid;
  border-top: 1px solid var(--seam);
}

/* Connectors differ wildly in height -- bol carries seven status rows and three buttons, Kaufland
   two rows and one. In a two-column grid the short one leaves a hole the height of the tall one
   beside it, which is what made this panel look half empty. Columns let the blocks flow and the
   browser balance them, so the air is spread instead of parked under Kaufland. */
.settings-columns {
  column-count: 2;
  column-gap: 20px;
  padding: 14px var(--gutter);
}

/* Each block is a card of its own with a labelled head: the name of a connector has to read as
   a heading, not as the first line of the list under it. */
.settings-columns > div {
  break-inside: avoid;
  margin: 0 0 14px;
  padding: 0 13px 13px;
  background: var(--deck);
  border: 1px solid var(--seam);
  border-radius: var(--r-panel);
}

.settings-columns > div > h3:first-child {
  margin: 0 -13px 10px;
  padding: 9px 13px;
  background: var(--deck-hi);
  border-bottom: 1px solid var(--seam);
  border-radius: calc(var(--r-panel) - 1px) calc(var(--r-panel) - 1px) 0 0;
}

/* A second heading in a block (Avado-facturen, Verstuurde alerts) opens a new part of that same
   card: a rule and air, not another head band. */
.settings-columns h3:not(:first-child) {
  margin: 18px 0 8px;
  padding: 14px 0 0;
  background: none;
  border-top: 1px solid var(--seam);
  border-bottom: 0;
  border-radius: 0;
}

/* Inside a padded card the 16px side paddings these carry for full-bleed panels would double up;
   strip them so rows, notes, fields and buttons all start at the card's own edge. */
.settings-columns .status-row,
.settings-columns .table-note,
.settings-columns .form-row {
  padding-left: 0;
  padding-right: 0;
}

/* The head band already closes with its own rule; the grid's top border would double it. */
.settings-columns h3 + .settings-status-grid {
  border-top: 0;
}

.settings-columns .form-actions {
  padding: 14px 0 12px;
}

.settings-columns .form-actions:last-child {
  padding-bottom: 0;
}

.form-row + .settings-status-grid {
  margin-top: 12px;
}

/* --- Transport (Instellingen) --- */

/* Heading and measurement span the column; the two day fields share a row beneath them, at
   the heading's own left edge (.form label's gutter padding would indent them otherwise). */
.transport-family {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--gap-field);
  align-content: start;
}

.transport-family h3,
.transport-family .measure-chip {
  grid-column: 1 / -1;
}

.transport-family label {
  display: grid;
  gap: 6px;
  padding: 0;
  margin: 0 0 10px;
}

/* --- Wanneer de fabriek dicht is (Leveranciers) --- */

.supplier-holidays {
  margin-top: 18px;
}

.supplier-holidays h3 {
  margin-bottom: 4px;
}

.supplier-holidays table {
  margin-top: 8px;
}

/* The day the factory is back is the one you plan with, so it reads as a date, not as prose. */
.supplier-holidays .holiday-reopen {
  font-family: var(--font-mono);
  white-space: nowrap;
}

.supplier-holidays td input,
.fee-editor td input,
.fee-editor td select {
  width: 100%;
}

/* An amount typed into a table stays an amount: the column is right-aligned, so the figure in it
   is too. Its header follows through alignTableHeads(). */
.fee-editor td.num input {
  text-align: right;
}

/* A row marked for deletion is struck through until Opslaan makes it real. */
.holiday-removed td,
.is-removed td {
  opacity: 0.45;
  text-decoration: line-through;
}

.holiday-removed td.num,
.is-removed td.num {
  text-decoration: none;
}

/* The button sits on the row's text baseline unless it is taken out of the line box; blocking it
   lines it up with the inputs beside it (38px tall against the button's 32). */
.holiday-edit-row td.num button,
.fee-edit-row td.num button {
  display: block;
  margin: 3px 0 0 auto;
}

/* Nine short fields in a row. Without a floor per column the browser hands the three date inputs
   everything and squeezes the text fields to nothing -- the first version showed an empty box
   where "Kaufland Plus" should be. Each column gets what its control actually needs and the table
   scrolls inside its .table-wrap, like every other wide one. */
.fee-editor th:nth-child(1), .fee-editor td:nth-child(1) { min-width: 160px; }
.fee-editor th:nth-child(2), .fee-editor td:nth-child(2) { min-width: 118px; }
.fee-editor th:nth-child(3), .fee-editor td:nth-child(3) { min-width: 178px; }
.fee-editor th:nth-child(4), .fee-editor td:nth-child(4) { min-width: 96px; }
.fee-editor th:nth-child(5), .fee-editor td:nth-child(5) { min-width: 104px; }
.fee-editor th:nth-child(6), .fee-editor td:nth-child(6),
.fee-editor th:nth-child(7), .fee-editor td:nth-child(7),
.fee-editor th:nth-child(8), .fee-editor td:nth-child(8) { min-width: 138px; }
.fee-editor th:nth-child(9), .fee-editor td:nth-child(9) { min-width: 120px; }

.holiday-row-actions {
  display: flex;
  justify-content: center;
  margin-top: 10px;
}

/* What is on file, before Bewerken turns it into a form: label above value, same wrapping grid
   as the form it replaces so nothing jumps when you switch. */
.supplier-facts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 12px;
  margin: 0;
  padding: 10px 16px 0;
}

.supplier-facts > div {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.supplier-facts dt {
  font-size: 11.5px;
  color: var(--chalk);
}

.supplier-facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.supplier-facts .wide {
  grid-column: 1 / -1;
}

.supplier-facts .wide dd {
  white-space: pre-wrap;
}

/* The supplier detail closes with its own action bar rather than a table cell's action row:
   centred and set apart, so Opslaan is never glued to the button above it. */
.detail-form-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--seam);
}

/* --- Zendingen op de inkooporder: alleen-lezen doorklikblok --- */

.batch-shipments {
  margin: 16px 0;
  padding: 12px 14px;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
}

.batch-shipments-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 8px;
}

.batch-shipments-head h3 {
  margin: 0;
}

.batch-shipments table {
  width: 100%;
}

.batch-shipments td,
.batch-shipments th {
  padding: 6px 14px 6px 0;
}

/* --- Infostip: uitleg achter een hover in plaats van een lap tekst. Eigen tooltip via
   ::after — de native title-tooltip verscheen te traag of niet, dus deze toont direct. --- */

.info-dot {
  position: relative;
  display: inline-block;
  width: 14px;
  height: 14px;
  line-height: 14px;
  text-align: center;
  border: 1px solid var(--seam);
  border-radius: 50%;
  font-size: 10px;
  font-style: italic;
  color: var(--chalk);
  cursor: pointer;
  vertical-align: 1px;
}

.info-dot::after {
  content: attr(data-tip);
  box-shadow: var(--shadow-tip);
  position: absolute;
  top: calc(100% + 7px);
  left: -6px;
  width: 280px;
  padding: 9px 11px;
  background: var(--well);
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  font-family: var(--font-ui);
  font-style: normal;
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: normal;
  text-transform: none;
  text-align: left;
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
  z-index: 30;
}

.info-dot:hover::after,
.info-dot:focus-visible::after {
  opacity: 1;
}

/* An info-dot whose text comes from INFO_TIPS is shown by #tipLayer, which stays inside the
   window; its own ::after would be an invisible 300px box that still widens the page outside a
   clipping .table-wrap (Direct vrijgeven on LVB, 2026-10-15). */
.info-dot[data-tip-key]::after {
  content: none;
}

/* --- Meetchip: een gemeten waarde als blokje, duidelijk niet-instelbaar --- */

.measure-chip {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 7px 10px;
  margin-bottom: 10px;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
  font-size: 12px;
}

.measure-chip strong {
  font-family: var(--font-mono);
  white-space: nowrap;
}

.measure-chip.idle {
  color: var(--chalk);
}

/* The advanced card: set-once settings behind a fold, styled like every other panel head. */
.settings-advanced summary,
.panel-fold summary {
  list-style: none;
  cursor: pointer;
  padding: 13px 16px;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.settings-advanced summary::-webkit-details-marker,
.panel-fold summary::-webkit-details-marker {
  display: none;
}

.settings-advanced summary::before,
.panel-fold summary::before {
  content: "▸";
  margin-right: 9px;
  color: var(--chalk);
}

.settings-advanced[open] summary::before,
.panel-fold[open] summary::before {
  content: "▾";
}

.settings-advanced[open] summary,
.panel-fold[open] summary {
  border-bottom: 1px solid var(--seam);
}

.settings-subhead {
  margin: 16px var(--gutter) 0;
  padding-top: 14px;
  border-top: 1px solid var(--seam);
}

.settings-advanced .settings-subhead:first-of-type {
  border-top: 0;
  padding-top: 0;
}

/* A group of fields inside a subhead (the routes of EPR verzendverpakking): the caps label of
   the nav, as Klantvragen and Tijdlijn use it inside a page, on the card's gutter, with a
   gutter of air above it so each group reads as its own. Its note follows on the 8px a
   subhead keeps above its note. */
.settings-advanced .nav-group-label {
  margin: 0;
  padding: var(--gutter) var(--gutter) 0;
}

.settings-save {
  padding: 2px 0 6px;
}

.location-name-input {
  min-width: 160px;
}

/* --- Modals --------------------------------------------------------------- */

.modal[hidden] {
  display: none;
}

.modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 24px;
}

.modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--scrim);
}

.modal-panel {
  position: relative;
  width: min(920px, 100%);
  max-height: calc(100vh - 48px);
  overflow: auto;
  overscroll-behavior: contain;
  background: var(--deck);
  border: 1px solid var(--seam-hi);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow);
  padding: 4px var(--gutter) var(--gutter);
}

.modal-panel .panel-head {
  padding: var(--pad-head) 0;
  margin-bottom: 6px;
}

.mail-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(220px, 1fr));
  gap: 12px;
}

/* Direct children only — nested labels (radio rows, window options) lay out flat. */
.mail-form > label {
  display: grid;
  gap: 6px;
  color: var(--chalk);
  font-size: 12px;
}

/* Hidden inputs still count for :nth-child, so match on content instead. */
.mail-form > label:has(textarea),
.mail-form .bol-window-section,
.mail-form .bol-product-section {
  grid-column: 1 / -1;
}

.mail-form textarea {
  resize: vertical;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.5;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

/* Section heading inside the SKU detail panel — keeps product, packaging and carton apart. */
.sku-group {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  margin: 18px 0 8px;
  padding-top: 12px;
  border-top: 1px solid var(--seam);
}

.sku-panel .line-editor {
  padding-left: 0;
  padding-right: 0;
}

/* What bol has on the listing, directly under Mitch's own packaging fields. It belongs INSIDE the
   Verpakking group, so it carries no rule of its own -- a border here read as the start of the next
   group. Sunk into the well instead, the way an expanded row is, so it is visibly a second reading
   of the same thing rather than a footnote. */
.sku-bol-pack {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-field);
  /* .form-row has no bottom padding, so the top margin is what keeps this off the field row. */
  margin: var(--gap-field) var(--gutter) 4px;
  padding: 10px 12px;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
  font-size: 13px;
}

.sku-bol-pack .sku-bol-label {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  display: block;
  margin-bottom: 2px;
}

.sku-bol-pack strong {
  font-family: var(--font-mono);
  font-weight: 500;
}

/* View mode: the fields are locked but must still read as data, not as dead controls. */
.sku-locked input:disabled,
.sku-locked select:disabled {
  background: transparent;
  border-color: var(--seam);
  color: var(--chalk);
  opacity: 1;
  -webkit-text-fill-color: var(--chalk);
}

tr.clickable-row {
  cursor: pointer;
}

tr.clickable-row:hover {
  background: var(--seam);
}

.bol-window-section,
.bol-product-section {
  grid-column: 1 / -1;
}
/* The products, then the pallet block, on the field gap (the label keeps its own margin). */
.bol-product-section {
  display: grid;
  gap: var(--gap-field);
}
.bol-product-section > .form-label {
  margin-bottom: 0;
}

/* Datepick: the app's own calendar (2026-10-16). The toggle is a button drawn as a field, so
   it sits in a .form-row like the input it replaces; the pop is .ec-menu's card with a month
   head, a seven-column grid and an optional foot. A day outside the handed-in dates is
   disabled in chalk-dim: that is the weekend, a holiday and anything past bol's window. */
.datepick-toggle,
.form-row .datepick-toggle {
  width: 100%;
  margin: 0; /* .form button carries the action row's margin; this one sits in a field */
  text-align: left;
  /* The metrics of an input, so it lines up with the field beside it, but a button's face and
     the select's chevron: it opens something, you cannot type in it (Mitch, 2026-10-16). */
  padding: 8px 26px 8px 10px;
  font-size: 13.5px;
  background-image: linear-gradient(45deg, transparent 50%, var(--chalk) 50%),
    linear-gradient(135deg, var(--chalk) 50%, transparent 50%);
  background-position: right 12px center, right 7px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.datepick-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-field);
  flex-wrap: wrap;
  font-size: 13px;
}
.datepick-line.is-refused strong {
  color: var(--alarm);
}

.datepick-alternatives {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: var(--gap-field);
}

.datepick-pop {
  position: fixed;
  z-index: 60; /* above .modal (50): Andere datum opens it from inside the bol window */
  width: 300px;
  max-width: calc(100vw - 2 * var(--gutter));
  padding: var(--gap-field);
  border: 1px solid var(--seam);
  border-radius: var(--r-panel);
  background: var(--deck);
  box-shadow: var(--shadow);
}

.datepick-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 6px;
  font-size: 13px;
  text-transform: capitalize;
}

.datepick-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
}

.datepick-dow {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--chalk-dim);
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding: 6px 0;
}

.datepick-day {
  padding: 0;
  height: 36px;
  border-color: transparent;
  background: transparent;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.datepick-day:disabled,
.datepick-day:disabled:hover {
  color: var(--chalk-dim);
  background: transparent;
  border-color: transparent;
}
.datepick-day.is-today {
  text-decoration: underline;
  text-underline-offset: 3px;
}
.datepick-day.is-chosen {
  background: var(--stowed);
  border-color: var(--stowed);
  color: var(--on-accent);
}

.datepick-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 6px;
}

.bol-product-table {
  width: 100%;
  font-size: 13px;
  border-collapse: collapse;
}

.bol-product-table th,
.bol-product-table td {
  padding: 5px 8px;
  border-bottom: 1px solid var(--seam);
  background: transparent;
  position: static;
}

.radio-row {
  display: flex;
  gap: 16px;
  padding: 6px 0;
}

.radio-row label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: normal;
  padding: 0;
}

.radio-row input {
  width: auto;
}

/* --- Scrollbars ----------------------------------------------------------- */

::-webkit-scrollbar {
  width: 11px;
  height: 11px;
}

::-webkit-scrollbar-thumb {
  background: var(--seam);
  border: 3px solid transparent;
  background-clip: padding-box;
  border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--seam-hi);
  background-clip: padding-box;
}

::-webkit-scrollbar-track {
  background: transparent;
}

[hidden] {
  display: none !important;
}

/* --- Responsive: see the end of the file. The rules live last so they outrank the
   component rules above at equal specificity. ------------------------------ */

@media (max-width: 760px) {
  .mail-form {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* --- Purchase order line capacity ------------------------------------------ */

/* What the order weighs and measures as freight, shown where the lines are edited. Four metrics
   rather than the five the page-level strips use: this one sits inside an already-indented detail
   panel and a fifth column made the numbers cramp. */
.batch-capacity {
  padding: 0 0 12px;
}

.batch-capacity .capacity-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(110px, 1fr));
  gap: 10px;
  margin-bottom: 8px;
}

.batch-capacity .metric {
  padding: 11px 13px;
  background: var(--deck);
  border: 1px solid var(--seam);
  border-radius: var(--r-panel);
}

.batch-capacity .capacity-fill {
  margin: 0;
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 11px;
}

/* Same grid as the order form's line editor, minus the SKU column's width — the detail panel is
   indented and cannot carry the full 620px. */
.batch-line-add {
  display: grid;
  grid-template-columns: minmax(200px, 1.3fr) minmax(90px, 0.5fr) minmax(110px, 0.6fr) auto;
  gap: 8px;
  align-items: center;
  margin-top: 10px;
}

.batch-line-add button {
  margin: 0;
}

/* Save and delete share one cell, so the delete cross keeps the button's height instead of
   collapsing to its glyph. */
.line-actions {
  display: flex;
  gap: 6px;
  align-items: center;
}

.line-actions button {
  margin: 0;
}

/* A product without carton measurements sits outside the volume total, so its warning sits apart
   from the fill chip rather than inside it. */
.batch-capacity .capacity-gap {
  margin: 6px 0 0;
  color: var(--alarm);
  font-family: var(--font-mono);
  font-size: 11px;
}

/* Nine columns where there were six. Without a floor under the numeric ones the carton count and
   the volume ran into each other — "175" and "16,61" read as one number. */
.nested-table .line-cartons,
.nested-table .line-cbm {
  min-width: 62px;
  padding-left: 14px;
}

.nested-table .line-actions {
  white-space: nowrap;
}

/* --- Cost line detail ------------------------------------------------------ */

/* The panel sits in a table cell that is far wider than the viewport, so it carries its own width
   instead of inheriting the settings grid's three fixed columns — those pushed the last fields off
   the right edge where nobody could reach them. */
.invoice-line-fields {
  max-width: 900px;
}

.invoice-line-fields .settings-form-row {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

/* Removing a booked cost moves the stock value, so the button says so rather than hiding behind a
   cross the way a line editor's remove does. */
button.danger {
  border-color: var(--alarm);
  color: var(--alarm);
}

button.danger:hover {
  background: var(--alarm-wash);
}

/* --- Inkooporder indelen --------------------------------------------------- */

/* One column per shipment, so the table grows sideways as an order is split. Inputs stay narrow:
   the numbers are unit counts off a packing list, not currency. */
.allocation-col {
  min-width: 130px;
}

.allocation-input {
  width: 90px;
  text-align: right;
}

#allocationTable tfoot td {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 11px;
  border-top: 1px solid var(--seam);
}

/* Left to allocate reads as neutral at zero, as work outstanding above it, and as an error below —
   more allocated than ordered is a typo, not a state to live with. */
.allocation-rest.open {
  color: var(--transit);
}

.allocation-rest.over {
  color: var(--alarm);
  font-weight: 600;
}

/* --- Verzendvorm op de zending --------------------------------------------- */

.shipment-mode-fields {
  padding: 10px 12px 4px 30px;
  max-width: 900px;
}

/* What the forwarder reports, set apart from the fields we fill in ourselves. */
.shipment-cargoplot {
  margin: 12px 0 0;
  padding: 8px 11px;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--chalk);
}

/* The save button keeps 12px to the Cargoplot box or the note above it. A note already carries
   12px of bottom padding, so after one the row adds nothing of its own. */
.shipment-mode-fields .detail-actions {
  margin-top: 12px;
}

.shipment-mode-fields .table-note + .detail-actions {
  margin-top: 0;
}

.shipment-cargoplot .muted-cell {
  margin-right: 8px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* --- Vulling per container ------------------------------------------------- */

/* All three containers for the current selection, side by side. Clickable, because reading them
   and then hunting for the same name in a dropdown is one step too many. */
.container-fill-overview {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 8px;
  padding: 12px 16px 10px;
}

/* A <button>, so it is reachable by keyboard; styled back to the tile it always was. */
.container-fill {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.container-fill:hover {
  background: none;
  border-color: var(--chalk-dim);
}

/* The one the buttons act on, marked so "Pas aan op container" is never a surprise. */
.container-fill.chosen {
  border-color: var(--paint);
}

.container-fill-name {
  display: block;
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.container-fill strong {
  font-family: var(--font-mono);
  font-size: 19px;
  margin-right: 6px;
}

.container-fill-bar {
  display: block;
  margin-top: 6px;
  height: 3px;
  border-radius: 2px;
  background: var(--seam);
  overflow: hidden;
}

.container-fill-bar i {
  display: block;
  height: 100%;
  background: var(--chalk-dim);
}

.container-fill.good .container-fill-bar i {
  background: var(--stowed);
}

.container-fill.limit .container-fill-bar i {
  background: var(--transit);
}

.container-fill.over .container-fill-bar i {
  background: var(--alarm);
}

.container-fill.over strong {
  color: var(--alarm);
}

/* Part of an order that never made it onto a shipment. Same red as the missing carton sizes: both
   mean the figures above are not telling the whole story. */
.batch-capacity .capacity-unshipped {
  margin: 6px 0 0;
  color: var(--alarm);
  font-family: var(--font-mono);
  font-size: 11px;
}

/* --- Cargoplot op de zending ------------------------------------------------ */

/* The four figures you look up while someone is on the phone — departure, arrival, vessel, box —
   as blocks rather than a run-on sentence. */
.cargoplot-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.cargoplot-status {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--chalk-dim);
}

.cargoplot-blocks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
}

.cargoplot-block {
  padding: 8px 11px;
  background: var(--keel);
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
}

.cargoplot-block span {
  display: block;
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.cargoplot-block strong {
  font-family: var(--font-mono);
  font-size: 14px;
}

.cargoplot-extra {
  margin: 8px 0 0;
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 11px;
}

/* A reference the forwarder does not recognise is usually a typo, so it says so rather than
   staying blank. */
.shipment-cargoplot.not-linked {
  border-style: dashed;
}

/* --- Verkoop -------------------------------------------------------------- */

.sales-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* The channel switch. Bol, Kaufland and Shopify sell the same eight SKUs, so the useful default
   is the sum of them and the split is one click away -- not three separate dashboards. */
.segmented {
  display: inline-flex;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  overflow: hidden;
}

.segmented button {
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--chalk);
  padding: 6px 13px;
  font-size: 12.5px;
}

.segmented button:hover {
  background: rgba(var(--lift-rgb), 0.04);
  color: var(--paint);
}

.segmented button + button {
  border-left: 1px solid var(--seam);
}

.segmented button.active {
  background: var(--deck-hi);
  color: var(--paint);
}

/* A select in a filter bar takes its content width, never the whole row. The base rule is
   `select { width: 100% }` for fields in a form column; in a flex bar that makes every select
   claim a line of its own, which is how the Bestellingen head ended up as a stack of full-width
   boxes beside a narrow search field. The min-width keeps them readable. */
.sales-controls select {
  min-width: 150px;
  width: auto;
}

.sales-custom {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--chalk);
  font-size: 12.5px;
}

/* What the strip below is showing and what it is measured against, as pills rather than a
   sentence. As prose it grew a clause per caveat until it ran wider than the tiles it explains,
   and the tiles are the page. Pills keep it to one line at 1280 and wrap on a phone; the
   explanations moved into the hover card of the tile each one qualifies. */
.sales-source {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 12px var(--gutter) 0;
  margin: 0;
}

.sales-metrics {
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  padding: 0;
  margin: 0;
}

/* A fourth line, for the figure this tile is measured against. Only the two strips that render
   it: `.sales-metrics` is also worn by Opname, Bestellingen and EPR, and widening the shared
   grid gave those a fourth row that was empty but still took its 12px gap -- 12px of dead air
   under every tile on three pages, which no layout rule catches because nothing is painted. */
.metrics-compare {
  grid-template-rows: auto auto auto auto;
}

.metrics-compare > .metric {
  grid-row: span 4;
}

/* The figure the delta above it moved from. Quieter than the delta: the percentage is the
   judgement, this is the thing being judged, and it must not compete with the tile's own number.
   The caps come off -- it is a sentence fragment, not a label, and `.metric span` would otherwise
   shout "T.O.V." under every tile. Two classes deep because `.metric span` is a class plus an
   element and would otherwise win on specificity whatever the source order. */
.metric .metric-prev {
  display: block;
  margin-top: 1px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--chalk-dim);
  font-variant-numeric: tabular-nums;
}

/* The strip sits on the panel gutter through .panel > .metrics; its own padding was a second
   indent, which put the tiles 16px past the note above them. */

/* The comparison against the period before. Without it a number says almost nothing. */
.metric .delta {
  display: block;
  margin-top: 2px;
  /* Keep a footnote short: the caps spacing would push nineteen characters past a tile at 1280,
     and because the strip is a subgrid, a footnote that runs onto a second line lifts every tile
     in it. Two deliberate lines (a <br />) are fine -- they lift the strip evenly. */
  line-height: 1.45;
  letter-spacing: 0.08em;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--chalk-dim);
}

.metric .delta.up { color: var(--stowed); }
.metric .delta.down { color: var(--alarm); }

/* A tile standing on the half-hourly estimate rather than on the invoice. Marked rather than
   hidden: the figure is usable, you just need to know which one you are reading. Only the tiles
   that carry a cost get it -- see COST_TILES in app.js. It used to go on the whole strip from one
   boolean, so revenue and unit counts, which read straight from the orders and never move again,
   were dashed too; a marker that is on everything distinguishes nothing. */
.metric.provisional {
  border-style: dashed;
}

/* A tile whose number is right as far as it goes but is missing a cost component. Louder than
   `provisional`: that one says "the invoice has not landed yet", this one says "a cost is
   missing from this figure". */
.metric.incomplete {
  border-color: var(--alarm);
  border-style: dashed;
}

.delta.warn {
  color: var(--alarm);
}

/* Column picker: a grid of equal cells, so the checkboxes line up in columns instead of
   flowing into a ragged cloud. */
.sales-columns {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px 16px;
  padding: 12px var(--gutter) 14px;
  border-bottom: 1px solid var(--seam);
}

.sales-columns label {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 24px;
  color: var(--chalk);
  font-size: 12.5px;
}

.sales-columns input {
  width: auto;
  flex: none;
  margin: 0;
}

.ghost {
  background: transparent;
  color: var(--chalk);
  font-size: 12.5px;
  padding: 6px 12px;
}

/* --- Advertenties --------------------------------------------------------- */

/* The chart card on Producten, Campagnes and the product detail: the quick selections in a
   segmented control that may wrap (six of them do not fit a phone on one line), the tile strip
   of `.tl-tile` switches, the `.tl-chart` and a legend that repeats each tile's swatch. Inside
   the enlarged copy in the dialog the panel gutter is the dialog's own padding. */
.inz-presets {
  flex-wrap: wrap;
}
.inz-presets-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--gap-field);
  flex-wrap: wrap;
  padding: 12px var(--gutter) 0;
}
.modal-panel .inz-presets-row {
  padding: 0;
}
.mc-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-field);
  padding: 0 var(--gutter) var(--gutter);
  color: var(--chalk);
  font-size: 12px;
}
.mc-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.mc-legend .tl-swatch {
  width: 22px;
  height: 6px;
}
.modal-panel.modal-wide {
  width: 100%;
}
.modal-panel .inz-tiles {
  margin: 12px 0;
}
.modal-panel .tl-chart {
  margin: 0 0 var(--gutter);
}
.modal-panel .mc-legend {
  padding: 0 0 var(--gutter);
}
/* The three glyph buttons on the chart card (tile settings, enlarge, fold) and the sort mark on
   a column head: Marktmentor's glyphs, drawn in the stroke of the rail icons. A head that sorts
   is a button that inherits the head's own type, so the column reads exactly as before with a
   small double chevron after it; the active column shows one chevron in the paint colour. */
.inz-icon {
  display: inline-flex;
  align-items: center;
  padding: 6px 8px;
}
.inz-glyph {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.inz-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}
.inz-sort:hover {
  color: var(--paint);
}
/* In a numeric column the label's right edge is what lines up with the figures under it, so
   the chevron moves to the front there; in a text column it stays after the label. */
th.num .inz-sort {
  flex-direction: row-reverse;
}
.inz-sort-icon {
  width: 12px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--chalk-dim);
}
th[aria-sort] .inz-sort-icon {
  color: var(--paint);
}

/* The targets table in a campaign folder carries a switch and a bid field in its rows, which
   are taller than a line of text; every cell centres on the row so the words sit level with
   them instead of hanging at the top. The bid field keeps the column narrow and its figure on
   the right, under the head, like every other number. */
.inz-targets td {
  /* `.expanded-row td` strips the padding of every cell under a folder, the nested ones
     included; here the cells get the standard 9px 12px back so the switch and the words sit
     under their heads, which do keep theirs. */
  padding: 9px 12px;
  vertical-align: middle;
}
.inz-bid {
  width: 84px;
  padding: 5px 8px;
  text-align: right;
}

/* The switch at the head of a target row, the shape of the theme switch: a pill with a thumb
   that slides right and takes the stowed colour when the target is on. The checkbox stays in
   the page for the keyboard and the screen reader; only its box is hidden. */
.inz-switch {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
  vertical-align: middle;
  cursor: pointer;
}
.inz-switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.inz-switch-thumb {
  position: absolute;
  inset: 0;
  border: 1px solid var(--seam-hi);
  border-radius: 999px;
  background: var(--well);
  transition: background 0.18s ease;
}
.inz-switch-thumb::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 999px;
  background: var(--chalk);
  transition: transform 0.18s ease, background 0.18s ease;
}
.inz-switch input:checked + .inz-switch-thumb {
  background: var(--stowed-wash);
  border-color: var(--stowed);
}
.inz-switch input:checked + .inz-switch-thumb::before {
  background: var(--stowed);
  transform: translateX(16px);
}
.inz-switch input:focus-visible + .inz-switch-thumb {
  outline: 2px solid var(--stowed);
  outline-offset: 2px;
}
.inz-switch input:disabled + .inz-switch-thumb {
  opacity: 0.5;
  cursor: default;
}

/* --- E-mailcampagnes ------------------------------------------------------- */

/* The campaign switch: the .inz-switch copied under the page's own prefix, same tokens and
   values, so the two can drift apart only on purpose. */
.ec-switch {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
  vertical-align: middle;
  cursor: pointer;
}
.ec-switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.ec-switch-thumb {
  position: absolute;
  inset: 0;
  border: 1px solid var(--seam-hi);
  border-radius: 999px;
  background: var(--well);
  transition: background 0.18s ease;
}
.ec-switch-thumb::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 999px;
  background: var(--chalk);
  transition: transform 0.18s ease, background 0.18s ease;
}
.ec-switch input:checked + .ec-switch-thumb {
  background: var(--stowed-wash);
  border-color: var(--stowed);
}
.ec-switch input:checked + .ec-switch-thumb::before {
  background: var(--stowed);
  transform: translateX(16px);
}
.ec-switch input:focus-visible + .ec-switch-thumb {
  outline: 2px solid var(--stowed);
  outline-offset: 2px;
}
.ec-switch input:disabled + .ec-switch-thumb {
  opacity: 0.5;
  cursor: default;
}

/* A pane is not a panel, so its filter bar carries the gutter itself. The search field keeps the
   width it has in a panel head, so the four selects stay on its line. */
[data-ec-pane] > .sales-controls {
  padding: 0 var(--gutter) var(--gap-field);
}
[data-ec-pane] > .sales-controls input[type="search"],
.panel-head > .sales-controls input[type="search"] {
  max-width: 280px;
}

/* Zoek referentie and Filter on one line: the shrinking flex item wrapped the button under the
   field at 1280 (2026-10-15). */
.lvb-history-controls {
  flex-wrap: nowrap;
}

/* Every figure carries a "% van afgeleverd" line, and on one line each the table ran 330px past
   its panel at 1280. The cells wrap instead (the name, the chips, the small lines); a button never
   breaks, and the name keeps room for "Verzoek productreview" on two lines. */
#ecRows > tr:not(.expanded-row) > td {
  white-space: normal;
}
#ecRows > tr:not(.expanded-row) > td button {
  white-space: nowrap;
}
#ecRows > tr:not(.expanded-row) > td:first-child {
  min-width: 180px;
}

/* The subject is the one long string of the Templates table; it breaks rather than pushing the
   last column off the panel. */
#ecTemplateRows td:nth-child(2) {
  white-space: normal;
  min-width: 180px;
}

/* `.expanded-row td` strips the padding of every cell under a folder; the folder's own tables
   get the standard cell padding back so the words sit under their heads (as .inz-targets does). */
#ecRows .nested-table td {
  padding: 9px 12px;
  vertical-align: middle;
}

/* Actief | Gearchiveerd with "Campagne aanmaken" on the same line: the rule under the tabs runs
   under the button too, so the row reads as one tab bar. The tabs keep their own -1px underline
   overlap, now on this row's border. */
.ec-tabbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gap-field);
  margin: 0 0 var(--gap-panel);
  padding: 0 var(--gutter);
  border-bottom: 1px solid var(--seam);
}
.ec-tabbar > .tabs {
  margin: 0;
  padding: 0;
  border-bottom: 0;
}
/* The tabs stand on the rule; the button keeps the filter bar's air above that rule, so it never
   touches it (Mitch, 6 October 2026). */
.ec-tabbar > button {
  margin-bottom: var(--gap-field);
}

/* Gebruikt door: the table on the gutter of the note above it, not on the 30px folder indent. */
#ecUsedByModal .nested-table {
  padding: 0 var(--gutter);
}
/* A long campaign name breaks, so the three columns fit a phone without scrolling sideways. */
#ecUsedByRows td:first-child {
  white-space: normal;
}

/* The A/B sections sit in the panel, not in an open row: the folder indent becomes the gutter. */
#ecAbBody > .detail-section {
  --folder-indent: var(--gutter);
  padding-right: var(--gutter);
}

/* The ⋯ menu. Fixed to the window by app.js under its button, so a .table-wrap never clips it;
   the shape of .batch-choice-list on the panel colour. */
.ec-menu {
  position: fixed;
  z-index: 30;
  display: grid;
  gap: 2px;
  min-width: max-content;
  padding: 6px;
  border: 1px solid var(--seam);
  border-radius: var(--r-panel);
  background: var(--deck);
  box-shadow: var(--shadow);
}
.ec-menu button {
  border-color: transparent;
  background: transparent;
  text-align: left;
}
.ec-menu button:hover,
.ec-menu button:focus-visible {
  background: var(--btn-hover);
}

/* Verschillen inslag on Voorraad: the figure is a pill that opens a card with the open lines and
   their two buttons. The card is .ec-menu copied under its own prefix, same tokens and values,
   so the two drift apart only on purpose; its buttons stay ordinary buttons in .cell-actions. */
.inv-diff-pill {
  cursor: pointer;
}
.inv-diff-pill:hover {
  background: var(--transit-wash);
  border-color: var(--transit);
}
.inv-diff-menu {
  position: fixed;
  z-index: 30;
  display: grid;
  gap: 2px;
  min-width: max-content;
  padding: 6px;
  border: 1px solid var(--seam);
  border-radius: var(--r-panel);
  background: var(--deck);
  box-shadow: var(--shadow);
}

/* Product cell in Productstatistieken: thumbnail, title, EAN. */
.ec-product {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  align-items: center;
  gap: var(--gap-field);
  min-width: 180px;
  white-space: normal;
}
.ec-product img,
.ec-product-thumb {
  display: block;
  width: 40px;
  height: 40px;
  object-fit: cover;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
}
#ecProductsModal .metrics {
  margin: 0 var(--gutter) var(--gap-panel);
}

/* --- Regels: the product picker ---------------------------------------------- */

/* The rule dialog's products as tiles: the .ec-product cell inside a button that is pressed when
   the product is chosen, with the accent rim and wash of the .inz-switch. A grid that fills the
   dialog's width, one column on a phone (the responsive block). */
.rg-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--gap-field);
  margin: var(--gap-field) 0;
}
.rg-tile {
  display: block;
  width: 100%;
  padding: var(--gap-field);
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
  color: var(--paint);
  text-align: left;
}
.rg-tile:hover {
  border-color: var(--seam-hi);
  background: var(--well);
}
.rg-tile[aria-pressed="true"] {
  border-color: var(--stowed);
  background: var(--stowed-wash);
}
.rg-tile .ec-product {
  min-width: 0;
}
#rgForm .sales-controls input[type="search"] {
  max-width: 280px;
}
/* The fields of the rule dialog line up at the bottom: a label is a grid whose spare height
   would otherwise go to the input, so a field with a two-line caption (the Aan and Uit pills)
   got a thinner input than its neighbours (Mitch, 19 October 2026). Packed to the end, every
   input keeps its own height and sits on one line; the caption sits right above it. */
#rgForm .form-row label {
  align-content: end;
}
/* Three target buttons are wider than a phone's dialog: they wrap, like the quick selections. */
#rgTypes {
  flex-wrap: wrap;
}
/* Several target buttons may be on at once, unlike a filter switch, and in the light theme the
   boxed `--deck-hi` of an active segment barely differs from the rest: the chosen ones take the
   accent of the chosen tiles above them, so on and off read apart at a glance. */
#rgTypes button.active {
  background: var(--stowed-wash);
  color: var(--stowed);
}
/* Keep campaign identity beside the start of an expanded target list. */
#rgCampaigns td:first-child {
  width: 1px;
}
#rgCampaignRows td {
  vertical-align: top;
}

/* --- The campaign form (#emailcampagne) ------------------------------------- */

/* One fieldset around the five panels, disabled as a whole while the campaign is active (the
   switch to change that is in the overview). It lays out like the workflow stack it replaces; min-width 0 because a fieldset's default
   min-content width would let a wide table push the page sideways. */
.ec-fields {
  display: grid;
  gap: var(--gap-panel);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* The panels are .panel + .form-row rather than .panel.form: `.form button` gives every button
   a field margin, which would split the template buttons. The
   panel brings the air under its last field row itself. */
.ec-fields > .panel {
  padding-bottom: var(--gutter);
}

/* "Deze campagne is actief" on the panel's gutter; the Status switch below is the way out. */
.ec-locked {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-field);
  padding: var(--gap-field) var(--gutter);
}
.ec-locked > .table-note {
  padding: 0;
  color: var(--paint);
}

/* A field cell holding more than one thing (a switch and its hint, a select and the custom
   number): the label's own 6px between them, the cell starting at the top of its row. */
.ec-field {
  display: grid;
  gap: 6px;
  align-content: start;
}
.ec-field > .table-note,
.ec-choices .table-note {
  padding: 0;
}
/* A field row on this page lines its cells up at the top; stretched, a select beside a taller
   cell grew to the height of that cell. */
#emailcampagne .form-row {
  align-items: start;
}
.ec-field-label {
  color: var(--chalk);
  font-size: 12px;
}
#emailcampagne .form-row .check {
  color: var(--paint);
  font-size: 13px;
}

/* A short number or select with its unit after it: "[ 12 ] dagen na trigger", "[09] : [00]". */
.ec-inline {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--chalk);
  font-size: 12.5px;
}
.ec-inline input {
  width: 6em;
}
.ec-inline select {
  width: auto;
}

/* Inside .check the switch keeps its own box; .check's `input { width: auto }` must not shrink
   the invisible checkbox that lies over it. */
.check .ec-switch {
  flex: none;
}
.check .ec-switch input {
  width: 100%;
}

/* One block per language in E-mail Templates, the second under a rule. */
.ec-lang + .ec-lang {
  margin-top: var(--gutter);
  border-top: 1px solid var(--seam);
}
.ec-lang-head {
  display: flex;
  align-items: center;
  gap: var(--gap-field);
  padding: var(--gap-field) var(--gutter) 0;
}
.ec-lang-head > h3 {
  margin: 0;
}
.ec-lang > .form-row:empty {
  display: none;
}

/* The chosen template: name and subject left, its buttons right -- the box of .detail-facts. */
.ec-cards {
  display: grid;
  gap: var(--gap-field);
  max-width: 760px;
  padding: var(--gap-field) var(--gutter) 0;
}
.ec-cards > div:not(.ec-card) {
  justify-self: start;
}
.ec-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-field);
  padding: var(--gap-field) var(--gap-panel);
  background: var(--deck);
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
}
.ec-card-text {
  display: grid;
  min-width: 0;
  font-size: 13px;
  overflow-wrap: anywhere;
}
.ec-card-label {
  color: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.ec-card .cell-actions {
  align-items: center;
}
.ec-sum-error {
  padding-top: var(--gap-field);
  padding-bottom: 0;
  color: var(--alarm);
}

/* Productselectie: three choices, each a title over its explanation. */
.ec-choices .check {
  align-items: baseline;
}
.ec-choices .check > span {
  display: grid;
}
.ec-picker {
  margin-top: var(--gap-field);
}
.ec-picker > .sales-controls {
  padding: 0 var(--gutter) var(--gap-field);
}
.ec-picker > .sales-controls input[type="search"] {
  max-width: 280px;
}
.ec-picker > .table-note {
  padding-bottom: 0;
}
#ecProductRows tr {
  cursor: pointer;
}
#ecProductRows td,
#ecAttachRows td {
  vertical-align: middle;
}
/* The panel already brings the air under its last block. */
.ec-fields > .panel > .form-actions:last-child {
  padding-bottom: 0;
}
#ecProductRows td:first-child {
  width: 1px;
}

/* The chooser's subject breaks rather than pushing Selecteer out of sight on a phone. */
#ecPickerRows td:first-child {
  white-space: normal;
}

/* Annuleer, Opslaan als concept, Opslaan en activeren: under the last card, on its right edge. */
.ec-form-actions {
  justify-content: flex-end;
  padding: 0;
}

/* --- The template editor (#emailtemplate) ----------------------------------- */

/* Panels of field rows rather than .panel.form, as on the campaign form: the panel brings the
   air under its last block, and a field cell starts at the top of its row. */
#emailtemplate .panel {
  padding-bottom: var(--gutter);
}
#emailtemplate .form-row {
  align-items: start;
}

/* The HTML left, the variables right; one column on a phone. The list takes the height of the
   textarea and scrolls inside it: `contain: size` keeps its thirty-odd buttons from setting the
   row height. */
.ec-editor {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--gap-field);
  padding: 0 var(--gutter);
}
.ec-editor > div {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 6px;
  min-width: 0;
}
#ecTplHtmlPane textarea {
  resize: vertical;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.5;
}
/* Tekst: the eight fields under each other, each a label over its textarea as in a field row,
   the hint under the last one. */
.ec-editor > .ec-tpl-text {
  grid-template-rows: auto;
  align-content: start;
  gap: var(--gap-field);
}
.ec-tpl-fields {
  display: grid;
  gap: var(--gap-field);
}
.ec-tpl-fields label {
  display: grid;
  gap: 6px;
  color: var(--chalk);
  font-size: 12px;
}
.ec-tpl-fields textarea {
  resize: vertical;
}
.ec-tpl-text > .table-note {
  padding: 0;
}
.ec-vars {
  display: grid;
  align-content: start;
  contain: size;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
}

/* One variable: the tag in mono over its Dutch label; a click copies the tag. */
.ec-var {
  display: grid;
  gap: 2px;
  padding: 8px 10px;
  border: 0;
  border-bottom: 1px solid var(--seam);
  border-radius: 0;
  background: transparent;
  text-align: left;
}
.ec-var:last-child {
  border-bottom: 0;
}
.ec-var:hover {
  background: var(--btn-hover);
}
.ec-var:focus-visible {
  outline-offset: -2px;
}
.ec-var code {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--paint);
  overflow-wrap: anywhere;
}
.ec-var span {
  color: var(--chalk);
  font-size: 12px;
}

/* The preview: the subject, then the mail in a sandboxed frame on the card's gutter. Under the
   dark theme the frame keeps the mail's own white page (its colour scheme differs from ours). */
.ec-preview {
  padding: 0 var(--gutter);
}
.ec-preview > .table-note {
  padding-left: 0;
  padding-right: 0;
}
.ec-preview iframe {
  display: block;
  width: 100%;
  height: 900px;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
}

/* The product code is the way into the detail page; it reads as a link, in the accent. */
.inz-product-link {
  color: var(--stowed);
  text-decoration: none;
}
.inz-product-link:hover,
.inz-product-link:focus-visible {
  text-decoration: underline;
}

/* Campaign status, readable at a glance: green is live, grey is paused. */
.chip.channel-bol { background: var(--brand-bol); color: var(--on-brand); }
.chip.channel-kaufland { background: var(--brand-kaufland); color: var(--on-brand); }
.chip.channel-shopify { background: var(--brand-shopify); color: var(--on-brand); }

/* A channel chip inside a tile label: the label is caps mono at 10px, the chip keeps its own
   size and sits on the words' baseline. */
.metric .tile-channels {
  display: flex;
  justify-content: center;
  gap: 4px;
  margin-top: 4px;
}

.metric .tile-channels .chip {
  padding: 1px 7px;
  font-size: 9.5px;
  letter-spacing: 0.06em;
}

.chip {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--chalk-wash);
  color: var(--chalk);
}

.chip.on {
  background: var(--stowed-wash);
  color: var(--stowed);
}

.chip.warn {
  background: var(--alarm-wash);
  color: var(--alarm);
}

/* Two figures that belong together in one pill, a hairline between them (Regels, 27 October
   2026: "23 gematcht | 23 gedaan"). The halves keep the chip's own padding on the inside. */
.chip.chip-split {
  display: inline-flex;
  padding: 0;
}
.chip.chip-split > span {
  padding: 2px 9px;
}
.chip.chip-split > span + span {
  border-left: 1px solid var(--seam-hi);
}

/* ACOS/ROAS judged against the tile above: better than the account average for the same period
   reads green, worse than twice the average reads red. Relative, so it needs no margin threshold
   that would go stale. */
.num-good { color: var(--stowed); }
.num-bad { color: var(--alarm); }

/* --- Pace chips (Voorraad, Inkooporders) ---------------------------------- */

/* The forecast-vs-reality warning beside a daily figure: a red pill to the LEFT of the number
   it contradicts, on the same line. Left of it, not behind it — the numeric columns are
   right-aligned, so a trailing pill would push the figure out of its column. */
.deviation-note {
  display: inline-block;
  margin-right: 8px;
  vertical-align: middle;
  padding: 3px 9px;
  border: 1px solid var(--alarm);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  background: var(--alarm-wash);
  color: var(--alarm);
  white-space: nowrap;
  cursor: pointer;
}

.action-card {
  border-bottom: 1px solid var(--seam);
  padding: 14px 16px;
}

.action-card h3 {
  margin-bottom: 8px;
}

/* Kaufland advertising going stale. Loud enough to notice on a page full of numbers, quiet enough
   that it is not an error -- nothing is broken, a report is simply due. */
.stale-note {
  margin: 11px var(--gutter) 12px;
  padding: 9px 12px;
  border-left: 2px solid var(--alarm);
  border-radius: 3px;
  background: var(--alarm-wash);
  color: var(--alarm);
  font-size: 12.5px;
  line-height: 1.5;
}

/* Same block, warning tone: a stale report is an error, stock waiting for its invoice is a job
   still to do. Colour carries cargo state here as everywhere. */
.stale-note.notice {
  border-left-color: var(--transit);
  background: var(--transit-wash);
  color: var(--transit);
}

/* The two report names, as a list you can tick off rather than a sentence you have to parse. */
.stale-note ul {
  margin: 5px 0 5px;
  padding-left: 18px;
}

.stale-note code {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--paint);
}

/* The one link inside a warning: it stays the warning's own colour and is marked by the underline
   alone. A second colour inside a coloured block reads as a second signal, and the block already
   says what state it is in. */
.stale-note a {
  color: inherit;
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.stale-note a:hover {
  text-decoration-thickness: 2px;
}

/* --- Rankings, Zoektermen & Zoekwoordlijsten (2026-08-30) ------------------ */

/* Underline tabs inside one view — the Marktmentor tab pair. Distinct from .segmented, which is
   a boxed filter switch; these read as page sections. The 16px inset is the house gutter every
   other direct child of a .panel carries (.panel-head, .table-note, .form-actions); without it
   the tabs sit flush against the panel border while the heading above them does not. */
.tabs {
  display: flex;
  /* Six tabs (Bestellingen) do not fit a phone on one line; wrapping keeps every tab reachable. */
  flex-wrap: wrap;
  gap: 18px;
  border-bottom: 1px solid var(--seam);
  margin: 4px 0 14px;
  padding: 0 16px;
}
.tabs button {
  background: none;
  border: none;
  color: var(--chalk);
  font: inherit;
  font-weight: 600;
  /* No side padding: the gap separates the tabs, and the label then starts on the same gutter
     as the heading above it rather than two pixels past it. */
  padding: 8px 0 10px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.tabs button:hover {
  color: var(--paint);
}
.tabs button.active {
  color: var(--paint);
  border-bottom-color: var(--stowed);
}

/* ⓘ markers on column headers; the text itself lives in the floating #tipLayer. */
.info-tip {
  cursor: pointer;
  color: var(--chalk-dim);
  font-style: normal;
  font-size: 11px;
}
#tipLayer {
  position: fixed;
  z-index: 300;
  max-width: 340px;
  background: var(--deck-hi);
  border: 1px solid var(--seam-hi);
  border-radius: var(--r-control);
  padding: 10px 12px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--paint);
  box-shadow: var(--shadow-tip);
  pointer-events: none;
}
#tipLayer .hover-kpi {
  border-collapse: collapse;
}
#tipLayer .hover-kpi th,
#tipLayer .hover-kpi td {
  padding: 2px 10px 2px 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
  /* A long value wraps inside the card instead of running past its edge (Dagen leeg, phone). */
  white-space: normal;
  overflow-wrap: anywhere;
}
#tipLayer .hover-kpi td:last-child {
  padding-right: 0;
}
#tipLayer .hover-kpi th:first-child,
#tipLayer .hover-kpi td:first-child {
  text-align: left;
  color: var(--chalk);
}

/* A profit and loss reads as a sum only when the block being summed is visibly a block. The cost
   lines indent, the subtotal draws a rule over itself to close them, and the tile's own figure
   draws a second one. Without the indent the card was thirteen equal rows and the eye had no way
   to see which of them add up to which. */
#tipLayer .hover-kpi .row-leaf td:first-child {
  padding-left: 10px;
}
#tipLayer .hover-kpi .row-sub td,
#tipLayer .hover-kpi .row-total td {
  border-top: 1px solid var(--seam-hi);
  padding-top: 4px;
}
#tipLayer .hover-kpi .row-total td,
#tipLayer .hover-kpi .row-total td:first-child {
  color: var(--paint);
}

/* Anything that carries a hover card invites the pointer, like a clickable row does: tiles,
   pills, and the bare figures of Verwacht/dag and Dagen verwacht (Mitch, 12 October 2026: a
   hand, not a text cursor). */
[data-pop] {
  cursor: pointer;
}

/* De regel onder de som — waar de kosten gemeten zijn en waar geschat — staat los van de tabel. */
#tipLayer .hover-kpi + .muted {
  display: block;
  margin-top: 8px;
}

/* The on/off switch per term — Marktmentor's toggle, in house colours. */
.switch {
  position: relative;
  display: inline-block;
  width: 30px;
  height: 17px;
  vertical-align: middle;
}
.switch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.switch input:focus-visible + i {
  outline: 2px solid var(--stowed);
  outline-offset: 2px;
}
.switch i {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 999px;
  background: var(--seam-hi);
  transition: background 0.15s;
  pointer-events: none;
}
.switch i::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--paint);
  transition: transform 0.15s;
}
.switch input:checked + i {
  background: var(--stowed);
}
.switch input:checked + i::after {
  transform: translateX(13px);
}
.switch-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--chalk);
  font-size: 12.5px;
}

.rk-expanded {
  padding: 4px 2px 8px;
}
.rk-expanded-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 6px 0 10px;
}
.rk-expanded-bar span {
  display: inline-flex;
  gap: 8px;
}
.muted {
  color: var(--chalk-dim);
}

/* Zoektermen page */
.tw-search-row {
  margin-bottom: 10px;
  padding: 0 16px;
}
.tw-search-row input[type="search"] {
  flex: 1;
  min-width: 280px;
}
.tw-selection {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  padding: 0 16px;
}
/* The volume-history block, laid out like Marktmentor's: the period picker top right, then a
   bordered card with the heading inside it and the bar chart filling the card's width. */
.tw-chart-controls {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  margin: 4px 0 12px;
}
.tw-chart-card {
  border: 1px solid var(--seam);
  border-radius: var(--r-panel);
  background: var(--well);
  padding: 14px 18px 10px;
  margin-bottom: 8px;
}
.tw-chart-card h3 {
  margin: 0 0 12px;
  font-weight: 600;
}
.tw-chart svg {
  display: block;
  width: 100%;
  height: auto;
}
.tw-chart .bar {
  fill: var(--stowed);
  opacity: 0.82;
}
.tw-chart .bar:hover {
  opacity: 1;
}
.tw-chart .chart-grid,
.tl-chart .chart-grid {
  stroke: var(--seam);
  stroke-width: 1;
}
.tw-chart .chart-label,
.tl-chart .chart-label {
  fill: var(--chalk-dim);
  font-size: 11px;
}
/* The Tijdlijn chart: one line chart with a lane strip under it. Series carry no hue of their
   own -- at most three inks (accent for the series the page is about, paint for the volume pair,
   chalk for the cost pair) in a solid or a dashed stroke, and the swatch on each tile repeats
   the pattern. Colour keeps meaning cargo state. */
.tl-chart {
  margin: 0 var(--gutter) var(--gutter);
}
.tl-chart svg {
  display: block;
  width: 100%;
  height: auto;
}
.tl-line {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.tl-line.dashed {
  stroke-dasharray: 6 3;
}
.tl-line.dotted {
  stroke-dasharray: 2 4;
}
/* A dot per day on every line, as on Marktmentor: the same ink as its line, filled, so the
   day a value belongs to is never in doubt. */
.tl-dot {
  stroke: none;
}
.tl-dot.ink-accent {
  fill: var(--stowed);
}
.tl-dot.ink-paint {
  fill: var(--paint);
}
.tl-dot.ink-chalk {
  fill: var(--chalk);
}
.tl-hit {
  cursor: pointer;
}
.ink-accent {
  stroke: var(--stowed);
}
.ink-paint {
  stroke: var(--paint);
}
.ink-chalk {
  stroke: var(--chalk);
}
.tl-bar {
  fill: var(--chalk-dim);
  opacity: 0.7;
}
.tl-band {
  fill: var(--stowed-wash);
}
.tl-hit {
  fill: transparent;
}
.tl-tick {
  stroke: var(--chalk);
  stroke-width: 1.5;
}
.tl-lane-rule {
  stroke: var(--seam);
  stroke-width: 1;
}
.tl-lane-label {
  fill: var(--chalk-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.tl-marker {
  fill: var(--paint);
  stroke: var(--deck);
  stroke-width: 1.5;
  cursor: pointer;
}
.tl-marker:hover,
.tl-marker:focus-visible {
  fill: var(--stowed);
  outline: none;
}
.tl-marker.selected {
  fill: var(--stowed);
}
.tl-marker-count {
  fill: var(--deck);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
}
/* A metric that is also a switch: the same tile as every KPI strip, with a border that says it
   can be pressed and the raised surface when it is. */
.tl-tile {
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--deck);
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.tl-tile:hover {
  border-color: var(--seam-hi);
}
.tl-tile[aria-pressed="true"] {
  background: var(--deck-hi);
  border-color: var(--seam-hi);
}
.tl-tile[disabled] {
  color: var(--chalk-dim);
  cursor: default;
}
.tl-tile .tl-swatch {
  width: 22px;
  height: 6px;
  margin-top: 2px;
}
.tl-tile .tl-bar {
  opacity: 1;
}
/* The event filter: a column per lane, each under its own caps heading, so the four groups read
   as four groups instead of one run of checkboxes. Columns and not a grid, for the reason the
   settings page learnt: a grid row locks to its tallest cell, and Listing holds fifteen types
   against Notities' one -- which parked an empty block beside Advertenties. */
.tl-types {
  column-count: 3;
  column-gap: var(--gutter);
  padding: 12px var(--gutter) 14px;
  border-bottom: 1px solid var(--seam);
}
.tl-type-group {
  break-inside: avoid;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  margin-bottom: var(--gap-field);
}
.tl-type-group .nav-group-label {
  padding: 0 0 2px;
}
.tl-type-group label {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 24px;
  color: var(--chalk);
  font-size: 12.5px;
}
.tl-type-group input {
  width: auto;
  flex: none;
  margin: 0;
}
.relation-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  background: var(--chalk-wash);
  color: var(--chalk);
}
.relation-chip.basisterm {
  background: var(--deck-hi);
  color: var(--paint);
}
.relation-chip.specificatie {
  background: var(--stowed-wash);
  color: var(--stowed);
}
.relation-chip.generalisatie {
  background: var(--transit-wash);
  color: var(--transit);
}
.relation-chip.merk {
  background: var(--alarm-wash);
  color: var(--alarm);
}

/* Zoekwoordlijsten: master-detail */
.kl-layout {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 14px;
  align-items: start;
}

/* Tiles sit directly in the panel, so they carry the gutter themselves — same as
   .sales-metrics. The auto-fill keeps two or three tiles at a readable width instead of
   stretching them across five fixed columns. */
#klTiles {
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  padding: 12px 16px 16px;
  margin: 0;
}

.kl-tree {
  padding: 8px 10px 4px;
}

/* A row is a container, not a button: the delete control is a real <button> and nesting one
   button inside another is invalid HTML that browsers silently unnest. */
.kl-row {
  display: flex;
  align-items: center;
  gap: 2px;
  border-radius: var(--r-control);
}
.kl-row:hover {
  background: var(--deck-hi);
}
.kl-row.active {
  background: var(--deck-hi);
}
.kl-label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  color: var(--paint);
  font: inherit;
  text-align: left;
  padding: 7px 8px;
  cursor: pointer;
}
.kl-label span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kl-list-row .kl-label {
  padding-left: 24px;
  color: var(--chalk);
}
.kl-row.active .kl-label {
  color: var(--paint);
}
.kl-tree small {
  color: var(--chalk-dim);
}

/* The delete affordance. It was a bare 8px × that gave no sign it was a control at all: now a
   real button that stays out of the way until you are on the row, and turns red under the
   cursor so there is no doubt what it does. */
.kl-del {
  width: 26px;
  height: 26px;
  font-size: 15px;
  border: none;
  background: none;
  border-radius: var(--r-control);
  color: transparent;
  flex: 0 0 auto;
}
.kl-row:hover .kl-del,
.kl-del:focus-visible {
  color: var(--chalk);
}
/* Both selectors are needed: ".kl-row:hover .kl-del" outweighs a bare ".kl-del:hover", which
   left the × grey under the cursor even though its background had already turned red. */
.kl-del:hover,
.kl-row:hover .kl-del:hover {
  color: var(--alarm);
  background: var(--alarm-wash);
}

/* Same treatment for the × in the lists and terms tables. */
td .kl-del {
  color: var(--chalk-dim);
}
tbody tr:hover td .kl-del {
  color: var(--chalk);
}
td .kl-del:hover,
tbody tr:hover td .kl-del:hover {
  color: var(--alarm);
  background: var(--alarm-wash);
}

/* The trend pill on the rankings tables, following Marktmentor's own system: green with an up
   arrow when it improved, red with a down arrow when it worsened, and nothing at all when it did
   not move. A bare coloured number was too quiet to read at a glance in a table of numbers. */
.trend {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 7px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.6;
  white-space: nowrap;
}
.trend.up {
  color: var(--stowed);
  background: var(--stowed-wash);
}
.trend.down {
  color: var(--alarm);
  background: var(--alarm-wash);
}

/* Keeping the rankings table inside the viewport. The keyword is the column that grows without
   bound: every other cell holds a number. It wraps instead of forcing the table wider than the
   panel and pushing the whole thing into a horizontal scroll. The product column is the pinned
   .sticky-sku column, code on top and a truncated name under it. */
.rk-term {
  white-space: normal;
  min-width: 180px;
  max-width: 260px;
}

#rankings th,
#rankings td {
  padding-left: 10px;
  padding-right: 10px;
}

/* A term that is switched off still has to be readable -- it is the row you are deciding about --
   so it dims rather than fades away, and its switch stays at full strength. */
tr.rk-disabled td {
  opacity: 0.55;
}
tr.rk-disabled td:last-child {
  opacity: 1;
}

/* Equal row heights. A row grew or shrank with the length of its product name and with whether
   the impressions cell had a share of voice under it, so the table read as ragged even though
   every row holds the same fields. The product cell is always two lines (the code, and the name
   truncated under it) and the impressions cell always one, so every product row is the same
   height as a Verkoop row, on both tabs. The keyword is clamped to two lines. */
/* Whole pixels rather than a ratio: at 13px a 1.35 line-height lands on 17.55, and two of those
   rounded a one-line row and a two-line row a pixel apart. */
.rk-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 18px;
}
/* The ↗ that opens a keyword or a category on bol. It sits in the sentence with the name, so it
   has to stay quiet: chalk-dim like the other secondary text and no underline, teal only when
   the pointer or the keyboard is on it. Both themes come out of the tokens. */
.rk-peek {
  color: var(--chalk-dim);
  text-decoration: none;
}
.rk-peek:hover,
.rk-peek:focus-visible {
  color: var(--stowed);
}

/* Numbers and keywords sit vertically centred in their fixed-height rows; the global default of
   vertical-align: top left them hanging from the top border. */
#rankings td {
  vertical-align: middle;
}

/* The share of voice in front of the impressions figure, its trend pill after it -- the pill on
   the right like every rank column -- all on one line, so a rankings row is no taller than a
   Verkoop or Producten row. */
/* A thin seam between the share of voice and the figure: side by side without it, "SoV: 41,8%
   2.918" read as one run of numbers (Mitch, 2026-09-24). */
.rk-lead {
  display: inline-block;
  margin-right: var(--gap-field);
  padding-right: var(--gap-field);
  border-right: 1px solid var(--seam-hi);
  line-height: 1;
}

/* Scoped to this page on purpose: .nested-table is the shared expandable-row table used by
   suppliers, transfers, purchase orders and invoices too. */
#rankings .nested-table tbody td {
  height: 42px;
}

/* Both header rows stay put while the table scrolls. The outer one always did; the keyword header
   inside an expanded product did not, and that is the row saying which number is which. Three
   things were in the way.

   One: the expanded panel carried its own .table-wrap, so a long keyword list opened a second
   scrollport inside the first -- a scrollbar within a scrollbar, with the inner header pinned to a
   box that was itself scrolling out of view. It now flows at its natural height and the page is
   back to one scrollbar.

   Two: .nested-table th is position:static for the shared expandable tables elsewhere.

   Three: the offset. Both rows are 9px of padding plus a line plus the 1px seam, so stating the
   line-height fixes each at exactly 34px and the keyword header docks flush under the product
   header -- derived, that line was 15,45px and left half a pixel for rows to show through. */
#rankings th {
  line-height: 15px;
}
#rankings .rk-expanded .table-wrap {
  overflow: visible;
  max-height: none;
}
#rankings .nested-table th {
  position: sticky;
  top: 34px;
  z-index: 1;
}
/* The product header wins where the two meet: same z-index would hand it to the keyword header,
   which comes later in the document. */
#rankings > .panel > .table-wrap > table > thead th {
  z-index: 2;
}
/* ...and the pinned product header above both: at 2 it lost to the pinned product cell of the
   rows scrolling under it, which then covered the word Product. */
#rankings > .panel > .table-wrap > table > thead th:first-child {
  z-index: 3;
}

/* The hover card opens centred on whatever carries it, so that element has to hug its own
   content. Left to fill the cell it centred on the column while the figure itself sits
   right-aligned against the edge, which put the card well to the left of the number being
   pointed at. */
#rankings td.num > span[data-pop] {
  display: inline-block;
  text-align: right;
}

/* The logbook's tool (Wijzigingen / Alles; the page size moved to the table foot on 27 October
   2026) sits in the panel body under the note line, so it takes the panel's gutter and the
   note's spacing, like the note itself. */
#rgLogTools {
  padding: 12px var(--gutter) 0;
}
#rgLogTools + .table-wrap {
  margin-top: 12px;
}

/* The table foot under Zoektermen, a rule's logbook and Campagnes (27 October 2026, after bol's
   campaign overview): "Aantal per pagina" with its select and the count left, the page buttons
   right. On a single page only the left group stands. */
.tw-pager {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 16px 14px;
}
.tw-pager:empty {
  display: none;
}
.tw-pager-size {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-field);
  flex-wrap: wrap;
}
.tw-pager-size label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--chalk);
  font-size: 12.5px;
}
.tw-pager-size select {
  width: auto;
}
.tw-pages {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.tw-pages .active {
  border-color: var(--stowed);
  color: var(--stowed);
}
.tw-pages button[disabled] {
  opacity: 0.4;
}

/* The two relation labels that joined later: a spelling variant is near-identical intent, a
   verwant term relates only through an intermediate one. */
.relation-chip.spellingsvariant {
  background: var(--transit-wash);
  color: var(--transit);
}
.relation-chip.verwant {
  background: var(--chalk-wash);
  color: var(--chalk-dim);
}

/* Zoektermen rows centre vertically like the rankings rows do: the term is one line where the
   volume and relation cells are two, and top-alignment left the row reading ragged. */
#searchterms td,
#keywordlists td {
  vertical-align: middle;
}

/* The expand arrow on the two research tables, a notch bigger and brighter than the shared
   .chevron so it reads as a control — deliberately only a notch. Scoped: the purchase and
   invoice tables keep the standard size. */
#rankings .chevron,
#searchterms .chevron {
  font-size: 15px;
  width: 22px;
  color: var(--stowed);
}
#rankings tr.clickable:hover .chevron,
#searchterms tr.clickable:hover .chevron {
  color: var(--paint);
}

/* A rank that was not measured on the newest day carries its age. Muted rather than alarming:
   an old measurement is not an error, it is bol reporting a term only on days it cleared six
   impressions -- but a number that says nothing about its age reads as today's position. */
#rankings .rk-stale sup {
  margin-left: 3px;
  color: var(--chalk);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 500;
}

@media (max-width: 1150px) {
  /* Two 420px dashboard columns plus the rail need 1146px; below that, one column. */
  .dashboard-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Wide product tables (Verkoop, Advertenties, Rankings): the product column stays put while the
   numbers scroll under it, on every screen size. The code on top and the name under it, the name
   truncating rather than widening the column -- unless you drag the column wider yourself (the
   grip below). Child combinators on purpose: Rankings opens a product into a row that spans the
   whole table and holds a table of its own, and neither of those may pin. */
.sticky-sku {
  --sku-w: 108px;
}
.sticky-sku > thead > tr > th:first-child,
.sticky-sku > tfoot > tr > th:first-child,
.sticky-sku > tbody > tr:not(.expanded-row) > td:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--deck);
  padding-left: 10px;
  padding-right: 10px;
  border-right: 1px solid var(--seam);
  /* Shrink to its content. A table narrower than its panel -- a wide screen, a day with few
     products -- hands the spare width to every column, this one included, and then the grip
     could not pull it in to the code: 149px around a 58px SKU at 1600px wide. */
  width: 1px;
}

.sticky-sku > thead > tr > th:first-child,
.sticky-sku > tfoot > tr > th:first-child {
  z-index: 3;
  background: var(--deck-hi);
}

/* The code in the tabular mono the rest of the manifest uses, one size down: a fifteen-character
   SKU then measures about 125px including padding. */
.sticky-sku > tbody > tr:not(.expanded-row) > td:first-child {
  font-family: var(--font-mono);
  font-size: 12px;
}

.sticky-sku > tbody > tr:not(.expanded-row) > td:first-child small {
  display: block;
  max-width: var(--sku-w);
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--chalk);
}

/* A pinned cell has to be opaque or the numbers would show through it while they scroll, so the
   translucent row washes (hover, the open product) are laid over the panel colour instead of
   replacing it -- otherwise the product cell stayed white while the rest of its row lit up. */
.sticky-sku > tbody > tr.clickable:hover > td:first-child {
  background: linear-gradient(rgba(var(--lift-rgb), 0.035), rgba(var(--lift-rgb), 0.035)) var(--deck);
}
.sticky-sku > tbody > tr.selected-row > td:first-child {
  background: linear-gradient(var(--stowed-wash), var(--stowed-wash)) var(--deck);
}

/* The grip on the product header that widens or narrows the pinned column. It sits on the
   column's own seam, shows itself only on hover or focus, and stores the width per table. */
.col-grip {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: calc(var(--gap-field) / 2);
  cursor: col-resize;
  touch-action: none;
}
.col-grip:hover,
.col-grip:focus-visible,
.col-grip.dragging {
  background: var(--seam-hi);
}

/* --- Responsive ------------------------------------------------------------
   Last on purpose. Desktop keeps the sticky rail; below 1050px the rail is a
   drawer behind a hamburger (iPad portrait, phones); below 640px the layout is
   single-column; coarse pointers get 16px inputs so iOS does not zoom. ------ */

.nav-open-button {
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  flex: none;
  align-content: center;
}

.nav-open-button span {
  display: block;
  width: 18px;
  height: 2px;
  margin: 0 auto;
  border-radius: 2px;
  background: currentColor;
}

.nav-open-button span + span {
  margin-top: 4px;
}

.nav-scrim {
  display: none;
}

/* --- Reviews -------------------------------------------------------------- */
/* The fetch note sits under the source pills; a note that follows pills needs its own air. */
#reviews .sales-source + .table-note {
  margin-top: var(--gap-field);
}

/* The three cards under the strip -- NPS, scores, volume -- each a panel with a distribution
   list. Three across on a desktop, one under the other from the tablet width down. */
.rv-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap-panel);
}

/* A distribution row: the band's name, a track, the share with the count. The track is the
   container fill bar; the tone on the row says which side of the line the band sits on. */
.rv-dist {
  display: grid;
  gap: 10px;
  padding: 12px var(--gutter) var(--gutter);
}

.rv-dist-row {
  display: grid;
  grid-template-columns: minmax(84px, auto) minmax(0, 1fr) minmax(76px, auto);
  align-items: center;
  gap: 10px;
  font-size: 12.5px;
}

.rv-dist-row .container-fill-bar {
  margin-top: 0;
  height: 4px;
}

.rv-dist-row.good .container-fill-bar i { background: var(--stowed); }
.rv-dist-row.bad .container-fill-bar i { background: var(--alarm); }

.rv-dist-label {
  color: var(--chalk);
  font-family: var(--font-mono);
  font-size: 12px;
  white-space: nowrap;
}

.rv-dist-value {
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rv-dist-value small {
  color: var(--chalk);
  font-size: 11px;
}

/* "+11" after a SKU: the variants that share this row's reviews. A span, not a <small>: the
   table's small lines are block level and would put it on a line of its own. */
.rv-variants {
  color: var(--chalk);
  font-family: var(--font-mono);
  font-size: 11px;
  white-space: nowrap;
}

/* The stacked star bar in the table's Verdeling cell. Colour keeps meaning cargo state: the two
   top stars in the accent at two strengths, the middle star in chalk, the two bottom stars in
   the alarm at two strengths -- not five hues of their own. */
.rv-top {
  display: inline-block;
  white-space: nowrap;
}

.rv-top small { color: var(--chalk); }

.rv-bar {
  display: flex;
  gap: 1px;
  height: 4px;
  min-width: 96px;
  margin-top: 6px;
  border-radius: 2px;
  background: var(--seam);
  overflow: hidden;
}

.rv-bar i {
  display: block;
  height: 100%;
}

.rv-bar .s5 { background: var(--stowed); }
.rv-bar .s4 { background: rgba(var(--stowed-rgb), 0.5); }
.rv-bar .s3 { background: var(--chalk); }
.rv-bar .s2 { background: rgba(var(--alarm-rgb), 0.5); }
.rv-bar .s1 { background: var(--alarm); }

/* Five stars as text, the empty ones dimmed. */
.rv-stars {
  letter-spacing: 1px;
  white-space: nowrap;
}

.rv-stars-off { color: var(--seam-hi); }

/* The filter row above the table: fields in a .form-row, a minimum and a maximum side by side.
   Not a form, so the unsaved-work guard ignores it. */
.rv-filters {
  padding-bottom: 14px;
  border-bottom: 1px solid var(--seam);
}

/* A field whose control is a group (two inputs) cannot be one label; this is the label's shape
   on a div, so a group name can sit on it instead. */
.rv-field {
  display: grid;
  gap: 6px;
  color: var(--chalk);
  font-size: 12px;
}

.rv-range {
  display: flex;
  align-items: center;
  gap: 6px;
}

.rv-range input { min-width: 0; }
.rv-range span { color: var(--chalk-dim); }

/* Pagina 1 op bol (2026-10-22): the pills come first and the filter sits under them, so the
   controls row lives in the card's own gutter instead of in the head. The note that follows the
   folded filter keeps the air a note after a controls row has. */
.rv-page-controls {
  padding: var(--gap-field) var(--gutter);
}

/* With the filter folded out, the controls row already carries the air above the fields. */
.rv-page-controls + .rv-filters > .form-row {
  padding-top: 0;
}

.rv-filters + .table-note {
  padding-top: var(--gap-field);
}

/* The toolbar above the campaign table (27 October 2026, bol's order): Filter left, the search
   in the middle, Kolommen right, in the card's own gutter like the Pagina 1 controls. The
   filter row under it is the Reviews row; with it folded out the toolbar already carries the
   air above the fields. */
.adc-toolbar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  padding: var(--gap-field) var(--gutter);
}
.adc-toolbar input[type="search"] {
  justify-self: center;
  width: 100%;
  max-width: 320px;
}
.adc-toolbar + .rv-filters > .form-row {
  padding-top: 0;
}
.adc-filter-reset {
  align-self: end;
  justify-self: start;
}

/* The star picker in that filter: a .segmented where several buttons may be .active, like the
   target kinds on Regels. Left-aligned in its field, not stretched to the field's width. */
.rv-field > .segmented {
  justify-self: start;
}

/* The logbook filter on Regels (27 October 2026): four rules, six products and nine decisions
   are wider than a 320px field, so each choice takes the whole row and its buttons wrap; a
   button keeps its words on one line. The chosen ones take the accent of #rgTypes. */
#rgFilters .rv-field {
  grid-column: 1 / -1;
}
#rgFilters .segmented {
  flex-wrap: wrap;
}
#rgFilters .segmented button {
  white-space: nowrap;
}
#rgFilters .segmented button.active {
  background: var(--stowed-wash);
  color: var(--stowed);
}

/* The Pagina 1 table groups the six reviews under their product: the product cell spans its
   rows (the SKU in the mono of the pinned column, the whole name under it, never truncated), and
   a hairline on the first row of each group separates the products. */
.rv-page-table td[rowspan] {
  font-family: var(--font-mono);
  font-size: 12px;
}

.rv-page-table td[rowspan] small {
  display: block;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--chalk);
}

.rv-page-table > tbody > tr.rv-group-first:not(:first-child) > td {
  border-top: 1px solid var(--seam-hi);
}

/* The thumbs in the Pagina 1 table: a small icon before the count, in the table's own ink. */
.rv-glyph {
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -2px;
}


/* --- Klantvragen (prefix kv-): the list on the left, the conversation on the right -------- */
/* The same shape as Zoekwoordlijsten, on the tokens: the panel gap and a collapsible track. */
.kv-layout {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: var(--gap-panel);
  align-items: start;
}

/* A question in the list: the chip and the name on one line, the subject under it, the clock
   pill at the end. The label is a flex row already (.kl-label); the main part may wrap. */
.kv-label {
  align-items: flex-start;
}
.kv-row-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
  white-space: normal;
}
.kv-row-top {
  display: flex;
  align-items: center;
  gap: var(--gap-field);
  min-width: 0;
}
.kv-row-top strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kv-row-main .muted-cell {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kv-label small {
  flex: none;
}

.kv-facts {
  padding: var(--gap-field) var(--gutter) 0;
}
/* bol's translation of the customer's words: one muted line of its own under the facts row. */
.kv-facts + .kv-facts-note {
  padding: var(--gap-field) var(--gutter) 0;
}

/* The conversation: the customer on the left on the deck, us on the right on the accent wash,
   the marketplace's own system lines in chalk. */
.kv-thread {
  display: grid;
  gap: var(--gap-field);
  padding: var(--gap-field) var(--gutter) var(--gutter);
}
.kv-msg {
  max-width: 85%;
  padding: 8px var(--gap-field);
  border: 1px solid var(--seam);
  border-radius: var(--r-panel);
  background: var(--deck-hi);
}
.kv-msg-uit {
  margin-left: auto;
  background: var(--stowed-wash);
  border-color: rgba(var(--stowed-rgb), 0.3);
}
.kv-msg-systeem {
  color: var(--chalk);
  font-style: italic;
}
.kv-msg-head {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--chalk-dim);
  margin-bottom: 4px;
}
.kv-msg-body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.5;
}
/* "nieuw" in a message head and in the list row: the stowed chip, a little away from the text. */
.kv-msg-head .chip,
.kv-label small .chip {
  margin-left: 6px;
}
/* The customer's photos under the message: thumbnails of one height that wrap on a phone, then
   one muted line per photo seen before and per file that is only known by name. */
.kv-photos {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
.kv-photo {
  display: block;
  line-height: 0;
}
.kv-photo img {
  height: 96px;
  width: auto;
  max-width: 100%;
  object-fit: cover;
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--well);
}
.kv-photo:hover img {
  border-color: var(--seam-hi);
}
.kv-files {
  margin-top: 8px;
  font-size: 12.5px;
}
/* The Dutch of what we sent, or of what the customer wrote in another language, under the message: sunken, in chalk. */
.kv-msg-translation {
  margin-top: 8px;
  padding: 8px var(--gap-field);
  border-radius: var(--r-control);
  background: var(--well);
  color: var(--chalk);
  font-size: 12.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The reply box and the order under the conversation sit in the panel on its gutter. */
.kv-reply,
.kv-order {
  padding: 0 var(--gutter) var(--gutter);
}
.kv-reply textarea {
  width: 100%;
  resize: vertical;
}
/* The reply column is one grid on the field gap: Prompt, its button row, the card of the last
   draft, Antwoord with its translation, the notes and the send row. The grid owns the air between them, so a hidden
   block (chips, translation, note) leaves no double gap behind; the blocks themselves carry
   none. */
.kv-reply .detail-section {
  display: grid;
  gap: var(--gap-field);
}
.kv-reply .table-note,
.kv-reply .form-actions {
  padding: 0;
}
#kvListNote {
  padding-top: var(--gap-field);
}
/* The card of the last draft: one sunken block, the procedure, step and certainty in chalk,
   the note for Mitch under it in the warning ink. */
.kv-draft-card {
  display: grid;
  gap: 4px;
  padding: 8px var(--gap-field);
  border-radius: var(--r-control);
  background: var(--well);
}
.kv-draft-line {
  margin: 0;
  color: var(--chalk);
  font-size: 12.5px;
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.kv-draft-note {
  color: var(--transit);
}
/* The translation under the answer is the thread's grey box, directly under the field; its
   caps label sits on a line of its own. */
.kv-reply .kv-translation {
  margin-top: 0;
}
.kv-translation .nav-group-label {
  display: block;
  padding: 0 0 4px;
}
.kv-portal {
  display: inline-flex;
  align-items: center;
  line-height: normal;
  text-decoration: none;
}
.kv-preview {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: inherit;
  line-height: 1.5;
  max-height: 50vh;
  overflow: auto;
  margin: 0 0 var(--gap-field);
  padding: var(--gap-field);
  border: 1px solid var(--seam);
  border-radius: var(--r-control);
  background: var(--deck-hi);
}
.kv-link {
  color: var(--paint);
  font-weight: 600;
}
/* The send window's warning that the answer looks Dutch for a customer who ordered in another
   language: a warning, not an error, so the transit ink. The preview above and the button row
   under it already carry the air. */
.kv-send-language {
  color: var(--transit);
}

/* --- Antwoordenbank (prefix awb-): the panel on Instellingen ------------------------------- */
/* A procedure opens as an expanded row under its line, like an order folder; the editor
   inside gets the panel gutter back that `.expanded-row td` strips, and its fields stack on the
   field gap. The rules and the facts flow in two columns like the settings cards. */
.awb-editor {
  display: grid;
  gap: var(--gap-field);
  padding: var(--gutter);
}
.awb-editor label,
.awb-item {
  display: block;
}
.awb-editor textarea,
.awb-grid textarea {
  width: 100%;
  resize: vertical;
}
.form-row .awb-check {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 6px;
  align-self: end;
  padding-bottom: 8px;
}
.form-row .awb-check input {
  width: auto;
  margin: 0;
}
.awb-grid {
  column-count: 2;
  column-gap: var(--gap-panel);
  padding: 0 var(--gutter);
}
.awb-item {
  break-inside: avoid;
  margin-bottom: var(--gap-field);
}
.awb-item-head {
  display: block;
  margin-bottom: 6px;
}
/* The title cell carries the first line of the procedure; it wraps, so the table never pushes
   its chips and status off the edge. */
.awb-panel tbody > tr:not(.expanded-row) > td:first-child {
  white-space: normal;
  overflow-wrap: anywhere;
}
/* An open procedure is edited, not browsed: the wrapper grows with it instead of scrolling
   inside the card. */
.awb-panel .table-wrap {
  max-height: none;
}

@media (max-width: 1050px) {
  .rv-cards { grid-template-columns: minmax(0, 1fr); }
}

/* On a phone the product cell of Pagina 1 wraps its name instead of pushing the column wide: the
   same 108px the pinned SKU column measures (`--sku-w` on .sticky-sku), declared here because
   this table is not pinned. */
@media (max-width: 640px) {
  .rv-page-table { --sku-w: 108px; }
  .rv-page-table td[rowspan] small {
    white-space: normal;
    max-width: var(--sku-w);
  }
}

@media (max-width: 1050px) {
  body {
    grid-template-columns: minmax(0, 1fr);
  }

  .tl-types {
    column-count: 2;
  }

  main {
    padding: 16px 20px 24px;
  }

  /* The rail becomes a drawer: fixed and off-canvas, slid in by body.nav-open. */
  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 60;
    width: min(300px, 86vw);
    height: 100dvh;
    padding: 20px 12px calc(16px + env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
    transform: translateX(-100%);
    transition: transform 0.2s ease;
  }

  body.nav-open .sidebar {
    transform: none;
    box-shadow: var(--shadow);
  }

  body.nav-open {
    overflow: hidden;
  }

  .nav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 55;
    background: var(--scrim);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }

  body.nav-open .nav-scrim {
    opacity: 1;
    pointer-events: auto;
  }

  .nav-open-button {
    display: grid;
  }

  .nav {
    min-height: 44px;
    padding-top: 10px;
    padding-bottom: 10px;
  }

  .theme-switch {
    grid-template-columns: repeat(2, 38px);
  }

  .theme-switch::before,
  .theme-switch button {
    width: 38px;
    height: 34px;
  }

  html[data-theme="dark"] .theme-switch::before {
    transform: translateX(38px);
  }

  .topbar {
    align-items: center;
    gap: 12px;
  }

  .topbar-actions {
    margin-left: auto;
  }

  /* minmax(0, 1fr) rather than 1fr: a bare 1fr has an auto minimum, so one nowrap table
     would widen the track past the viewport instead of scrolling inside its wrapper. */
  .dashboard-layout,
  .kl-layout,
  .kv-layout,
  .workflow-stack {
    grid-template-columns: minmax(0, 1fr);
  }

  .awb-grid {
    column-count: 1;
  }

  .table-wrap {
    max-height: calc(100dvh - 190px);
  }

  .panel-head {
    align-items: stretch;
    flex-direction: column;
  }

  .panel-head input {
    max-width: none;
  }

  .chain {
    flex-wrap: wrap;
  }

  .chain-node {
    flex: 1 1 140px;
  }

  .chain-arrow {
    display: none;
  }

  .chain-total {
    flex: 1 1 100%;
    margin-left: 0;
    padding: 12px 16px;
    border-left: 0;
    border-top: 1px solid var(--seam);
  }
}

@media (max-width: 640px) {
  .ec-editor {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The campaign toolbar on a phone: Filter and Kolommen on one line, the search under them
     at full width -- in the middle it was cut down to "Zoek op cam". */
  .adc-toolbar {
    grid-template-columns: auto auto;
    justify-content: space-between;
  }
  .adc-toolbar input[type="search"] {
    grid-column: 1 / -1;
    grid-row: 2;
    max-width: none;
  }

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

  .ec-vars {
    contain: none;
  }

  main {
    padding: 12px max(14px, env(safe-area-inset-right)) calc(20px + env(safe-area-inset-bottom)) max(14px, env(safe-area-inset-left));
  }

  /* One column: four groups under each other, so no group waits on the tallest one. */
  .tl-types {
    column-count: 1;
  }

  h1 {
    font-size: 22px;
  }

  .topbar {
    flex-wrap: wrap;
    margin-bottom: 14px;
  }

  .topbar-title {
    flex: 1 1 0;
    min-width: 0;
  }

  .topbar-actions {
    flex: 1 1 100%;
    justify-content: space-between;
    margin-left: 0;
  }

  .status {
    min-width: 0;
  }

  .metrics,
  .report-metrics,
  .sales-metrics,
  #klTiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Two tiles per row leave ~110px for the value; "€ 29.002,11" at 22px is 140px. The value
     scales with its tile (13cqi fits twelve characters of the tabular mono), never past 22px. */
  .metric {
    container-type: inline-size;
    padding: 12px;
  }

  .metric strong {
    font-size: min(22px, 13cqi);
  }

  .chain {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    padding: 0;
  }

  .chain-node {
    border-bottom: 1px solid var(--seam);
  }

  .chain-total {
    grid-column: 1 / -1;
    min-width: 0;
  }

  /* One scroll container is enough on a phone: the page scrolls, the table only sideways. */
  .table-wrap {
    max-height: none;
  }

  .compact-table table {
    min-width: 0;
  }

  .status-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }

  .lvb-form-row,
  .movement-form-row,
  .settings-form-row,
  .transport-family {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Six columns of Aantallen in Zending wijzigen: a narrower Sturen field, half the cell
     padding and the product name under the SKU folded away keep Dozen on screen. */
  .lvb-finish .bol-product-table input[type="number"] {
    width: 64px;
  }
  .lvb-finish .bol-product-table th,
  .lvb-finish .bol-product-table td {
    padding: 5px 4px;
  }
  .lvb-finish .bol-product-table td:first-child .muted-cell {
    display: none;
  }

  /* The two settings layouts flow in columns rather than a grid, so this is what makes them
     single-file on a phone -- a grid-template-columns here would do nothing. */
  .settings-columns,
  .settings-stack {
    column-count: 1;
  }

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

  /* A folder sits in a cell as wide as its table, which on a phone is wider than the screen. The
     tables inside it keep their own sideways scroll; the heading and the fact boxes stay put. */
  .detail-section {
    max-width: calc(100vw - 42px);
    padding-left: 14px;
  }

  /* With the folder capped at the screen, a head with more buttons than fit (Uitsluitingen:
     Kolommen, two buttons and the chips) wraps onto a second line instead of running off it. */
  .inz-section-head {
    width: auto;
    flex-wrap: wrap;
  }

  .modal {
    padding: 0;
  }

  .modal-panel {
    width: 100%;
    min-height: 100dvh;
    max-height: 100dvh;
    border: 0;
    border-radius: 0;
    padding: 4px 14px calc(14px + env(safe-area-inset-bottom));
  }

  .modal-actions {
    flex-wrap: wrap;
  }

  #tipLayer {
    max-width: calc(100vw - 16px);
  }

  .info-dot::after {
    width: min(280px, calc(100vw - 32px));
  }
}

@media (pointer: coarse) {
  /* Below 16px iOS Safari zooms the page on focus. Component rules (.mail-form textarea) set
     their own size at higher specificity, so this one override carries !important. */
  input,
  select,
  textarea {
    font-size: 16px !important;
  }

  /* 44px targets for standalone controls on any touch screen (phone and iPad). Buttons inside
     table rows and line editors keep their size: 44px per row would double every table. */
  .view button:not(.icon-button):not(.kl-del):not(table button):not(.line-editor button):not(.segmented button):not(.tabs button),
  .sync-button,
  .kv-portal,
  .nav {
    min-height: 44px;
  }

  /* Hover does not exist on touch: keep the delete cross visible. */
  .kl-del {
    color: var(--chalk-dim);
  }
}

/* Dayparting: the 7 x 24 week clock of the rule kind Dayparting, in the rule dialog on
   Adverteren → Regels (a page, then a tab, of its own until 22 October 2026). Every cell shows a
   percentage (read-only since 23 October 2026: the pattern comes from the orders, daily) on a wash
   of the accent whose strength is the percentage itself (--dp-a, set by app.js), so the shape of
   the week reads before a single number does; the current hour carries a rim. Spacing is a share
   of the field gap; the cell width is in em so the grid follows the type, not a pixel value. */
.dp-grid th,
.dp-grid td {
  padding: calc(var(--gap-field) / 6);
  text-align: center;
}
.dp-grid td {
  background: rgba(var(--stowed-rgb), var(--dp-a, 0));
}
.dp-grid tbody th {
  text-align: left;
}
.dp-grid td.rg-pct {
  min-width: 2.6em;
  padding: calc(var(--gap-field) / 3) calc(var(--gap-field) / 4);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}
.dp-grid td.rg-now {
  box-shadow: inset 0 0 0 1px var(--stowed);
}
