/* Het hidden-attribuut hoort altijd te winnen. Elke klasse met een eigen
   `display` (.btn, .rep-uitgang, alles met flex) overschrijft het anders
   stilletjes, en dan staat er een knop die verborgen zou moeten zijn. Dat is hier
   twee keer gebeurd voordat deze regel er kwam. */
[hidden] { display: none !important; }

/* ── Plus Jakarta Sans (self-hosted, variable) — browser lazy-loads only the
   unicode-range subset a page actually needs. ── */
@font-face {
  font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url(/static/fonts/pjs-00.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url(/static/fonts/pjs-01.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url(/static/fonts/pjs-02.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url(/static/fonts/pjs-03.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── Lato (self-hosted) — same UI font as MikroNetCon, for the strak look. ── */
@font-face { font-family:'Lato'; font-style:normal; font-weight:300; font-display:swap; src:url(/static/fonts/lato-300.woff2) format('woff2'); }
@font-face { font-family:'Lato'; font-style:normal; font-weight:400; font-display:swap; src:url(/static/fonts/lato-400.woff2) format('woff2'); }
@font-face { font-family:'Lato'; font-style:normal; font-weight:700; font-display:swap; src:url(/static/fonts/lato-700.woff2) format('woff2'); }

:root, :root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0f1117;
  --bg2: #1a1d27;
  --bg3: #22263a;
  --border: #2e3347;
  --text: #e2e8f0;
  --muted: #8892a4;
  --accent: #f97316;        /* Helios orange (brand accent) */
  --accent-text: #fdba74;   /* readable accent-coloured label on an accent tint */
  --on-accent: #1a1d27;     /* text on an --accent background (dark on the bright orange) */
  --offline-seg: #374151;   /* timeline offline segment fill */
  --shadow-card: 0 8px 24px rgba(0,0,0,.4);   /* hover lift on clickable cards */
  --green: #22c55e;
  --red: #ef4444;
  --accent-soft: rgba(249,115,22,.12);  /* de vulling van een open knop onder de muis */
  --yellow: #f59e0b;
}

:root[data-theme="light"] {
  color-scheme: light;
  /* MikroNetCon-strakke look: witte panelen op een zacht grijs, subtiele randen.
     Iets meer contrast: dieper paginagrijs, sterkere randen, donkerder muted. */
  --bg: #eaeef3;     /* soft grey page — laat witte panelen 'poppen' */
  --bg2: #ffffff;    /* white panels / cards / rows */
  --bg3: #e6eaf0;    /* tint for inputs, hovers, tracks on white */
  --border: #d5dbe3;
  --text: #19212b;   /* dark slate ink */
  --muted: #61707f;
  --accent: #ea580c;        /* fresh Helios orange on light */
  --accent-text: #c2410c;   /* deeper orange for readable labels on tints */
  --on-accent: #ffffff;
  --accent-soft: rgba(234,88,12,.10);
  --offline-seg: #b6c1cd;
  --shadow-card: 0 8px 24px rgba(16,24,40,.11);
  --green: #15803d;   /* contrast-safe on light */
  --red: #dc2626;
  --yellow: #b45309;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--bg); color: var(--text);
  font-family: 'Lato', system-ui, -apple-system, sans-serif;
  font-size: 14px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }

.navbar {
  background: var(--bg2);
  border-bottom: 1px solid var(--border);
  padding: 0 24px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.nav-brand { font-weight: 700; font-size: 16px; letter-spacing: .5px; }
.nav-links { display: flex; align-items: center; gap: 20px; }
.nav-links a { color: var(--muted); text-decoration: none; }
.nav-links a:hover { color: var(--text); }

.container { max-width: 1600px; margin: 0 auto; padding: 28px 24px 44px; }

h1 { font-size: 22px; font-weight: 700; margin-bottom: 4px; }
h2 { font-size: 16px; font-weight: 600; margin: 28px 0 12px; }
.muted { color: var(--muted); font-size: 13px; margin-bottom: 20px; }

.page-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; }

/* Cards */
.cards { display: flex; flex-wrap: wrap; gap: 16px; margin: 20px 0; }
.card { background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; padding: 16px 20px; min-width: 140px; }
.card-label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .6px; margin-bottom: 6px; }
.card-value { font-size: 20px; font-weight: 700; }

/* ── Tabellen ────────────────────────────────────────────────────────────────
   REGELHOOGTE IS ÉÉN GETAL EN STAAT HIER. Elke tabel met class="table" op elke
   pagina en in elk tabblad gebruikt `--tbl-row`; wil je ze allemaal hoger of
   lager, verander dan dit getal en niets anders.

   Waarom dit een regel is en geen smaak: de hoogte werd bepaald door wat er
   toevallig in een cel stond. Een regel met alleen tekst werd 26px, dezelfde
   regel met een knop erin 34px — en dus had elk tabblad zijn eigen hoogte
   zonder dat iemand daar ooit voor gekozen had. Daarom staat de hoogte op de
   cel en zijn de bedieningselementen eronder aan banden gelegd: een knop, een
   invoerveld of een label mag nooit hoger worden dan de regel waar hij in zit.

   Twee regels tekst in één cel (naam met adres eronder) is wél een keuze en
   mag: `height` is een ondergrens, geen plafond. Alleen bediening mag de
   hoogte niet bepalen.

   Bewaakt door scripts/test_table_rows.py. */
:root { --tbl-row: 26px; }

.table { width: 100%; border-collapse: collapse; margin-top: 8px; }
.table th { background: var(--bg2); color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .4px; padding: 6px 12px; text-align: left; border-bottom: 1px solid var(--border); }
.table td { height: var(--tbl-row); padding: 0 12px; font-size: 12.5px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.table tbody tr:hover { background: var(--bg2); }

/* Alles wat je in een cel kunt aanklikken past in de regel. Zonder dit bepaalt
   het hoogste element in de tabel de hoogte van álle regels eromheen. */
.table td .btn, .table td .btn-sm, .table td summary {
  padding: 1px 8px; font-size: 11.5px; line-height: 18px; }
.table td input, .table td select, .table td textarea {
  padding: 0 6px; font-size: 12px; line-height: 18px; height: 20px; box-sizing: border-box; }
/* Een geopende <details> mag wél groeien: dan staat er een formulier open en is
   die hoogte de bedoeling. Alleen de dichte staat telt voor de regelhoogte. */
.table td details[open] { padding-bottom: 6px; }
.td-bold { font-weight: 600; }
.td-mono { font-family: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace; font-size: 12px; color: var(--muted); }
.td-date { color: var(--muted); white-space: nowrap; font-size: 12px; }
.td-right, .th-right { text-align: right; }

/* Badges */
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
/* A neutral badge: a subdomain, a folder name — something you read, not a state
   you judge. The coloured ones next to it all mean something; this one must not
   look like it does. */
.badge.neutral { background: var(--bg3); color: var(--muted); font-weight: 500;
  margin-right: 4px; font-family: ui-monospace, monospace; }
/* Second line in a cell: the description under a name, the "nowhere" under a
   count. Small and grey, and it may not push the row height around. */
.bijtekst { font-size: 11.5px; color: var(--muted); line-height: 1.3; margin-top: 2px; }
.badge-green { background: rgba(34,197,94,.15); color: var(--green); }
.badge-red { background: rgba(239,68,68,.15); color: var(--red); }
.badge-gray { background: var(--bg3); color: var(--muted); }
.badge-info { background: rgba(59,130,246,.15); color: #3b82f6; }
.badge-warn { background: rgba(245,158,11,.15); color: #f59e0b; }

/* Buttons */
/* Eén knop, en die is open: een oranje rand met het opschrift in de tekstkleur,
   die onder de muis oploopt met een vleug oranje. Hij komt van de aanmeldpagina
   en is nu de knop overal. Dichte oranje blokken schreeuwen op een donker scherm,
   en er stonden er drie van, elk in een andere stylesheet — welke je zag hing af
   van welk bestand een pagina toevallig bereikte.

   --on-accent staat hier bewust niet: het opschrift ligt op de pagina en niet op
   het oranje, dus het neemt de tekstkleur van de pagina. */
.btn { background: none; color: var(--text); border: 1px solid var(--accent); border-radius: 6px; padding: 7px 16px; font-size: 13px; font-weight: 600; cursor: pointer; text-decoration: none; display: inline-block; transition: background .15s, border-color .15s; }
.btn:hover { background: var(--accent-soft); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-sm { background: var(--bg3); color: var(--text); border: 1px solid var(--border); border-radius: 5px; padding: 4px 10px; font-size: 12px; font-weight: 500; cursor: pointer; text-decoration: none; }
.btn-sm:hover { background: var(--border); }
/* btn-primary bestaat nog omdat het op 63 plekken staat en "de belangrijkste
   knop hier" een echte betekenis is — hij is alleen niet langer een andere vórm.
   In de paginering markeert hij de huidige pagina; die leest naast het grijs van
   btn-secondary nog steeds als de actieve. */
.btn-primary { background: none !important; color: var(--text) !important; border: 1px solid var(--accent) !important; }
.btn-primary:hover { background: var(--accent-soft) !important; }
.btn-secondary { background: var(--bg3); color: var(--text); border: 1px solid var(--border); }

/* Pagination */
.tbl-footer { display: flex; align-items: center; gap: 6px; padding: 12px 0; }
.tbl-pg-btns { display: flex; gap: 4px; }
.tbl-pg-info { color: var(--muted); font-size: 12px; margin-left: 8px; }

/* Form */
.form-card { background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; padding: 20px; display: flex; gap: 16px; align-items: flex-end; margin-bottom: 24px; flex-wrap: wrap; }
.form-card label { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: .4px; }
.form-card select { background: var(--bg3); color: var(--text); border: 1px solid var(--border); border-radius: 5px; padding: 7px 10px; font-size: 13px; min-width: 220px; }

/* ── Hamburger menu ── */
.nav-hamburger {
  display: none; flex-direction: column; gap: 5px; cursor: pointer;
  background: none; border: none; padding: 4px;
}
.nav-hamburger span { display: block; width: 22px; height: 2px; background: var(--text); border-radius: 2px; transition: .2s; }
.nav-mobile-menu {
  position: fixed; inset: 52px 0 0 0; background: var(--bg2);
  z-index: var(--z-omlijsting-open); flex-direction: column; padding: 16px 20px; gap: 4px;
  border-top: 1px solid var(--border);
  display: none;
}
.nav-mobile-menu.open { display: flex !important; }
.nav-mobile-menu a, .nav-mobile-menu button {
  color: var(--text); text-decoration: none; font-size: 15px; font-weight: 500;
  padding: 12px 0; border-bottom: 1px solid var(--border);
  background: none; border-left: none; border-right: none; border-top: none;
  text-align: left; cursor: pointer; width: 100%;
}
.nav-mobile-menu a:last-child, .nav-mobile-menu button:last-child { border-bottom: none; }

/* ── Scrollable tables on mobile ── */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── Dashboard: KPI strip + section titles (base; responsive overrides below) ── */
.kpi-strip  { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; }
.kpi-card   { background:var(--bg2); border:1px solid var(--border); border-radius:10px; padding:16px 18px; }
.kpi-value  { font-size:28px; font-weight:700; line-height:1.1; font-variant-numeric:tabular-nums; }
.kpi-label  { font-size:12.5px; color:var(--muted); margin-top:4px; }
.chart-title { font-size:15px; font-weight:600; color:var(--text); }
.chart-sub   { font-size:12.5px; font-weight:400; color:var(--muted); margin-left:8px; }

/* ── Responsive breakpoints ── */
@media (max-width: 768px) {
  /* Nav */
  .nav-links { display: none; }
  .nav-hamburger { display: flex; }
  .container { padding: 16px 14px; }
  h1 { font-size: 18px; }
  .page-header { flex-direction: column; gap: 12px; }
  .page-header > div:last-child { width: 100%; }

  /* Dashboard KPI */
  .kpi-strip { grid-template-columns: 1fr 1fr; }
  .kpi-value { font-size: 20px; }
  .kpi-icon  { width: 36px; height: 36px; font-size: 15px; }

  /* Dashboard charts */
  .dash-grid-2     { grid-template-columns: 1fr; }
  .dash-grid-inner { grid-template-columns: 1fr; }

  /* Settings grid */
  .settings-grid { grid-template-columns: 1fr; }
  .s-add-row { flex-direction: column; }

  /* Timeline */
  .tl-loc-col, .tl-data-col,
  .tl-lte-col { display: none; }
  .tl-name-col { width: 160px; }

  /* Tables */
  .table-scroll-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 -14px; padding: 0 14px; }
  /* Smaller scherm: alleen zijkanten smaller. De hoogte blijft --tbl-row,
     want daar gaat die variabele juist over. */
  .table td { padding: 0 10px; font-size: 12px; }
  .table th { padding: 6px 10px; font-size: 12px; }

  /* Uptime grid */
  .uptime-grid { grid-template-columns: 1fr 1fr; }

  /* Login */
  .login-card { margin: 16px; padding: 28px 20px; }
}

@media (max-width: 480px) {
  .kpi-strip { grid-template-columns: 1fr; }
  .uptime-grid { grid-template-columns: 1fr; }
  .tl-name-col { width: 130px; }
}

/* ── Shared settings/links card + form primitives (used by settings.html and links.html) ── */
.s-card { background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; padding: 24px; }
.s-card-header { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 22px; }
.s-card-title { font-size: 14px; font-weight: 700; color: var(--text); margin-bottom: 3px; }
.s-card-sub   { font-size: 12px; color: var(--muted); }
.s-fields { display: flex; flex-direction: column; gap: 14px; margin-bottom: 20px; }
.s-field  { display: flex; flex-direction: column; gap: 5px; }
.s-label  { font-size: 12px; font-weight: 600; color: var(--muted); }
.s-input  { background: var(--bg3); border: 1px solid var(--border); color: var(--text);
  border-radius: 7px; padding: 9px 13px; font-size: 13px; width: 100%; box-sizing: border-box; transition: border-color .15s; }
.s-input:focus { outline: none; border-color: var(--accent); }
.lnk-badge { display: inline-block; font-size: 10px; font-weight: 600; background: var(--bg3);
  border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px; margin-right: 4px;
  color: var(--muted); vertical-align: middle; }

/* ── App shell (top bar · theme toggle · sidebar · flyout selector · dashboard modal · footer) ──
   Lives here rather than inline in base.html so all site-wide CSS is in one place. */
/* ── Lagen ──────────────────────────────────────────────────────────────────
   De app-omlijsting lag onder de pagina-inhoud. Op de plattegrond viel dat op:
   Leaflet zet zijn tegels op 400 en de pins staan op 800, terwijl de zijbalk op
   50 stond, de topbalk op 70 en de sitekiezer op 100. Die kiezer is breder dan
   de zijbalk en steekt dus de kaart in — en verdween daar prompt achter. De
   zoekresultaten hadden hetzelfde: die staan zelf op 300, maar zitten opgesloten
   in de topbalk, en een kind komt nooit boven de laag van zijn ouder uit.

   Vandaar banden in plaats van losse getallen. Wie iets nieuws stapelt kiest een
   band en niet een cijfer dat toevallig hoog genoeg leek:

     < 1000   pagina-inhoud — kaartlagen, pins, legenda's
     1000     omlijsting — topbalk en zijbalk
     1100     omlijsting die openklapt — sitekiezer, zoekresultaten
     2000+    vensters die álles mogen bedekken, inclusief de omlijsting
   ────────────────────────────────────────────────────────────────────────── */
:root {
  --z-omlijsting:       1000;
  --z-omlijsting-open:  1100;
  --z-venster:          2000;
}

body { margin:0; }
.topbar { height:46px; display:flex; align-items:center; padding:0 18px; background:var(--bg2);
          border-bottom:1px solid var(--border); position:sticky; top:0; z-index:var(--z-omlijsting); }
.topbar-brand { display:flex; align-items:center; gap:8px; font-size:15px; font-weight:600; letter-spacing:.3px; color:var(--text); text-decoration:none; }
.topbar-brand:hover { color:var(--text); }
/* The wordmark. Weight and tightness are the whole of it — there is no
   drawing to get wrong at a small size, and nothing that needs a background of
   a particular colour. */
.brand {
  font-weight: 800; letter-spacing: -.025em; color: var(--text);
  white-space: nowrap; line-height: 1;
}
.brand i { font-style: normal; color: var(--accent); }
.theme-toggle { position:relative; width:55px; height:30px; border:none; border-radius:16px;
                cursor:pointer; padding:0; flex-shrink:0; transition:background .25s; }
.theme-toggle[data-mode="light"] { background:linear-gradient(145deg,#e4e8ee,#f6f8fb);
                box-shadow: inset 2px 2px 5px #c5cad3, inset -2px -2px 5px #ffffff; }
.theme-toggle[data-mode="dark"]  { background:linear-gradient(145deg,#2b3140,#3b4252);
                box-shadow: inset 2px 2px 5px #1b1f29, inset -2px -2px 4px #474f60; }
.tt-knob { position:absolute; top:3px; left:3px; width:24px; height:24px; border-radius:50%;
           display:flex; align-items:center; justify-content:center; transition:left .25s, background .25s; }
.theme-toggle[data-mode="dark"] .tt-knob { left:28px; }
.theme-toggle[data-mode="light"] .tt-knob { background:linear-gradient(145deg,#ffffff,#e9edf2);
           box-shadow:1px 1px 3px #c5cad3, -1px -1px 2px #ffffff; }
.theme-toggle[data-mode="dark"]  .tt-knob { background:linear-gradient(145deg,#535b6c,#3a414f);
           box-shadow:1px 1px 3px #1b1f29; }
.tt-knob svg { width:14px; height:14px; }
.theme-toggle[data-mode="light"] .tt-sun  { display:block; color:#8a93a1; }
.theme-toggle[data-mode="light"] .tt-moon { display:none; }
.theme-toggle[data-mode="dark"]  .tt-moon { display:block; color:#dbe2ec; }
.theme-toggle[data-mode="dark"]  .tt-sun  { display:none; }
.topbar-dash { margin-left:10px; display:flex; align-items:center; gap:7px; background:var(--bg3); border:1px solid var(--border);
               color:var(--text); font-size:13px; font-weight:500; padding:6px 12px; border-radius: 6px; cursor:pointer; text-decoration:none; }
.topbar-dash:hover { border-color:var(--accent); }
.topbar-dash svg { width:15px; height:15px; }
/* Op een telefoon paste de topbalk niet: de knop stak er 16px uit en liet elke
   pagina zijwaarts schuiven. Alleen het woord verdwijnt — het icoon blijft, dus
   de knop blijft even goed te raken en herkenbaar. */
@media (max-width: 768px) {
  .topbar-dash { padding:6px 9px; gap:0; }
  .topbar-dash span { display:none; }
}

/* ── Global search (topbar) ── */
.topbar-search { margin-left:auto; margin-right:12px; position:relative; display:flex; align-items:center; gap:7px;
  width:270px; max-width:34vw; background:var(--bg3); border:1px solid var(--border); border-radius:8px; padding:6px 10px; }
.topbar-search:focus-within { border-color:var(--accent); }
/* Geschiedenis: een klokje naast het zoekveld, met de laatste vijf pagina's. */
.tb-hist { position:relative; margin-right:10px; }
.tb-hist-btn { display:flex; align-items:center; justify-content:center; width:32px; height:32px;
  background:var(--bg3); border:1px solid var(--border); border-radius:8px; color:var(--muted);
  cursor:pointer; transition:color .15s, border-color .15s; }
.tb-hist-btn:hover { color:var(--text); border-color:var(--accent); }
.tb-hist-btn svg { width:16px; height:16px; }
.tb-hist-list { display:none; position:absolute; top:calc(100% + 6px); right:0; min-width:250px;
  max-width:330px; background:var(--bg2); border:1px solid var(--border); border-radius:9px;
  box-shadow:0 8px 26px rgba(0,0,0,.25); padding:5px; z-index:60; }
.tb-hist-list.open { display:block; }
.tb-hist-item { display:flex; align-items:baseline; gap:9px; width:100%; text-align:left;
  background:none; border:0; border-radius:6px; padding:7px 9px; cursor:pointer;
  color:var(--text); font:inherit; font-size:12.5px; }
.tb-hist-item:hover { background:var(--bg3); }
.tb-hist-titel { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-weight:600; }
.tb-hist-site { flex:none; max-width:120px; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; font-size:11px; color:var(--muted); }
.tb-hist-empty { padding:10px 9px; font-size:12.5px; color:var(--muted); }
.topbar-search .ts-ico { width:15px; height:15px; color:var(--muted); flex:none; }
.topbar-search input { flex:1; min-width:0; background:none; border:none; outline:none; color:var(--text); font-size:13px; }
.topbar-search input::placeholder { color:var(--muted); }
.ts-results { position:absolute; top:calc(100% + 6px); right:0; left:0; background:var(--bg2); border:1px solid var(--border);
  border-radius:10px; box-shadow:0 16px 42px -12px rgba(0,0,0,.55); max-height:66vh; overflow:auto; z-index:300; display:none; padding:6px; }
.ts-results.open { display:block; }
.ts-group-lbl { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--muted); padding:9px 10px 4px; }
.ts-item { display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:7px; cursor:pointer; text-decoration:none; color:var(--text); }
.ts-item:hover, .ts-item.active { background:var(--bg3); }
.ts-item-ic { width:28px; height:28px; border-radius:7px; background:var(--bg3); display:flex; align-items:center; justify-content:center; color:var(--muted); flex:none; }
.ts-item:hover .ts-item-ic, .ts-item.active .ts-item-ic { background:var(--bg2); }
.ts-item-ic svg { width:15px; height:15px; }
.ts-item-main { flex:1; min-width:0; }
.ts-item-label { font-size:13.5px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ts-item-sub { font-size:12px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ts-empty { padding:16px 12px; text-align:center; color:var(--muted); font-size:13px; }
@media (max-width:768px) { .topbar-search { width:160px; margin-right:8px; } }
/* Keep the top bar dark on the light theme too — a consistent brand header. */
/* Light theme keeps everything light (MikroNetCon-stijl): the topbar uses the
   white panel token (var(--bg2)) on the soft grey page, separated by the border
   — no dark-topbar override. Brand/dash inherit the light tokens. */
.app { display:flex; min-height:calc(100vh - 46px); }
.sb { width:222px; flex-shrink:0; background:var(--bg2); border-right:1px solid var(--border);
      display:flex; flex-direction:column; position:sticky; top:46px; height:calc(100vh - 71px); z-index:var(--z-omlijsting); }
.app-main { flex:1; min-width:0; }
/* Site switcher card (opens the flyout) */
.sb-site { display:flex; align-items:center; gap:10px; margin:10px; padding:9px 10px; border-radius: 7px;
           background:var(--bg3); border:1px solid var(--border); cursor:pointer; transition:border-color .15s; }
.sb-site:hover { border-color:var(--accent); }
.sb-ico { width:32px; height:32px; border-radius: 7px; background:color-mix(in srgb, var(--accent) 15%, transparent); color:var(--accent);
          display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.sb-ico svg { width:17px; height:17px; }
.sb-site-name { font-size:13.5px; font-weight:600; color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sb-site-meta { font-size:11px; color:var(--muted); margin-top:1px; }
.sb-grp { padding:14px 18px 6px; font-size:10px; font-weight:600; color:var(--muted); letter-spacing:.09em; text-transform:uppercase; opacity:.65; }
.sb-nav { padding:0 10px; display:flex; flex-direction:column; gap:1px; }
.sb-link { display:flex; align-items:center; gap:11px; padding:8px 11px; border-radius: 6px;
           font-size:13.5px; color:var(--muted); text-decoration:none; transition:color .12s, background .12s; }
.sb-link:hover { background:var(--bg3); color:var(--text); }
.sb-link.active { background:color-mix(in srgb, var(--accent) 15%, transparent); color:var(--accent-text); font-weight:600; }
.sb-link.active svg { stroke:var(--accent); opacity:1; }
.sb-link svg { width:17px; height:17px; flex-shrink:0; stroke:currentColor; fill:none; stroke-width:2; opacity:.8; }
.sb-spacer { flex:1; }
.sb-foot { display:block; padding:9px 14px 13px; border-top:1px solid var(--border); font-size:11px; color:var(--muted); text-decoration:none; }
.sb-foot:hover { color:var(--text); }
/* Flyout site selector */
.fly-back { position:fixed; inset:0; background:rgba(0,0,0,.28); z-index:var(--z-omlijsting-open); display:none; }
.fly { position:fixed; top:56px; left:10px; width:306px; background:var(--bg2); border:1px solid var(--border);
       border-radius: 8px; z-index:calc(var(--z-omlijsting-open) + 10); padding:8px; display:none; box-shadow:0 18px 50px rgba(0,0,0,.55); }
.fly-act { display:flex; justify-content:space-between; align-items:center; margin-bottom:8px; font-size:12.5px; }
.fly-act a { color:var(--accent); cursor:pointer; text-decoration:none; }
.fly-search { display:flex; align-items:center; gap:6px; border:1px solid var(--border); border-radius:7px; padding:5px 9px; margin-bottom:8px; }
.fly-search input { border:none; background:transparent; color:var(--text); font-size:13px; flex:1; outline:none; }
.fly-cols { display:flex; justify-content:space-between; font-size:10px; color:var(--muted); padding:0 10px 4px; text-transform:uppercase; letter-spacing:.04em; }
/* Laat de lijst meegroeien met het scherm in plaats van hem op een vaste hoogte
   af te kappen: bij 12 organisaties paste dat niet in 340px en kreeg je een
   scrollbalk terwijl er ruimte zat. Scrollen gebeurt nu alleen nog als de lijst
   echt niet op het scherm past; de balk is dan smal en pas zichtbaar bij hover. */
.fly-list { max-height:calc(100vh - 190px); overflow-y:auto; scrollbar-width:thin;
            scrollbar-color:transparent transparent; }
.fly-list:hover { scrollbar-color:var(--border) transparent; }
.fly-list::-webkit-scrollbar { width:6px; }
.fly-list::-webkit-scrollbar-thumb { background:transparent; border-radius:3px; }
.fly-list:hover::-webkit-scrollbar-thumb { background:var(--border); }
.fly-row { display:flex; align-items:center; justify-content:space-between; padding:9px 11px; border-radius: 7px; cursor:pointer; font-size:13px; }
.fly-row:hover { background:var(--bg3); }
.fly-row.active { background:color-mix(in srgb, var(--accent) 16%, transparent); color:var(--accent-text); }
.fly-row.active .fly-cnt { color:var(--accent-text); }
.fly-cnt { color:var(--muted); }
.fly-crit { background:rgba(239,68,68,.16); color:var(--red); font-size:11px; font-weight:600; padding:1px 8px; border-radius:20px; }
/* Online/offline per organisatie. Twee getallen naast elkaar in plaats van tekst:
   in een kolom van ~80px past "12 online, 3 offline" niet, en kleur zegt hier
   hetzelfde in één oogopslag. Een offline-telling van 0 wordt weggelaten — dan is
   het stil, en dat hoort er ook stil uit te zien. */
/* flex:1 met min-width:0 — zonder dat laatste weigert een flex-item te krimpen
   en duwt een lange sitenaam het label en de tellingen de regel uit. */
.fly-nm { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis;
          white-space:nowrap; padding-right:8px; }
/* Loopt er nu een showdag op deze site? Hetzelfde groen als de melding in de
   balk, maar hier als woord in een kadertje: een bolletje moet je kennen, LIVE
   niet. Het staat vlak vóór de tellingen en die hebben een vaste breedte, dus
   alle labels komen onder elkaar op dezelfde plek uit. Niet knipperend: in een
   lijst van dertien regels is drie keer pulserend groen onrustig — de melding in
   de balk gaat over de site die je open hebt en mag wél opvallen. */
.fly-live { flex:none; margin-right:8px; padding:1px 6px; border-radius:5px;
            font-size:9.5px; font-weight:800; letter-spacing:.08em; line-height:1.5;
            color:var(--green); border:1px solid color-mix(in srgb, var(--green) 45%, transparent);
            background:color-mix(in srgb, var(--green) 14%, transparent); }
/* Zelfde groen als het online-getal ernaast: een vast #16a34a hield in het
   donkere thema te weinig over naast die pillen. */
.fly-row.active .fly-live { background:color-mix(in srgb, var(--green) 22%, transparent); }
/* Twee vaste kolommen in plaats van naast elkaar zwevende badges: zo staat het
   online-getal op elke regel op dezelfde plek, ook wanneer er geen offline-getal
   is. Met flex schoof dat getal dan naar rechts en liep de kolom niet meer door. */
.fly-counts { display:grid; grid-template-columns:34px 34px; gap:5px; flex-shrink:0;
              justify-items:end; align-items:center; font-variant-numeric:tabular-nums; }
.fly-num { font-size:11px; font-weight:600; padding:1px 0; border-radius:20px;
           width:100%; text-align:center; }
.fly-num.on  { background:color-mix(in srgb, var(--green) 16%, transparent); color:var(--green); }
.fly-num.off { background:rgba(239,68,68,.16); color:var(--red); }
/* Lege site en "tellingen laden nog": zelfde vorm en plek als de gekleurde
   pillen, alleen stil. Zonder dit stond het getal van een lege site net naast
   de kolom van de rest. */
.fly-num.leeg  { background:transparent; color:var(--muted); opacity:.55; }
.fly-num.wacht { background:var(--bg3); color:var(--muted); }
.fly-row.active .fly-num.on { background:color-mix(in srgb, var(--green) 24%, transparent); }
@media (max-width:760px){ .sb { width:60px; } .sb-site-name,.sb-site-meta,.sb-link span,.sb-grp,.sb-foot { display:none; } }
.app-footer-fade { position:fixed; bottom:25px; left:222px; right:0; height:30px;
                   background:linear-gradient(to bottom,transparent,var(--bg)); pointer-events:none; z-index:55; }
@media (max-width:760px){ .app-footer-fade { left:60px; } }
.app-footer { position:fixed; bottom:0; left:0; right:0; display:flex; align-items:center;
              justify-content:space-between; padding:0 14px; height:25px; font-size:10px; color:var(--muted);
              background:var(--bg2); border-top:1px solid var(--border); z-index:60; }
.app-footer-ver { color:var(--muted); text-decoration:none; }
.app-footer-ver:hover { color:var(--text); }

/* Respect the user's OS "reduce motion" setting. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Onderdelen van een uitklappaneel die geen label/waarde-rij zijn ────────
   Wat hier stond was een compleet tweede paneeldialect (.uxp-*): eigen kop,
   eigen kaartjesstrook, eigen kolommen-in-kaarten, eigen tabel. Het UISP- en
   UniFi-paneel gebruikten dat, de andere vier het routerdialect. Die twee
   spreken nu ook het routerdialect, dus alleen wat écht eigen was blijft over:
   de tekening bovenaan en twee kleuren. ── */

/* De prent-laag van paneel(): een tekening over de volle breedte, boven de
   cijfers. Nu alleen de UISP-straalverbinding, maar de laag staat los van wie
   hem vult. */
.tl-prent { border: 1px solid var(--border); border-radius: 7px; overflow: hidden;
  background: radial-gradient(120% 140% at 50% 0%, #161b24 0%, #0f1218 70%);
  padding: 6px; margin-bottom: 12px; }
/* One typeface across the whole hero graphic — the app sans — so labels and the
   dBm/MHz/km readouts match. tabular-nums keeps the figures evenly spaced.
   The SVG renders at its natural pixel size (width/height attributes) and only
   shrinks below its own width, so the text stays a fixed size on desktop instead
   of scaling up with the panel. */
.tl-prent svg { display: block; max-width: 100%; height: auto; margin: 0 auto;
  font-family: 'Lato', system-ui, -apple-system, sans-serif;
  font-variant-numeric: tabular-nums; }

/* Down/up in één cel: kleur scheidt ze, zodat er geen tweede label bij hoeft. */
.tl-dn { color: #7dd3fc; } .tl-up { color: #22c55e; }

/* Kleine pil in een tabelcel, waar een kleur op de tekst te weinig zegt. */
.tl-tag { display: inline-block; font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 20px; }
.tl-tag.g { background: rgba(34,197,94,.14);  color: #6ee7a5; }
.tl-tag.y { background: rgba(245,158,11,.14); color: #fcd34d; }
.tl-tag.r { background: rgba(239,68,68,.14);  color: #fca5a5; }

/* Gecentreerde tabelcellen. Deze twee klassen werden op vier pagina's gebruikt
   — negen keer in totaal — maar stonden nergens gedefinieerd, dus ze deden
   niets. Wie ze schreef bedoelde dit.

   Met het element ervoor, want `.table th` hierboven zet text-align:left en is
   met twee onderdelen specifieker dan een losse klasse. Zonder `th`/`td` ervoor
   verliest deze regel en centreert er alsnog niets. */
.table th.th-center, .table td.td-center { text-align:center; }

/* ── Aan/uit-schuifje ───────────────────────────────────────────────────────
   Stond woord voor woord overgeschreven in alerting.html en logs.html. Eén keer
   hier, zodat een schuifje overal hetzelfde is en een nieuwe pagina er niet
   opnieuw zes regels voor hoeft over te schrijven. Groen is aan, overal.

   settings.html heeft een eigen, grotere variant (`.conn-sw`, 40x22) die alles
   zelf definieert en de klasse `sw` niet gebruikt; die staat hier dus los van. */
.sw { position:relative; display:inline-block; width:34px; height:19px; flex-shrink:0; cursor:pointer; }
.sw input { position:absolute; opacity:0; width:0; height:0; }
.sw-track { position:absolute; inset:0; background:var(--bg3); border-radius:20px; transition:background .15s; }
.sw-track::before { content:""; position:absolute; left:2px; top:2px; width:15px; height:15px;
                    border-radius:50%; background:var(--muted); transition:transform .15s, background .15s; }
.sw input:checked + .sw-track { background:color-mix(in srgb, var(--green) 25%, transparent); }
.sw input:checked + .sw-track::before { transform:translateX(15px); background:var(--green); }

/* ── Modules tab: module cards (.mod-*) ────────────────────────────────────── */
.mod-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; margin-top: 8px; }
.mod-card {
  position: relative; background: var(--bg2); border: 1px solid var(--border);
  border-radius: 7px; padding: 20px 20px 18px; display: flex; flex-direction: column;
  min-height: 208px; overflow: hidden;
  transition: transform .18s cubic-bezier(.16,1,.3,1), border-color .18s, box-shadow .18s;
}
.mod-card::before { content: ''; position: absolute; left: 0; top: 0; right: 0; height: 2px;
  background: var(--mod-accent, transparent); }
.mod-card:hover { transform: translateY(-2px); border-color: var(--accent); box-shadow: 0 10px 26px rgba(0,0,0,.30); }
.mod-card.is-active  { --mod-accent: #22c55e; }
.mod-card.is-trial   { --mod-accent: #f59e0b; }
.mod-card.is-expired { --mod-accent: #ef4444; }
.mod-card.is-idle    { --mod-accent: transparent; }

.mod-top { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.mod-icon { width: 44px; height: 44px; border-radius: 7px; display: flex; align-items: center;
  justify-content: center; font-size: 22px; background: var(--bg3); border: 1px solid var(--border); flex-shrink: 0; }
.mod-card.is-active .mod-icon { background: rgba(34,197,94,.10);  border-color: rgba(34,197,94,.25); }
.mod-card.is-trial  .mod-icon { background: rgba(245,158,11,.10); border-color: rgba(245,158,11,.25); }
.mod-name { font-size: 15px; font-weight: 700; color: var(--text); letter-spacing: -.01em;
  flex: 1; min-width: 0; line-height: 1.25; }

.mod-status { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600;
  padding: 3px 10px 3px 8px; border-radius: 20px; white-space: nowrap; border: 1px solid transparent; align-self: flex-start; }
.mod-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.mod-card.is-active  .mod-status { color: #6ee7a5; background: rgba(34,197,94,.10);  border-color: rgba(34,197,94,.22); }
.mod-card.is-active  .mod-dot { background: #22c55e; animation: modPulse 2.4s infinite; }
.mod-card.is-trial   .mod-status { color: #fcd34d; background: rgba(245,158,11,.10); border-color: rgba(245,158,11,.22); }
.mod-card.is-trial   .mod-dot { background: #f59e0b; }
.mod-card.is-expired .mod-status { color: #fca5a5; background: rgba(239,68,68,.10);  border-color: rgba(239,68,68,.22); }
.mod-card.is-expired .mod-dot { background: #ef4444; }
.mod-card.is-idle    .mod-status { color: var(--muted); background: var(--bg3); border-color: var(--border); }
.mod-card.is-idle    .mod-dot { background: #6b7280; }
@keyframes modPulse { 0% { box-shadow: 0 0 0 0 rgba(34,197,94,.5); } 70% { box-shadow: 0 0 0 5px rgba(34,197,94,0); } 100% { box-shadow: 0 0 0 0 rgba(34,197,94,0); } }

.mod-desc { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-bottom: 14px;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.mod-meta { font-size: 11.5px; color: var(--muted); margin-bottom: 16px; display: flex;
  align-items: center; gap: 6px; min-height: 15px; }
.mod-meta.warn { color: #f59e0b; }
.mod-meta.crit { color: #ef4444; }
.mod-meta svg { flex-shrink: 0; opacity: .85; }

.mod-actions { display: flex; gap: 8px; margin-top: auto; }
.mod-actions .btn { flex: 1 1 0; text-align: center; padding: 7px 12px; font-size: 12.5px; }
/* Always rendered (empty when there's no destructive action) and a fixed height,
   so the action row above sits at the same offset from the card bottom on every
   card — the buttons line up across the whole grid. */
.mod-foot { display: flex; justify-content: flex-end; align-items: center; margin-top: 10px; height: 22px; }
.mod-danger { background: none; border: none; color: var(--muted); font-size: 11.5px; font-weight: 500;
  cursor: pointer; padding: 3px 4px; transition: color .15s; }
.mod-danger:hover { color: #ef4444; }
.mod-cfg-btn { color: var(--text) !important; }  /* neutral ghost look for ⚙ */

@media (prefers-reduced-motion: reduce) {
  .mod-card { transition: none; }
  .mod-card:hover { transform: none; }
  .mod-card.is-active .mod-dot { animation: none; }
}

/* Signaal dat een UniFi-apparaat in de controller naar een andere site is
   verhuisd terwijl het hier nog onder de oude hangt. Amber, niet rood: er is niets
   stuk, de indeling loopt alleen achter. Klikbaar wanneer de bijbehorende
   organisatie hier bestaat. */
.ap-site-moved { display:inline-block; margin-left:7px; font-size:11px; font-weight:600;
                 padding:1px 7px; border-radius:20px; vertical-align:1px;
                 background:color-mix(in srgb, var(--yellow) 15%, transparent);
                 color:var(--yellow); border:1px solid color-mix(in srgb, var(--yellow) 35%, transparent); }
.ap-site-moved.can-move { cursor:pointer; }
.ap-site-moved.can-move:hover { background:color-mix(in srgb, var(--yellow) 26%, transparent); }

/* ── LIVE in de balk ─────────────────────────────────────────────────────────
   Alleen zichtbaar als er op de geopende site een showdag loopt. Een balk die
   altijd iets meldt wordt niet meer gelezen; dit hoort juist op te vallen. */
.tb-live { display:inline-flex; align-items:center; gap:7px; margin-left:6px;
           padding:5px 11px 5px 9px; border-radius:20px; font-size:12px; font-weight:800;
           letter-spacing:.09em; color:#16a34a; background:#22c55e1f;
           border:1px solid #22c55e59; white-space:nowrap; }
.tb-live i { width:8px; height:8px; border-radius:50%; background:#22c55e; flex:none;
             animation:tb-live-puls 2s ease-in-out infinite; }
.tb-live em { font-style:normal; font-weight:600; letter-spacing:0; color:var(--muted);
              font-size:11.5px; }
@keyframes tb-live-puls {
  0%,100% { box-shadow:0 0 0 0 #22c55e66; }
  50%     { box-shadow:0 0 0 5px #22c55e00; }
}
/* Wie beweging uitzet, hoort geen knipperend bolletje te krijgen. */
@media (prefers-reduced-motion: reduce) { .tb-live i { animation:none; } }
@media (max-width:820px) { .tb-live em { display:none; } }

/* Kopjes in de sitekiezer: evenementen boven, vaste locaties eronder. */
.fly-grp { margin-top:10px; padding:9px 12px 4px; font-size:10.5px; font-weight:700;
           letter-spacing:.1em; text-transform:uppercase; color:var(--muted);
           border-top:1px solid var(--border); }
.fly-grp:first-child { margin-top:0; padding-top:4px; border-top:0; }
/* De bijzondere site bovenaan staat los van de groepen; een streep eronder laat
   zien dat hij er niet bij hoort in plaats van dat hij de eerste van de lijst is. */
.fly-list > .fly-row:first-child { border-bottom:1px solid var(--border); }

/* ── Bulk-selectie ───────────────────────────────────────────────────────────
   Stond in templates/apparaten.html toen alleen die pagina hem had. De
   Storingen-pagina gebruikt hem nu ook; een tweede kopie is er één te veel. */
.bulk-bar { display:none; align-items:center; gap:12px; flex-wrap:wrap;
            background:var(--bg2); border:1px solid var(--border); border-left:3px solid var(--accent);
            border-radius:6px; padding:9px 14px; margin-bottom:12px; }
.bulk-bar.visible, .bulk-bar.active { display:flex; }
.bulk-count { font-size:12px; font-weight:700; color:var(--accent-text);
              background:color-mix(in srgb, var(--accent) 14%, transparent);
              padding:4px 11px; border-radius:99px; white-space:nowrap; }
.bulk-sep { align-self:stretch; width:1px; background:var(--border); }
.bulk-lbl { font-size:12px; color:var(--muted); }
.bulk-bar .s-input { margin:0; }
.bulk-bar .bulk-del { margin-left:auto; color:#ef4444; border-color:#ef444440; }
.bulk-bar .bulk-del:hover { background:#ef44441a; }
.col-check { width:34px; text-align:center; }


/* ── Gebruikerslijst (Instellingen → Gebruikers én Gebruikers per site) ───────
   Eén opmaak voor beide schermen: een kopregel met kolommen, een regel per
   gebruiker die zijn bewerkscherm opent, en daarin de handelingen op één rij.
   Stond eerst alleen in settings.html; de site-pagina hoort er niet zijn eigen
   variant naast te zetten. */
.s-user-list { display: flex; flex-direction: column; gap: 2px; margin-bottom: 24px; }
/* Eén raster voor de kopregel en voor elke gebruiker, zodat de kolommen over de
   hele lijst uitlijnen. De maten zijn ruim genoeg voor een e-mailadres en een
   sitenaam; de datums krijgen precies wat ze nodig hebben. */
.s-user-head, .s-user-row {
  display: grid;
  grid-template-columns: 34px minmax(120px, 1.1fr) minmax(150px, 1.5fr) minmax(110px, 1.3fr) 92px 118px 12px;
  gap: 12px; align-items: center; padding: 10px 12px;
}
.s-user-head { padding-top: 4px; padding-bottom: 6px; font-size: 10.5px; font-weight: 700;
              letter-spacing: .5px; text-transform: uppercase; color: var(--muted);
              border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.s-user-row  { border-radius: 6px; transition: background .1s; }
.s-user-cell { min-width: 0; font-size: 12px; color: var(--text); line-height: 1.45; }
.gb-klein { font-size: 10.5px; line-height: 1.3; }
/* Een lang e-mailadres mag de kolom niet oprekken. */
.gb-knip { overflow: hidden; text-overflow: ellipsis; }
/* Meebewegen met de kaart en niet met het venster: de lijst staat naast het
   toevoegformulier, dus hij is smaller dan het scherm. Een vensterbrede
   mediaquery klapte de kolommen dicht terwijl er ruimte zat, en liet ze staan
   waar het krap was. Browsers zonder container-queries houden gewoon alle
   kolommen — dat is de veilige kant. */
.gb-list-card { container-type: inline-size; }
@container (max-width: 760px) {
  /* De vijfde kolom gaat er als eerste af. Die plek is bij afspraak de minst
     dringende van het scherm: "Aangemaakt" in Instellingen, "Andere sites" op
     de site-pagina. Zet daar dus niets neer wat je altijd wilt zien. */
  .s-user-head > :nth-child(5), .s-user-row > :nth-child(5) { display: none; }
  .s-user-head, .s-user-row {
    grid-template-columns: 34px minmax(110px, 1.1fr) minmax(140px, 1.4fr) minmax(100px, 1.2fr) 118px 12px; }
}
@container (max-width: 600px) {
  /* Alles onder elkaar, maar dan mét het kopje ervoor. Zonder die kopjes staan
     er vier grijze regels waarvan je moet raden wat ze zijn — precies de
     rommel waar de kolommen vanaf moesten. */
  .s-user-head { display: none; }
  .s-user-row { grid-template-columns: 34px 1fr 12px; row-gap: 2px; }
  .s-user-row > :nth-child(5) { display: block; }
  .s-user-row .s-user-cell:not(:nth-child(2)) { grid-column: 2; font-size: 11.5px; }
  .s-user-row .s-user-cell[data-col-head]::before {
    content: attr(data-col-head) ": "; color: var(--muted); opacity: .8; }
}
.s-user-row:hover { background: var(--bg3); }
.s-user-avatar {
  width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
  background: var(--accent); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700;
}
.s-user-name  { font-size: 13px; font-weight: 600; color: var(--text); display: flex;
                align-items: center; gap: 8px; flex-wrap: wrap; }
.s-user-actions { flex-shrink: 0; }
/* Twee kaders naast elkaar: de lijst krijgt de ruimte, het formulier een vaste
   kolom. Onder 1100px klapt het onder elkaar, met het formulier eerst — dan is
   het nog steeds het eerste wat je ziet als je iemand komt toevoegen. */
.gb-cols { display: grid; grid-template-columns: minmax(0,1fr) 340px; gap: 16px; align-items: start; }
.gb-new-card .s-card-header { margin-bottom: 16px; }
@media (max-width: 1100px) {
  .gb-cols { grid-template-columns: 1fr; }
  .gb-new-card { order: -1; }
}
/* Wat geen etiket verdient, maar wel gezien mag worden. */
.gb-jij { font-size: 11px; font-weight: 500; color: var(--muted); }
.gb-2fa { border: 1px solid var(--border); border-radius: 8px; padding: 11px 13px; margin-bottom: 14px; }
.gb-2fa-head { font-size: 12px; font-weight: 700; margin-bottom: 9px; display: flex; gap: 8px; align-items: baseline; }
.gb-2fa-head .gb-dim { font-weight: 500; }
.gb-2fa-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; font-size: 12px; }
.gb-2fa-row .s-input { width: 190px; }
.gb-dim { color: var(--muted); opacity: .75; }
.s-btn-delete {
  background: none; border: 1px solid rgba(239,68,68,.3); color: #ef4444;
  border-radius: 6px; padding: 4px 12px; font-size: 12px; cursor: pointer;
  transition: background .15s;
}
.s-btn-delete:hover { background: rgba(239,68,68,.12); }

/* ── User edit ── */
.s-user-wrap { margin-bottom: 2px; }
/* De regel is zelf de knop. Het pijltje draait mee zodat je ziet wat openstaat. */
.s-user-row { cursor: pointer; }
.s-user-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.s-user-chev { flex-shrink: 0; color: var(--muted); font-size: 17px; line-height: 1;
               transition: transform .15s; }
.s-user-wrap.open .s-user-chev { transform: rotate(90deg); }
/* Alle handelingen op één regel en in hetzelfde slanke jasje. Twee dikke
   oranje knoppen boven een apart regeltje met twee dunne maakte er twee blokken
   van, terwijl het één rij keuzes is. Opslaan houdt een accentrand — het is de
   knop waar je heen wilt — en verwijderen blijft rood en gaat naar rechts, weg
   van de knop waar je per ongeluk op mikt. */
.gb-acties { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 16px; }
.gb-btn { background: none; border: 1px solid var(--border); color: var(--text);
           border-radius: 6px; padding: 5px 13px; font-size: 12.5px; line-height: 1.5;
           cursor: pointer; transition: border-color .15s, color .15s, background .15s; }
.gb-btn:hover { border-color: var(--accent); }
.gb-btn-main { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.gb-btn-main:hover { background: rgba(249,115,22,.1); }
.gb-btn-danger { margin-left: auto; color: #ef4444; border-color: rgba(239,68,68,.3); }
.gb-btn-danger:hover { background: rgba(239,68,68,.12); border-color: #ef4444; }
.s-btn-edit {
  background: none; border: 1px solid var(--border); color: var(--muted);
  border-radius: 6px; padding: 4px 12px; font-size: 12px; cursor: pointer;
  transition: color .15s, border-color .15s;
}
.s-btn-edit:hover { color: var(--text); border-color: var(--accent); }
.s-edit-form {
  background: var(--bg3); border: 1px solid var(--border); border-top: none;
  border-radius: 0 0 8px 8px; padding: 16px;
  margin-bottom: 4px;
}
.s-edit-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
}
@media (max-width: 600px) { .s-edit-grid { grid-template-columns: 1fr; } }

/* Meldingsbalkjes boven een formulier. Stonden in settings.html; het profiel
   gebruikt ze ook. */
.s-alert {
  border-radius: 7px; padding: 10px 14px; font-size: 13px; margin-bottom: 18px;
  font-weight: 500;
}
.s-alert.ok  { background:rgba(34,197,94,.1);  border:1px solid rgba(34,197,94,.25);  color:#22c55e; }
.s-alert.err { background:rgba(239,68,68,.1);  border:1px solid rgba(239,68,68,.25);  color:#ef4444; }

/* ── Page tabs ───────────────────────────────────────────────────────────────
   A strip of tabs at the top of a page, for a screen that has two or three
   faces — Profile and its Billing tab, to begin with.

   Here and not inside one template's style block, which is where the Settings
   tabs live: a class defined in settings.html exists on that page and nowhere
   else, so using it elsewhere renders a bare browser button. That is exactly
   what happened, and nothing warned about it.

   Underlined rather than the pill Settings uses for its sub-tabs. These sit at
   the top of a page and mark where you are; a pill reads as a filter. */
.tabs {
  display: flex; flex-wrap: wrap; gap: 2px;
  border-bottom: 1px solid var(--border); margin-bottom: 20px;
}
.tab {
  background: none; border: none; color: var(--muted); font: inherit;
  font-size: 13.5px; font-weight: 600; padding: 9px 15px; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  text-decoration: none; display: inline-block;
  transition: color .15s, border-color .15s;
}
.tab:hover { color: var(--text); text-decoration: none; }
.tab.active { color: var(--text); border-bottom-color: var(--accent); }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 4px; }

/* ── Profile ─────────────────────────────────────────────────────────────────
   Shared by the profile and its billing tab. They lived in profile.html, which
   is why the billing page rendered them as nothing at all: a class in one
   template's style block does not exist on any other page. */
.pf-head { margin-bottom: 18px; }
.pf-feiten {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px;
  margin-top: 6px; font-size: 12.5px; color: var(--muted);
}
.pf-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px;
  align-items: start; max-width: 1040px;
}
@media (max-width: 900px) { .pf-grid { grid-template-columns: 1fr; } }
.pf-dim { font-size: 11.5px; color: var(--muted); opacity: .8; }

/* ── Lifted out of a template's style block ──────────────────────────────────
   Each of these was used on a second page that could not see it, so the
   element there rendered with no styling at all and nothing said so.
   scripts/test_css_scope.py is what found them. */
.s-hint { font-size: 11px; color: var(--muted); }
.pill{margin-left:auto;font-weight:700;font-size:.82rem;padding:5px 12px;border-radius:20px;white-space:nowrap; display:inline-flex;align-items:center;gap:6px}
.pill .dot{width:8px;height:8px;border-radius:50%}
.pill.online{color:var(--green);background:color-mix(in srgb,var(--green) 13%,transparent)}
.pill.aandacht{color:var(--amber);background:color-mix(in srgb,var(--amber) 14%,transparent)}
.pill.storing{color:var(--red);background:color-mix(in srgb,var(--red) 13%,transparent)}
.pill.uit,.pill.gepauzeerd{color:var(--muted);background:color-mix(in srgb,var(--muted) 12%,transparent)}
.pill.uit .dot,.pill.gepauzeerd .dot{background:var(--muted)}
.pf-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.pf-row .s-input { flex:1; min-width:150px; }

/* ── Control plane ───────────────────────────────────────────────────────────
   Everything above this line is Helios's own stylesheet, copied so the two
   look like one product rather than two that share a colour. That copy is a
   real cost: change something there and this does not follow. It is the price
   of two deployments, and it is cheaper than a dashboard that looks like a
   different company's.

   Below: only what the control plane has and Helios does not. */

/* Amounts line up in a column and read as money. */
.td-amount { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.th-amount { text-align: right; }
.th-center { text-align: center; }
.td-muted  { color: var(--muted); }
.td-actions { white-space: nowrap; text-align: right; }

/* A section with nothing in it says so, quietly. */
.empty { color: var(--muted); font-size: 13px; padding: 6px 0; }

/* The page heading and whatever acts on the whole page. */
.page-head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 20px; }
.page-head h1 { font-size: 20px; margin: 0 0 2px; letter-spacing: -.01em; }
.page-sub { color: var(--muted); font-size: 13px; }
.page-act { margin-left: auto; display: flex; gap: 8px; align-items: center; }

/* Cards in a column, because .s-card carries no margin of its own. */
.stack { display: flex; flex-direction: column; gap: 16px; }

/* A count beside a nav item, for the one number worth seeing from every page. */
.sb-count {
  margin-left: auto; font-size: 11px; font-weight: 650; color: var(--muted);
  background: var(--bg3); border-radius: 99px; padding: 1px 7px;
  font-variant-numeric: tabular-nums;
}
.sb-count.warn { color: var(--amber, #f59e0b); background: rgba(245,158,11,.14); }
.sb-count.bad  { color: var(--red, #ef4444);   background: rgba(239,68,68,.14); }

/* ── The sign-in surface ─────────────────────────────────────────────────────
   Signed out there is no sidebar, no top bar and no table: one small card on a
   very large empty field. Two things made that read as a void rather than as a
   page — nothing in it, and the card pinned to the top with two thirds of a
   tall screen below it. Both are fixed here, and the second one matters more.

   The light is a low dawn above the card. Helios is the sun; a warm horizon is
   the one piece of decoration this page can carry without inventing anything.
   Kept far below where you would notice it as a gradient — it should read as
   the room being lit, not as an effect. Under it a vignette weighs the bottom
   down so the card has something to sit on.

   The grain is not texture for its own sake: a wide, very dark gradient bands
   into visible steps on an 8-bit panel, and a little noise is what breaks the
   bands. Inline as a data URI, because the strict rule here is that a page
   fetches nothing from anywhere. */
body.signin {
  /* How high the sun comes up. One number, on purpose: this is the thing that
     gets adjusted, and it should be adjustable without reading the gradient. */
  --dawn: .19;

  min-height: 100vh;
  min-height: 100dvh;                    /* phones: the bar at the bottom counts */
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  isolation: isolate;                    /* keeps the layers off everything else */
  background:
    radial-gradient(120% 78% at 50% -12%, rgba(249,115,22,var(--dawn)), transparent 60%),
    radial-gradient(90% 60% at 50% 118%, rgba(0,0,0,.45), transparent 62%),
    var(--bg);
}

/* Fine grain over the gradients, and nothing else. */
body.signin::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* The card centres itself; the old 8vh top margin is what pinned it up. */
body.signin > .container { margin: 0; padding: 40px 20px; width: 100%; }

/* A card floating on its own needs to sit slightly forward of the ground. */
body.signin .s-card {
  box-shadow: 0 1px 0 rgba(255,255,255,.03) inset, 0 18px 44px rgba(0,0,0,.45);
}

:root[data-theme="light"] body.signin {
  /* Lower on white: the same warmth reads much louder against a light ground. */
  --dawn: .13;
  background:
    radial-gradient(120% 78% at 50% -12%, rgba(234,88,12,var(--dawn)), transparent 60%),
    radial-gradient(90% 60% at 50% 118%, rgba(16,24,40,.07), transparent 62%),
    var(--bg);
}
:root[data-theme="light"] body.signin::before { opacity: .025; }
:root[data-theme="light"] body.signin .s-card {
  box-shadow: 0 1px 0 rgba(255,255,255,.8) inset, 0 18px 44px rgba(16,24,40,.10);
}

/* Somebody who asked for less movement gets the flat ground. */
@media (prefers-reduced-motion: reduce) {
  body.signin { transition: none; }
}

/* The button on the doorstep — the same one Helios has there.

   An outline rather than a filled block, and the letters in --text rather than
   in the accent. This is the only button on the page, so it does not have to
   announce that it is the important one, and orange on orange reads busier than
   plain text does.

   Its own class rather than .btn-primary with overrides: that rule carries
   !important on three properties, and a fight with !important is a rule nobody
   can read afterwards. The dashboard behind the door keeps .btn-primary — those
   buttons sit among others and match Helios's, which is the point.

   As wide as its own word. A button that takes the full field width reads as a
   bar rather than as something you press. */
.signin-btn {
  display: block; width: fit-content; margin: 18px auto 0;
  padding: 8px 24px;
  background: none; border: 1px solid var(--accent); color: var(--text);
  border-radius: 7px; font-size: 13px; font-weight: 600; cursor: pointer;
  text-decoration: none; text-align: center;
  transition: background .15s, border-color .15s;
}
.signin-btn:hover { background: rgba(249,115,22,.12); }
.signin-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The accent differs per theme, so the tint behind it does too. */
:root[data-theme="light"] .signin-btn:hover { background: rgba(234,88,12,.10); }

/* ── Changelog ───────────────────────────────────────────────────────────────
   The control plane's own release notes, on the operator dashboard. Here and
   not in a <style> block on the page: every other template in this repository
   keeps its classes in this file, and the check that looks for a class with no
   rules behind it only reads this one. */
/* Een lijst, geen tabel: dit wordt gelezen en niet gescand. De datum en het
    nummer staan in een smalle kolom ernaast, zodat de tekst één rustige
    kolom blijft en niet om het nummer heen hoeft te lopen. */
.cl { display: flex; flex-direction: column; gap: 26px; max-width: 760px; }
.cl-rel { display: grid; grid-template-columns: 116px 1fr; gap: 20px; align-items: start; }
.cl-meta { position: sticky; top: 18px; }
.cl-ver { font-size: 14px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.cl-date { font-size: 12px; color: var(--muted); margin-top: 2px; }
/* Waar je nu op draait. Dit is de reden dat deze pagina bestaat, dus het mag
   opvallen — maar één keer, en verder niets. */
.cl-now { display: inline-block; margin-top: 6px; font-size: 11px; font-weight: 600;
          color: var(--accent-text); border: 1px solid var(--accent);
          border-radius: 999px; padding: 1px 8px; }
.cl-lines { display: flex; flex-direction: column; gap: 12px; }
.cl-line { display: grid; grid-template-columns: 62px 1fr; gap: 12px; align-items: start; }
.cl-kind { font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
           padding: 2px 0; color: var(--muted); }
.cl-kind.feat   { color: #22c55e; }
.cl-kind.fix    { color: #f59e0b; }
.cl-kind.change { color: var(--accent-text); }
.cl-text { font-size: 13.5px; line-height: 1.65; color: var(--text); }
.cl-rel + .cl-rel { border-top: 1px solid var(--border); padding-top: 26px; }
@media (max-width: 700px) {
  .cl-rel { grid-template-columns: 1fr; gap: 8px; }
  .cl-meta { position: static; display: flex; align-items: baseline; gap: 10px; }
  .cl-date { margin-top: 0; }
}

/* ── Processes ───────────────────────────────────────────────────────────────
   Everything that is supposed to be running. Not a list of test results: those
   prove something about the source and cannot change between deploys, and a
   page of forty always-green lights is one you stop reading. */
.pr-h { font-size: 14px; font-weight: 700; color: var(--text); margin: 0 0 4px; }
.pr-sub { font-size: 12.5px; color: var(--muted); margin: 0 0 16px; line-height: 1.6; max-width: 640px; }
/* De toestandsuitleg onder de badge, en de druk onder de notitie. Klein en
   grijs: het staat er om een woord te verklaren, niet om ernaast te concurreren. */
.pr-why { margin-top: 3px; font-size: 11px; color: var(--muted); line-height: 1.4; }

.pr-rows { display: flex; flex-direction: column; }
.pr-row { display: flex; gap: 16px; padding: 9px 0; border-bottom: 1px solid var(--border);
          font-size: 13px; align-items: baseline; }
.pr-row:last-child { border-bottom: none; }
.pr-label { color: var(--muted); min-width: 190px; }
.pr-val { color: var(--text); font-variant-numeric: tabular-nums; }
.pr-warn { color: var(--yellow); }

@media (max-width: 640px) {
  .pr-row { flex-direction: column; gap: 2px; }
  .pr-label { min-width: 0; }
}

/* ── Instances ───────────────────────────────────────────────────────────────
   The container chips carry the state; the sentence underneath only appears
   when there is something to say. A row where everything is fine should be
   quiet, or the one row that is not fine has nothing to stand out against. */
.inst-name { font-size: 13.5px; font-weight: 650; color: var(--text); text-decoration: none; }
.inst-name:hover { color: var(--accent-text); }
.inst-host { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
             font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.inst-co { font-size: 13px; color: var(--text); }
.inst-mail { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.inst-h { font-size: 14px; font-weight: 700; color: var(--text); margin: 0 0 4px; }
.inst-sub { font-size: 12.5px; color: var(--muted); margin: 0 0 14px; line-height: 1.6; max-width: 620px; }

.cn-list { display: flex; flex-direction: column; }
/* Vier kolommen die uitlijnen over de regels heen, zodat je verticaal kunt
   lezen: alle versies onder elkaar, alle tijden onder elkaar. Vaste breedtes,
   want een grid dat per rij anders uitvalt leest als vier losse regels.

   De versiekolom is ruim genoeg voor `16-alpine` — te krap en hij brak over
   twee regels, en dan moest de regelhoogte dat opvangen voor alle drie. Ruimte
   in de breedte is hier ruimte in de hoogte: de drie regels passen nu in de
   hoogte die één rij van de tabel toch al heeft. */
/* Six columns, because the line has six cells. It had five, so the sixth — the
   note about throttling or a memory kill — wrapped onto a line of its own
   underneath the containers, where it read as a remark about the instance
   instead of about the one container it belongs to. */
.cn { display: grid;
      grid-template-columns: 9px 132px 74px 58px 66px 62px 62px 104px auto;
      align-items: baseline;
      gap: 8px; font-size: 11.5px; line-height: 1.25; white-space: nowrap; }
.cn-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted);
          align-self: center; }
/* Breed genoeg voor `evolution-redis`, niet voor `scheduler`: onze eigen
   containers dragen hun hele naam en die van een instance alleen de dienst.
   Was 104px, gemeten aan `edge-caddy-1`, en toen de gedeelde stapel erbij kwam
   stond er `evolution-r…` — een naam die je niet kunt opzoeken. Met nowrap
   erboven liep een te lange naam gewoon over de versie heen in plaats van af
   te breken, dus staat er ook een afkapper op, en de hele naam staat op de
   hover: dat hoort niet nodig te zijn, maar als het gebeurt is het leesbaar. */
.cn-name { color: var(--text); font-weight: 600;
           min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cn-tag { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
          color: var(--muted); font-variant-numeric: tabular-nums; }
.cn-mem { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
           color: var(--muted); font-variant-numeric: tabular-nums; }
/* De piek kwam boven de 80% van de limiet. Niet rood: er is niets stuk, maar
   het is wel het getal waarvoor die limiet er staat. */
.cn-near { color: var(--yellow); font-weight: 600; }
.cn-since { color: var(--muted); white-space: nowrap; }
/* Getallen rechts, tekst links. Onder elkaar lezen alleen cijfers die op hun
   eenheden uitlijnen: 9M en 512M linksuitgelijnd staan naast elkaar, niet
   onder elkaar. De kopjes volgen dezelfde kant, anders wijst de kop niet naar
   de kolom eronder. */
.cn-cpu, .cn-mem, .cn-cache, .cn-limit { text-align: right; }
.cn-head span:nth-child(4), .cn-head span:nth-child(5),
.cn-head span:nth-child(6), .cn-head span:nth-child(7) { text-align: right; }
.cn-cpu { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
          color: var(--muted); font-variant-numeric: tabular-nums; }
.cn-cache { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
            color: var(--muted); font-variant-numeric: tabular-nums; }
.cn-limit { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
            color: var(--muted); font-variant-numeric: tabular-nums; }
.cn-note { color: var(--yellow); min-width: 0; overflow: hidden;
           text-overflow: ellipsis; }
.cn-green .cn-dot { background: var(--green); }
.cn-warn  .cn-dot { background: var(--yellow); }
.cn-warn  .cn-since { color: var(--yellow); }
.cn-red   .cn-dot { background: var(--red); }
.cn-red   .cn-since { color: var(--red); font-weight: 600; }
.cn-gray  .cn-dot { background: var(--muted); }
/* An observation that is minutes old is not a state, so it is shown as doubt
   rather than as a colour: the line fades instead of changing what it claims. */
.cn-stale { opacity: .5; }
.cn-mixed { margin-top: 4px; font-size: 11.5px; color: var(--yellow); line-height: 1.5; }
/* Bijwerken lukt niet: de zin in de kop verandert, niet de chips. Die tonen nog
   steeds wat we het laatst zeker wisten, en dat blijft waar. */
.cn-lost { color: var(--yellow); }
/* Hetzelfde getal als bij een container, en met opzet dezelfde vorm: een dienst
   die op de rem staat is hetzelfde feit of hij nu in een container draait of
   onder systemd. */
.pr-mem { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
          font-variant-numeric: tabular-nums; }
.pr-near { color: var(--yellow); font-weight: 600; }

/* ── De machine zelf ─────────────────────────────────────────────────────────
   Bovenaan Processen. Hier is de host het juiste onderwerp: hij is van ons, en
   alles op die pagina deelt precies deze ene bak — anders dan binnen een
   klantcontainer, waar dezelfde getallen ruimte zouden suggereren die niet van
   hem is. */
.sum { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
      gap: 12px; margin-bottom: 18px; }
.sum-tile { background: var(--bg2); border: 1px solid var(--border); border-radius: 10px;
           padding: 14px 16px; }
.sum-label { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
            color: var(--muted); }
.sum-big { font-size: 26px; font-weight: 700; color: var(--text); line-height: 1.2;
          margin-top: 4px; font-variant-numeric: tabular-nums; }
.sum-sub { font-size: 12px; color: var(--muted); line-height: 1.5; }
.sum-dim { margin-top: 6px; font-size: 11px; opacity: .85; }
.sum-bar { height: 4px; border-radius: 999px; background: var(--bg3); margin-top: 10px;
          overflow: hidden; }
.sum-bar span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.sum-hot span { background: var(--yellow); }

/* Geld. Tabulaire cijfers zodat bedragen onder elkaar uitlijnen — met gewone
   cijfers schuift een 1 alles op en kun je een kolom niet meer optellen met je
   oog. Nul staat grijs: een bedrag van niets hoort niet even hard te roepen. */
.eur { font-variant-numeric: tabular-nums; white-space: nowrap; }
.eur-nil { color: var(--muted); }
.cu-tier { font-size: 12.5px; color: var(--text); }
.cu-sub { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.cu-warn { color: var(--yellow); }

/* ── Tabbalk op een detailpagina ─────────────────────────────────────────────
   Eén niveau, horizontaal. Het onderstreepje draagt de keuze en niet een
   achtergrondvlak: een gekozen tabblad hoort deel van de pagina te zijn en niet
   een knop die erop ligt. */
.ctabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border);
         margin-bottom: 18px; flex-wrap: wrap; }
.ctab { background: none; border: none; border-bottom: 2px solid transparent;
        color: var(--muted); font-size: 13px; font-weight: 600; cursor: pointer;
        padding: 9px 14px; margin-bottom: -1px; font-family: inherit;
        transition: color .15s, border-color .15s; }
.ctab:hover { color: var(--text); }
.ctab.active { color: var(--text); border-bottom-color: var(--accent); }
.ctab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Het paneel zelf draagt geen opmaak — het is een houder, en de kaarten erin
   brengen hun eigen. Het staat er omdat het getoond en verborgen wordt, en een
   klasse zonder regels leest als een vergeten klasse. */
.ctab-panel { display: block; }

/* Een regel die ergens heen gaat. De cursor zegt het, de achtergrond bevestigt
   het onder de muis, en focus is zichtbaar voor wie met het toetsenbord komt. */
.row-link { cursor: pointer; }
.row-link:hover td { background: var(--bg3); }
.row-link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The live jobs panel: one grid row per job — name, instance, bar, percentage.
   The bar animates its width so a step-change reads as movement, and the
   queued/indeterminate variants stay quiet: grey for waiting, a soft pulse for
   a job that runs but does not measure itself. */
.livejob { display: grid;
           grid-template-columns: minmax(170px, 1.3fr) minmax(110px, .9fr) 108px 108px 1.6fr 56px;
           gap: 14px; align-items: center; padding: 10px 0; }
.livejob + .livejob { border-top: 1px solid var(--border); }
/* The column header stands before the first job arrives, and the body keeps
   standing room for a handful of rows so the page does not jump when one lands. */
.lj-head { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
           color: var(--muted); padding: 4px 0 8px; border-bottom: 1px solid var(--border); }
.lj-body { min-height: 170px; }
.lj-body .livejob:first-child { border-top: 0; }
.lj-empty { display: flex; align-items: center; justify-content: center; min-height: 170px; }
.lj-name { font-weight: 600; }
.lj-note { font-size: 11.5px; margin-top: 2px; }
.lj-inst { font-size: 13px; }
.lj-tijd { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.lj-bar { height: 8px; border-radius: 5px; background: var(--bg3); overflow: hidden; }
.lj-bar span { display: block; height: 100%; border-radius: 5px; background: var(--accent);
               transition: width .6s ease; }
/* A running fill carries a moving sheen: even while a long step holds one
   percentage, the bar visibly lives — a still bar reads as a hung job. */
.lj-bar .lj-run { background-image: linear-gradient(100deg, transparent 30%,
                    rgba(255,255,255,.28) 50%, transparent 70%);
                  background-size: 220% 100%;
                  animation: lj-sheen 1.8s linear infinite; }
@keyframes lj-sheen { from { background-position: 130% 0; } to { background-position: -90% 0; } }
@media (prefers-reduced-motion: reduce) { .lj-bar .lj-run { animation: none; } }
.lj-bar-queued span { width: 0; }
.lj-bar-busy span { opacity: .45; animation: lj-pulse 1.6s ease-in-out infinite; }
@keyframes lj-pulse { 0%, 100% { opacity: .25; } 50% { opacity: .6; } }
.lj-pct { font-size: 12.5px; font-variant-numeric: tabular-nums; text-align: right; }

/* The status strip on the overview: one tile per section, its colour the worst
   thing that section would tell you. Tiles are doors — the whole card is the
   link — and the accent lives in a thin edge so eight tiles do not shout. */
.ov-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
            gap: 12px; margin-bottom: 16px; }
.ov-tile { display: block; padding: 12px 14px; border: 1px solid var(--border);
           border-radius: 10px; background: var(--bg2); text-decoration: none;
           border-left-width: 3px; }
.ov-tile:hover { background: var(--bg3); }
.ov-green { border-left-color: #22c55e; }
.ov-warn  { border-left-color: #f59e0b; }
.ov-red   { border-left-color: #ef4444; }
.ov-gray  { border-left-color: var(--border); }
.ov-label { font-size: 11px; font-weight: 700; letter-spacing: .08em;
            text-transform: uppercase; color: var(--muted); }
.ov-big { font-size: 16px; font-weight: 700; color: var(--text); margin: 4px 0 2px;
          font-variant-numeric: tabular-nums; }
.ov-sub { font-size: 12px; color: var(--muted); }

/* The restore button: quiet until you reach for it — the house style for a
   dangerous control. Red is the hover, not the resting state. */
.btn-danger { background: var(--bg3); color: var(--text); border: 1px solid var(--border); }
.btn-danger:hover { background: rgba(239,68,68,.12); border-color: #ef4444; color: #ef4444; }
.restore-details { display: inline-block; text-align: right; }


/* Our own containers head the instance list. A hairline underneath keeps the
   distinction with the customers without making it a second table — one list
   is the point: Caddy falling over takes every instance with it. */
.inst-ours > td { border-bottom: 2px solid var(--border); background: var(--bg2); }
.inst-ours .inst-name { color: var(--text); }

/* The names above those values. Same grid as a container line, so the labels
   stand over the columns they name; a header measured differently from its
   rows is worse than no header.

   Typography is INHERITED from the table header it sits in, not restated: it
   had its own size, weight and letter-spacing, so one row of column names came
   out in two typefaces — Instance/Klant/Toestand at 12px and Dienst/Versie/...
   at 10.5px. A header that looks like two headers is not a header. */
/* No padding and the same line-height as the cells around it: the header sat
   in its own block inside the th, so its baseline landed a few pixels off the
   plain-text headers beside it. */
.cn-head { font: inherit; color: inherit; letter-spacing: inherit;
           text-transform: inherit; line-height: inherit; }
.cn-head span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* Een limiet die je kunt wijzigen ziet er ook zo uit: een stippellijn eronder
   in plaats van een knop, want het is een waarde en geen actie. */
.cn-edit { cursor: text; border-bottom: 1px dashed var(--border); }
.cn-edit:hover, .cn-edit:focus { border-bottom-color: var(--accent); color: var(--text); }
.cn-limit-input { width: 100%; box-sizing: border-box; text-align: right;
                  font: inherit; font-variant-numeric: tabular-nums;
                  color: var(--text); background: var(--bg); padding: 0 2px;
                  border: 1px solid var(--accent); border-radius: 3px; }

/* ── Wat een instance van de schijf gebruikt, tegen wat hij mag ──────────────
   Twee getallen en een balk. De getallen zijn het antwoord, de balk is de
   verhouding die je anders zelf moet uitrekenen terwijl je langs de lijst
   scrollt. Kleur pas als het iets betekent: grijs is de normale toestand en
   moet er ook zo uitzien, anders leest niemand de amber nog. */
.quota-cell { vertical-align: middle; }
.quota { display: flex; flex-direction: column; gap: 5px; }
.quota-nums { display: flex; align-items: baseline; gap: 5px;
          font-variant-numeric: tabular-nums; white-space: nowrap; }
.quota-used { color: var(--text); }
.quota-sep  { color: var(--muted); }
.quota-cap  { color: var(--muted); }
.quota-unit { color: var(--muted); font-size: 11.5px; }
/* Het invoerveld verschijnt in een span die net zo breed is als het getal
   dat er stond: 100% is daar bijna niets, en er paste één cijfer in. Hier
   krijgt het dus een eigen maat, breed genoeg voor '500m'. */
.quota-nums .cn-limit-input { width: 52px; text-align: left; }
.quota-bar  { height: 3px; border-radius: 2px; background: var(--bg3); overflow: hidden; }
.quota-bar > i { display: block; height: 100%; background: var(--muted);
             border-radius: 2px; transition: width .25s ease; }
.quota-bar.quota-full > i { background: var(--yellow); }
.quota-bar.quota-over > i { background: var(--red); }

/* Een melding die vanzelf weggaat. Neemt geen plek in de opmaak in — hij komt
   over de pagina heen en verdwijnt weer, want hij hoort bij een handeling en
   niet bij de pagina. */
.cn-toast { position: fixed; right: 18px; bottom: 18px; z-index: 60;
            max-width: 340px; margin: 0; opacity: 0; transform: translateY(6px);
            pointer-events: none; transition: opacity .18s ease, transform .18s ease; }
.cn-toast-on { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .quota-bar > i, .cn-toast { transition: none; }
}

/* De reden dat een job mislukte. De enige tekst in die kolom die er staat als
   er iets te melden valt, dus hij mag gelezen worden — maar niet schreeuwen:
   de rode pil ernaast draagt het signaal al. */
.job-fout { color: var(--red); font-size: 12px; }
/* Waar een job over ging, naast de soort. Met lucht ertussen: "upgrade→0.118.0"
   leest als één woord. */
.job-doel { color: var(--muted); margin-left: 6px; }

/* Het vinkje waarmee een instance meegaat in een uitrol. Iets groter dan de
   browserstandaard en met de accentkleur, want het staat in een tabel waar de
   rest van de regel klikbaar is: het moet zichtbaar iets ánders zijn dan de
   rij eromheen. */
.roll-pick {
  width: 15px; height: 15px; cursor: pointer;
  accent-color: #3b82f6; vertical-align: middle;
}
