/* public/css/app.css
   Design tokens first, then layout, then components. Everything is driven by custom
   properties so dark mode is one media query rather than a parallel stylesheet. */

:root {
  --bg:            #f6f7f9;
  --surface:       #ffffff;
  --surface-2:     #fbfbfc;
  --surface-3:     #f0f1f4;
  --border:        #e3e5ea;
  --border-strong: #c9ccd4;

  --text:          #16181d;
  --text-2:        #5b6070;
  --text-3:        #8a8f9e;

  --accent:        #2f5fe0;
  --accent-hover:  #2a53c6;
  --accent-soft:   #eaf0fe;
  --accent-border: #c3d3fb;

  --ok:            #17794a;
  --ok-soft:       #e6f4ec;
  --warn:          #92600a;
  --warn-soft:     #fdf3e0;
  --danger:        #b4231f;
  --danger-soft:   #fdeceb;

  --radius:    8px;
  --radius-sm: 6px;
  --radius-lg: 12px;

  --shadow-sm: 0 1px 2px rgba(16, 18, 24, 0.05);
  --shadow:    0 2px 6px rgba(16, 18, 24, 0.07), 0 1px 2px rgba(16, 18, 24, 0.05);
  --shadow-lg: 0 12px 32px rgba(16, 18, 24, 0.14), 0 2px 8px rgba(16, 18, 24, 0.07);

  --sidebar-w:  248px;
  --topbar-h:   56px;

  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:            #0f1114;
    --surface:       #171a1f;
    --surface-2:     #1c1f25;
    --surface-3:     #23272e;
    --border:        #2a2e36;
    --border-strong: #3a3f49;

    --text:          #e8eaef;
    --text-2:        #a2a8b6;
    --text-3:        #737a89;

    --accent:        #5b87f5;
    --accent-hover:  #6f96f7;
    --accent-soft:   #1a2540;
    --accent-border: #2f4478;

    --ok:            #4fbe86;
    --ok-soft:       #14291f;
    --warn:          #d9a544;
    --warn-soft:     #2b2213;
    --danger:        #f0736e;
    --danger-soft:   #2e1817;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
    --shadow:    0 2px 6px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
  }
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

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

/* ── App shell ───────────────────────────────────────────────────────────── */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: 100vh;
}
.app.nav-collapsed { --sidebar-w: 64px; }

/* ── Sidebar ─────────────────────────────────────────────────────────────── */

.sidebar {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--border);
  overflow: hidden;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  height: var(--topbar-h);
  padding: 0 16px;
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}
.brand-mark {
  width: 26px; height: 26px;
  flex: 0 0 26px;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: #fff;
  display: grid; place-items: center;
  font-weight: 700; font-size: 13px;
  letter-spacing: -0.02em;
}
.brand-name {
  font-weight: 650;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
}
.nav-collapsed .brand-name { display: none; }

.nav {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 12px 8px 16px;
}
.nav-group + .nav-group { margin-top: 18px; }
.nav-label {
  padding: 0 10px 6px;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}
.nav-collapsed .nav-label {
  overflow: hidden;
  text-indent: -999px;
  padding-bottom: 2px;
}
.nav-collapsed .nav-label::after {
  content: "";
  display: block;
  height: 1px;
  background: var(--border);
  text-indent: 0;
  margin: 4px 6px;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--text-2);
  font-weight: 500;
  cursor: pointer;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  font-size: 14px;
  font-family: inherit;
  white-space: nowrap;
}
.nav-item:hover { background: var(--surface-3); color: var(--text); text-decoration: none; }
.nav-item[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}
.nav-item svg { flex: 0 0 17px; width: 17px; height: 17px; }
.nav-item-text { overflow: hidden; text-overflow: ellipsis; }
.nav-collapsed .nav-item-text { display: none; }
.nav-item[disabled] { opacity: 0.4; cursor: not-allowed; }
.nav-item[disabled]:hover { background: none; color: var(--text-2); }

/* "Actie nodig": een teller rechts in het menu-item; ingeklapt een stip op het icoon. */
.nav-item { position: relative; }
.nav-attn {
  margin-left: auto;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--warn-soft);
  color: var(--warn);
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  flex: 0 0 auto;
}
.nav-attn-danger { background: var(--danger-soft); color: var(--danger); }
.nav-collapsed .nav-attn {
  position: absolute;
  top: 5px;
  left: 22px;
  min-width: 8px;
  width: 8px;
  height: 8px;
  padding: 0;
  font-size: 0;
  background: var(--warn);
}
.nav-collapsed .nav-attn-danger { background: var(--danger); }

.nav-foot {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  padding: 8px;
}
.user-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.avatar {
  width: 28px; height: 28px;
  flex: 0 0 28px;
  border-radius: 50%;
  background: var(--surface-3);
  border: 1px solid var(--border);
  display: grid; place-items: center;
  font-size: 11px; font-weight: 650;
  color: var(--text-2);
}
.user-meta { min-width: 0; }
.user-name {
  font-weight: 550; font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.user-role { font-size: 11px; color: var(--text-3); }
.nav-collapsed .user-meta { display: none; }

/* ── Main ────────────────────────────────────────────────────────────────── */

.main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}

.topbar {
  height: var(--topbar-h);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 20px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.crumbs {
  display: flex; align-items: center; gap: 7px;
  font-size: 13px; color: var(--text-3);
  min-width: 0;
}
.crumbs .sep { opacity: 0.5; }
.crumbs .cur { color: var(--text); font-weight: 600; }
.topbar-spacer { flex: 1 1 auto; }

.icon-btn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: 1px solid transparent;
  background: none;
  border-radius: var(--radius-sm);
  color: var(--text-2);
  cursor: pointer;
}
.icon-btn:hover { background: var(--surface-3); color: var(--text); }

.view {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 24px 24px 64px;
}
.view-inner { max-width: 1320px; margin: 0 auto; }

.page-head {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.page-title {
  margin: 0;
  font-size: 21px;
  font-weight: 650;
  letter-spacing: -0.02em;
}
.page-sub { margin: 3px 0 0; color: var(--text-2); font-size: 13px; }
.page-actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }

/* ── Buttons ─────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  cursor: pointer;
  white-space: nowrap;
}
/* Een download is een LINK en geen knop — de browser moet het bestand ophalen, niet wij.
   Maar hij hoort er wel uit te zien als de knoppen ernaast, dus zonder onderstreping en met
   dezelfde uitlijning. */
a.btn { text-decoration: none; }
.btn:hover:not(:disabled) { background: var(--surface-3); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn svg { width: 15px; height: 15px; }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.btn-primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }

.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover:not(:disabled) { filter: brightness(1.08); }

.btn-ghost { border-color: transparent; background: none; }
.btn-ghost:hover:not(:disabled) { background: var(--surface-3); }

.btn-sm { height: 26px; padding: 0 9px; font-size: 12px; }
.btn-sm svg { width: 13px; height: 13px; }

/* ── Cards ───────────────────────────────────────────────────────────────── */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.card-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--border);
}
.card-title { font-weight: 620; font-size: 14px; }
.card-sub { font-size: 12px; color: var(--text-3); }
.card-head .spacer { flex: 1 1 auto; }
.card-body { padding: 16px; }
.card-body.tight { padding: 0; }

.grid { display: grid; gap: 16px; }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }

.stat { padding: 15px 16px; }
.stat-label {
  font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-3);
}
.stat-value {
  margin-top: 6px;
  font-size: 25px; font-weight: 650;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
}
.stat-hint { margin-top: 2px; font-size: 12px; color: var(--text-2); }

/* ── Tables ──────────────────────────────────────────────────────────────── */

.table-wrap { overflow-x: auto; }
table.tbl {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13px;
}
table.tbl th {
  position: sticky; top: 0; z-index: 1;
  text-align: left;
  padding: 9px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: 11px; font-weight: 650;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}
/* Een kop boven een getallenkolom hoort rechts te staan, net als de getallen eronder.
   `.num` alleen verliest van `table.tbl th` op specificiteit, dus stond elke kop links
   terwijl de waarden rechts stonden — en dan schuift "44" onder de kop van de VOLGENDE
   kolom. Op de Gamma-koppelpagina, met twee getallenkolommen naast elkaar, was daardoor
   niet te zien welk aantal bij "nu op contract" hoorde en welk bij "volgens Gamma". */
table.tbl th.num { text-align: right; }
table.tbl td {
  padding: 9px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
table.tbl tbody tr:last-child td { border-bottom: none; }
table.tbl tbody tr:hover { background: var(--surface-2); }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); font-size: 12px; }
.muted { color: var(--text-3); }
.nowrap { white-space: nowrap; }
.w-full { width: 100%; }

/* ── Badges ──────────────────────────────────────────────────────────────── */

.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 7px;
  border-radius: 20px;
  font-size: 11px; font-weight: 600;
  border: 1px solid transparent;
  white-space: nowrap;
}
.badge-neutral { background: var(--surface-3); color: var(--text-2); border-color: var(--border); }
.badge-ok      { background: var(--ok-soft); color: var(--ok); }
.badge-warn    { background: var(--warn-soft); color: var(--warn); }
.badge-danger  { background: var(--danger-soft); color: var(--danger); }
.badge-accent  { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-border); }

.delta-up   { color: var(--ok); font-weight: 600; font-variant-numeric: tabular-nums; }
.delta-down { color: var(--danger); font-weight: 600; font-variant-numeric: tabular-nums; }
.delta-zero { color: var(--text-3); font-variant-numeric: tabular-nums; }

/* ── Forms ───────────────────────────────────────────────────────────────── */

.field { display: flex; flex-direction: column; gap: 5px; }
.field label { font-size: 12px; font-weight: 600; color: var(--text-2); }
.field .hint { font-size: 11px; color: var(--text-3); }

/* ── formuliervelden ────────────────────────────────────────────────────────
   De selector stond op input[type="text"] en dat is een val: een <input> ZONDER
   type-attribuut is wel degelijk een tekstveld, maar matcht die selector niet. Elf velden
   verspreid over vijf pagina's kregen daardoor helemaal geen styling — geen rand, geen
   hoogte, geen focusring — terwijl het veld ernaast er wel goed uitzag. Uitsluiten wat
   GEEN tekstveld is werkt wel, en blijft werken als er ooit een type bijkomt. */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="color"]),
select,
textarea {
  height: 34px;
  padding: 0 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  width: 100%;
  /* Iets rustiger dan een harde omslag bij focus of hover. */
  transition: border-color 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
  /* Safari geeft velden anders een eigen achtergrond en afronding. */
  -webkit-appearance: none;
  appearance: none;
}

textarea { height: auto; min-height: 72px; padding: 8px 11px; resize: vertical; line-height: 1.5; }

/* Een select is geen tekstveld: zonder eigen pijl is niet te zien dat je kunt kiezen.
   Als data-URI zodat de strikte CSP niets hoeft toe te laten, en in currentColor zodat hij
   in beide thema's meekleurt. */
select {
  padding-right: 30px;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23888f9e' stroke-width='1.6' stroke-linecap='round'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 12px;
  cursor: pointer;
}

input:hover:not(:disabled):not([readonly]),
select:hover:not(:disabled),
textarea:hover:not(:disabled):not([readonly]) {
  border-color: var(--text-3);
}

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Placeholder mag niet als ingevulde waarde lezen — dat is precies het verschil tussen
   "hier staat niets" en "hier staat iets wat je niet hoeft te veranderen". */
input::placeholder, textarea::placeholder { color: var(--text-3); opacity: 1; }

/* Uitgezet en alleen-lezen zijn NIET hetzelfde en horen er ook niet hetzelfde uit te zien:
   alleen-lezen toont een echte waarde die je niet mag wijzigen (een inkoopprijs die van de
   factuur komt), uitgezet is een veld dat nu niet meedoet. */
input[readonly], textarea[readonly] {
  background: var(--surface-3);
  color: var(--text-2);
  cursor: default;
}
input:disabled, select:disabled, textarea:disabled {
  background: var(--surface-3);
  color: var(--text-3);
  cursor: not-allowed;
  opacity: 0.75;
}

/* Zoekvelden: het kruisje van de browser is in donkere thema's vaak onzichtbaar. */
input[type="search"] { -webkit-appearance: none; }
input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  height: 12px;
  width: 12px;
  cursor: pointer;
  background: currentColor;
  opacity: 0.4;
  mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 2l8 8M10 2l-8 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
input[type="search"]::-webkit-search-cancel-button:hover { opacity: 0.8; }

/* Bedragen horen rechts uitgelijnd en in cijfers van gelijke breedte: zo staan de komma's
   onder elkaar en zie je in één oogopslag welk bedrag groter is. */
input.num-input { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--mono); }
input.money-input { text-align: right; font-variant-numeric: tabular-nums; }
input.inline-edit { height: 27px; max-width: 120px; }

/* Een label hoort bij zijn veld te horen, niet ernaast te zweven. */
label { color: var(--text-2); }
label.field-label {
  display: block;
  margin: 12px 0 5px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-2);
}
/* De uitleg ONDER een veld: dichter bij het veld dan bij het volgende label, anders lijkt
   hij bij het verkeerde veld te horen. */
.field-hint { margin: 5px 0 0; font-size: 12px; color: var(--text-3); line-height: 1.45; }

.toolbar {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.toolbar .grow { flex: 1 1 auto; min-width: 160px; }

.switch { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font-size: 13px; }
.switch input { width: auto; height: auto; margin: 0; accent-color: var(--accent); }

/* ── Drawer ──────────────────────────────────────────────────────────────── */

.scrim {
  position: fixed; inset: 0;
  background: rgba(10, 12, 16, 0.42);
  z-index: 40;
  animation: fade 0.13s ease;
}
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: min(760px, 100%);
  background: var(--bg);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  z-index: 41;
  display: flex; flex-direction: column;
  animation: slide 0.16s ease;
}
.drawer-head {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.drawer-title { font-weight: 650; font-size: 15px; letter-spacing: -0.01em; }
.drawer-body { flex: 1 1 auto; overflow-y: auto; padding: 18px; }
.drawer-foot {
  flex: 0 0 auto;
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

@keyframes fade  { from { opacity: 0; } }
@keyframes slide { from { transform: translateX(14px); opacity: 0; } }

/* ── Feedback ────────────────────────────────────────────────────────────── */

.toasts {
  position: fixed; bottom: 18px; right: 18px;
  z-index: 60;
  display: flex; flex-direction: column; gap: 8px;
  max-width: 380px;
}
.toast {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 11px 13px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--text-3);
  box-shadow: var(--shadow-lg);
  font-size: 13px;
  animation: slide 0.15s ease;
}
.toast-ok    { border-left-color: var(--ok); }
.toast-warn  { border-left-color: var(--warn); }
.toast-error { border-left-color: var(--danger); }

.notice {
  display: flex; gap: 10px;
  padding: 11px 13px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  border: 1px solid var(--border);
  background: var(--surface-2);
}
.notice-warn  { background: var(--warn-soft); border-color: transparent; color: var(--warn); }
.notice-error { background: var(--danger-soft); border-color: transparent; color: var(--danger); }
.notice-info  { background: var(--accent-soft); border-color: var(--accent-border); color: var(--accent); }
.notice strong { font-weight: 650; }

.empty {
  padding: 44px 20px;
  text-align: center;
  color: var(--text-3);
}
.empty-title { font-weight: 600; color: var(--text-2); margin-bottom: 4px; }

.skeleton {
  height: 11px;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 50%, var(--surface-3) 75%);
  background-size: 220% 100%;
  animation: shimmer 1.15s linear infinite;
}
@keyframes shimmer { to { background-position: -220% 0; } }

.spinner {
  width: 15px; height: 15px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.65s linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

.loading-bar {
  position: fixed; top: 0; left: 0; right: 0;
  height: 2px; z-index: 70;
  background: var(--accent);
  transform-origin: left;
  animation: grow 1.4s ease-out forwards;
}
@keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(0.92); } }

/* ── Login ───────────────────────────────────────────────────────────────── */

.login-wrap { display: grid; place-items: center; height: 100vh; padding: 20px; }
.login-card {
  width: 100%; max-width: 380px;
  padding: 30px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.login-card .brand-mark { margin: 0 auto 16px; width: 40px; height: 40px; font-size: 17px; }
.login-card h1 { font-size: 18px; margin: 0 0 6px; letter-spacing: -0.02em; }
.login-card p { color: var(--text-2); margin: 0 0 22px; font-size: 13px; }

/* ── Responsive ──────────────────────────────────────────────────────────── */

@media (max-width: 860px) {
  .app { grid-template-columns: 1fr; }
  .sidebar {
    position: fixed; inset: 0 auto 0 0;
    width: var(--sidebar-w); z-index: 50;
    transform: translateX(-100%);
    transition: transform 0.16s ease;
    box-shadow: var(--shadow-lg);
  }
  .app.nav-open .sidebar { transform: none; }
  .app.nav-collapsed { --sidebar-w: 248px; }
  .view { padding: 16px 14px 56px; }
  .nav-toggle-mobile { display: grid !important; }
}
.nav-toggle-mobile { display: none; }

/* ── Utilities ──────────────────────────────────────────────────────────────
   The Content-Security-Policy sets style-src 'self' with no 'unsafe-inline', so
   style="..." attributes are BLOCKED by the browser — silently, as a console warning,
   with the declaration simply not applied. Page modules build HTML as strings, so every
   inline style they emitted was being dropped and the layout quietly degraded.

   These classes cover the spacing, sizing and text patterns those pages actually used.
   Anything genuinely dynamic (a computed bar width) must be set through the CSSOM in JS
   — el.style.setProperty() is not governed by style-src — never as an attribute.
   ------------------------------------------------------------------------- */

/* vertical spacers, used as <div class="sp-16"></div> between blocks */
.sp-4  { height: 4px; }
.sp-6  { height: 6px; }
.sp-8  { height: 8px; }
.sp-12 { height: 12px; }
.sp-16 { height: 16px; }
/* CSP-safe replacements for the last inline styles in app.js. style="..." attributes are
   blocked by the Content-Security-Policy (style-src 'self'), so these live here. */
.dim { opacity: .7; }
.notice-block { margin-bottom: 16px; text-align: left; }
.btn-full { width: 100%; justify-content: center; }
.sp-20 { height: 20px; }

.fs-11 { font-size: 11px; }
.fs-12 { font-size: 12px; }
.fs-13 { font-size: 13px; }
.fw-550 { font-weight: 550; }
.fw-600 { font-weight: 600; }

.mt-2 { margin-top: 2px; }
.mt-3 { margin-top: 3px; }
.mt-6 { margin-top: 6px; }
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mb-4 { margin-bottom: 4px; }
.mb-8 { margin-bottom: 8px; }
.mb-16 { margin-bottom: 16px; }
.my-4 { margin: 4px 0; }
.my-12 { margin: 12px 0; }
.m-0 { margin: 0; }

.pa-8 { padding: 8px; }
.pv-6 { padding: 6px 0; }

.mw-180 { min-width: 180px; }
.mw-200 { min-width: 200px; }
.mw-220 { min-width: 220px; }
.mw-230 { min-width: 230px; }
.mw-240 { min-width: 240px; }
.mw-260 { min-width: 260px; }
.maxw-180 { max-width: 180px; }
.maxw-340 { max-width: 340px; }
.w-34 { width: 34px; }
.w-60 { width: 60px; }
.w-90 { width: 90px; }
.w-100 { width: 100px; }
.w-110 { width: 110px; }
.w-150 { width: 150px; }
.w-60pct { width: 60%; }

.clickable { cursor: pointer; }
.text-danger { color: var(--danger); }

/* scrollable panes */
.scroll-220 { max-height: 220px; overflow: auto; }
.scroll-320 { max-height: 320px; overflow: auto; }
.scroll-360 { max-height: 360px; overflow: auto; }
.scroll-420 { max-height: 420px; overflow: auto; }

/* raw JSON / preformatted output */
.jsonbox {
  max-height: 320px;
  overflow: auto;
  font-size: 12px;
  white-space: pre-wrap;
  margin: 0;
}
.jsonbox-lg { max-height: 420px; }
.jsonbox-mt { margin: 8px 0 0; }

/* a section label above a table */
.section-label { font-weight: 600; font-size: 13px; margin-bottom: 4px; }
.section-label-mt { margin-top: 12px; }

/* inline detail row inside a table */
.detail-cell { padding: 0 8px 10px; }
.detail-cell-wide { padding: 0 12px 12px; }

/* a bar whose width is computed at runtime; JS sets --bar-w via CSSOM */
.bar-fill { width: var(--bar-w, 0%); }

/* remaining one-off patterns from the page modules */
.sp-22 { height: 22px; }
.sp-24 { height: 24px; }
.fs-14-loose { font-size: 14px; line-height: 1.6; }
.fs-15-title { font-weight: 620; font-size: 15px; margin-bottom: 4px; }
.flex-1 { flex: 1 1 auto; }
.ml-6 { margin-left: 6px; }
.mr-auto { margin-right: auto; }
.mt-16 { margin-top: 16px; }
.mb-12 { margin-bottom: 12px; }
.hidden { display: none; }
.items-start { align-items: start; }
.row-flex { display: flex; gap: 8px; align-items: center; }
.text-warn { color: var(--warn); }
/* Een tekstlabel tussen twee knoppen hoort op knophoogte te staan, niet bovenaan de rij. */
.self-center { align-self: center; }

/* Een rij die aandacht vraagt: zachte achtergrond, geen rode rand. De rij is niet fout,
   er ontbreekt alleen iets — een streep in de kleur van de waarschuwing links is genoeg
   om hem te vinden zonder de tabel te laten schreeuwen. */
tr.row-warn > td { background: var(--warn-soft); }

/* Een regel die bij de regel erboven hoort: ingesprongen en iets ingetogener, zodat de
   samenhang te zien is zonder dat hij zich als eigen contractregel voordoet. */
tr.derived-row > td { background: var(--surface-2); }
tr.derived-row > td:first-child { padding-left: 28px; box-shadow: inset 3px 0 0 var(--border); }
tr.row-warn > td:first-child { box-shadow: inset 3px 0 0 var(--warn); }
.fs-12-tight { font-size: 12px; padding: 2px 0; }
.match-cust { margin: 4px 0 2px; padding-left: 18px; list-style: disc; }
.match-cust li { padding: 1px 0; }
.fs-11-220 { font-size: 11px; max-width: 220px; }
.fs-12-280 { font-size: 12px; max-width: 280px; }
.section-label-y { font-weight: 600; font-size: 13px; margin: 12px 0 4px; }
.section-label-y-lg { font-weight: 600; font-size: 13px; margin: 16px 0 4px; }
.section-label-sm { font-size: 12px; font-weight: 600; margin: 12px 0 4px; }
.panel-outline {
  border-radius: var(--radius);
  border: 1px solid var(--border);
  margin-bottom: 16px;
}
.dropzone {
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  padding: 38px 20px;
  text-align: center;
  cursor: pointer;
  background: var(--surface);
  transition: background .12s, border-color .12s;
}

/* drag-over highlight — a class instead of JS-applied .style, which the CSP blocks. */
.dropzone.is-dragover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* skeleton placeholder widths — varied so a loading table does not look like a grid.
   Classes rather than inline widths because style attributes are blocked by the CSP. */
.skel-w0 { width: 45%; }
.skel-w1 { width: 50%; }
.skel-w2 { width: 55%; }
.skel-w3 { width: 60%; }
.skel-w4 { width: 65%; }
.skel-w5 { width: 70%; }
.skel-w6 { width: 75%; }
.skel-w7 { width: 80%; }
.skel-w8 { width: 85%; }
.skel-w9 { width: 90%; }

/* full-width left-aligned option button, used for the customer picker list */
.opt-row {
  display: block;
  width: 100%;
  text-align: left;
  margin: 2px 0;
}
.opt-row.is-selected { border-color: var(--accent); }

/* Behavioural hook only: marks the collapsible raw-JSON row under a sweep result so the
   page can find it. Visibility is controlled by the `hidden` attribute, so this needs no
   declarations of its own — it is defined here deliberately, both to document that the
   class is intentional and so the CSP test's "every class exists" check stays meaningful
   instead of needing an exception. */
tr.json-detail { /* no visual styling by design */ }

/* Margekleuren op het Go-Trex-plan. Tekstkleur, geen badge: een tabel met 79 gevulde
   badges leest niet meer als kolom met getallen. Echt verlies houdt wél zijn badge —
   dat is de enige regel die om actie vraagt. */
.marge-ok     { color: var(--ok);     font-weight: 600; font-variant-numeric: tabular-nums; }
.marge-warn   { color: var(--warn);   font-weight: 600; font-variant-numeric: tabular-nums; }
.marge-danger { color: var(--danger); font-weight: 600; font-variant-numeric: tabular-nums; }

/* Samenvoegen per klant (TransIP): de tabel is een boom, geen lijst. De basisregel staat
   bovenaan en wat erin opgaat staat ingesprongen eronder — zonder dat verschil leest elke
   regel als een los probleem terwijl het om een keuze per machine gaat. */
.res-head td { background: rgba(127, 127, 127, 0.07); font-weight: 600; }
.res-child td:first-child { padding-left: 26px; }
.res-arrow { opacity: .55; margin-right: 4px; }

/* Lange foutmeldingen in tabellen: afbreken in plaats van de kolom oprekken. */
.wrap-anywhere { overflow-wrap: anywhere; white-space: normal; }
