﻿/*
    Autonomycs.Admin.Web - the app's own chrome.

    Tokens, primitives (.btn, .card, .page, .row, .field, .badge, .signedout, ...) and the Radzen
    skin live in Autonomycs.Ui.Web and are linked BEFORE this file. What is here is the shell the
    design system deliberately does not own: "every app's navigation is its own", so the sidebar, the
    top bar and this portal's screen families stay app-side.

    Desktop-first, 1280-2560px, per the design brief. There is no mobile drawer - this is a tool used
    at a workstation, not a phone portal, and pretending otherwise would mean designing a
    twelve-destination grouped sidebar for a 375px screen nobody will use it on.
*/

/* ----------------------------------------------------------------- shell */

.shell {
    display: flex;
    height: 100vh;
    width: 100%;
    overflow: hidden;
}

/*
    Wins its tie with .shell purely by loading later, which is why it cannot move into ui.css: there
    it would lose to .shell at equal specificity and the sign-in screen would try to lay out a
    sidebar that is not there.

    A flex COLUMN with a viewport height, not a plain block, and both halves are load-bearing.
    .signedout (ui.css) centres itself with `margin: auto`, which only centres vertically inside a
    flex container that has a height to centre within - on a block parent those auto margins collapse
    to zero and the panel sits at the top. The height is also what makes --canvas cover the viewport
    rather than stopping at the panel's 560px.
*/
.shell--bare {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    overflow: visible;
    background: var(--canvas);
}

/*
    Breathing room for the panelled screens only. The sign-in screen is full-bleed brand green by
    design, so padding on the shell itself would inset it and leak a canvas-coloured border around
    it. :has() scopes the inset to the screens that want it without needing the page to tell the
    layout which one it is - a layout takes no parameters from the page it renders.

    Where :has() is unsupported the rule is simply dropped: the panel loses its inset on a short
    window and touches the viewport edges. Cosmetic, and the centring above does not depend on it.
*/
.shell--bare:has(.signedout) {
    padding: 40px 20px;
}

.shell__main {
    flex: 1;
    min-width: 0;
    max-width: 100%;
    overflow-x: hidden;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

.shell__body {
    flex: 1;
    width: 100%;
    min-width: 0;
    padding: 30px 36px 56px;
}

/* ----------------------------------------------------------------- sidebar */

/*
    The hamburger, and the sheet behind an open drawer. Both are display:none until the media query
    at the bottom of this file turns them on - above the breakpoint the sidebar is simply always
    there and neither exists.
*/
.navtoggle {
    display: none;
    flex: none;
    align-items: center;
    justify-content: center;
    padding: 8px;
    border: 1px solid var(--card-border);
    border-radius: var(--radius-control);
    background: transparent;
    color: var(--brand);
    cursor: pointer;
}

.navtoggle:hover {
    background: var(--canvas);
}

.sidebar-backdrop {
    display: none;
}

.sidebar {
    width: var(--sidebar-width);
    flex: none;
    background: var(--brand);
    /* Every icon and the brand mark draw with currentColor, so setting it once here tints all of
       them. Without it the mark renders black on green. */
    color: #fff;
    padding: 26px 18px 18px;
    display: flex;
    flex-direction: column;
    gap: 24px;
    height: 100%;
    overflow-y: auto;
}

.sidebar__brand {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 0 8px;
}

/* The wordmark and the version stacked beside the mark. Together they are about as tall as the
   40px mark, which is what lets the version sit there without pushing the row taller. */
.sidebar__title {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    min-width: 0;
}

.sidebar__wordmark {
    font: 500 19px/1.1 var(--font);
    letter-spacing: -.01em;
}

/* A button drawn as a caption: the version, and the way into the changelog. No chrome and no
   padding, so it lines up under the wordmark's first letter; muted like the role line, white on
   hover so it declares itself clickable. font and color are explicit because a <button> inherits
   neither; ui.css only hands it the family. */
.sidebar__version {
    border: 0;
    background: transparent;
    padding: 0;
    cursor: pointer;
    font: 400 12px/1.2 var(--font);
    font-variant-numeric: tabular-nums;
    color: rgba(255, 255, 255, .62);
    white-space: nowrap;
}

.sidebar__version:hover,
.sidebar__version:focus-visible {
    color: #fff;
}

.sidebar__version:focus-visible {
    outline: 2px solid rgba(255, 255, 255, .7);
    outline-offset: 3px;
    border-radius: 2px;
}

.sidebar__nav {
    display: flex;
    flex-direction: column;
    gap: 22px;
}

.navgroup__label {
    display: block;
    padding: 0 16px 8px;
    font: 500 12px/1 var(--font);
    color: rgba(255, 255, 255, .5);
    text-transform: uppercase;
    letter-spacing: .12em;
}

.navgroup__items {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.navlink {
    position: relative;
    display: flex;
    align-items: center;
    gap: 13px;
    padding: 12px 16px;
    border-radius: var(--radius-control);
    cursor: pointer;
    text-align: left;
    color: rgba(255, 255, 255, .88);
    font: 400 16px/1.25 var(--font);
    text-decoration: none;
}

.navlink:hover {
    background: rgba(255, 255, 255, .1);
    color: #fff;
}

.navlink--active {
    background: rgba(255, 255, 255, .16);
    color: #fff;
}

.navlink__icon {
    flex: none;
    display: flex;
    opacity: .8;
}

.navlink__label {
    flex: 1;
    min-width: 0;
}

.sidebar__identity {
    margin-top: auto;
    border-top: 1px solid rgba(255, 255, 255, .18);
    padding-top: 14px;
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
}

.identity__avatar {
    flex: none;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(255, 255, 255, .18);
    display: flex;
    align-items: center;
    justify-content: center;
    font: 500 15px/1 var(--font);
}

.identity__text {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.identity__name {
    font: 500 16px/1.2 var(--font);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.identity__role {
    font: 400 13px/1.3 var(--font);
    color: rgba(255, 255, 255, .62);
}

/* ----------------------------------------------------------------- top bar */

.topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    flex: none;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 16px 24px;
    padding: 16px 36px;
    background: #fff;
    border-bottom: 1px solid var(--hairline);
}

.topbar__context {
    display: flex;
    align-items: center;
    gap: 14px;
    min-width: 0;
}

.topbar__label {
    font: 500 12px/1 var(--font);
    color: var(--muted-soft);
    text-transform: uppercase;
    letter-spacing: .12em;
}

.topbar__actions {
    display: flex;
    align-items: center;
    gap: 14px;
}

.housepicker {
    display: flex;
    align-items: center;
    background: var(--canvas);
    border: 1px solid var(--card-border);
    border-radius: var(--radius-control);
    cursor: pointer;
}

.housepicker:hover {
    border-color: var(--olive);
}

/* The same blending .langpicker does in ui.css: the pill is the control's chrome, so the control
   drops its own. The hover/focus selectors are not optional - the theme's hover rule ADDS a border
   at higher specificity, and a pill that grows a pixel on mouse-over shifts the whole top bar. */
.housepicker .rz-dropdown,
.housepicker .rz-dropdown:not(.rz-state-disabled):hover,
.housepicker .rz-dropdown:not(.rz-state-disabled):focus-within,
.housepicker .rz-dropdown.rz-state-focused {
    padding: 11px 16px;
    border: 0;
    border-block-end: 0;
    background: transparent;
    box-shadow: none;
    height: auto;
    min-height: 0;
}

/* Wide enough for the longest house name AND the longest placeholder - "Sélectionnez un health
   house" - on one line, so picking one does not resize the top bar and nothing is cut before a
   house is chosen. A separate rule from the state group above, matching .langpicker, so a narrower
   override never has to out-rank the hover/focus selectors as well. */
.housepicker .rz-dropdown {
    min-width: 300px;
}

/* The end padding is not optional. Radzen's own label reserves room for the chevron
   (padding-inline-end of twice the icon width), and a bare `padding: 0` threw that away - so the
   placeholder ran under the arrow. The trigger is a 24px glyph with an 8px margin against the
   pill's edge, 16px of which is the pill's own padding; 24px here keeps the text clear of it at
   any width, and the ellipsis lands before the arrow rather than beneath it. */
.housepicker .rz-dropdown .rz-dropdown-label,
.housepicker .rz-dropdown .rz-placeholder {
    padding: 0 24px 0 0;
    font: 500 17px/1.2 var(--font);
    color: var(--ink);
}

/* No selection yet: the placeholder reads as a prompt rather than as a chosen value. */
.housepicker .rz-dropdown .rz-placeholder {
    color: var(--muted);
}

.housepicker .rz-dropdown .rzi {
    color: var(--muted);
}

/* The one-house case: the name where the pill would be, in the pill's type, with no chrome and no
   chevron - there is nothing to open. The block padding is the pill's 11px plus its 1px border, so
   the top bar is the same height whether a person sees one house or a picker. */
.housename {
    padding: 12px 0;
    font: 500 17px/1.2 var(--font);
    color: var(--ink);
    white-space: nowrap;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ----------------------------------------------------------------- environment banner */

/*
    Above the top bar, full width, impossible to scroll away from - it is sticky with the rest of the
    header stack. Yellow rather than the brand orange on purpose: orange is this design's single
    action colour and a strip of it reads as something to click.
*/
.envbanner {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 8px 36px;
    background: var(--warning);
    color: var(--ink);
    font: 500 14px/1.2 var(--font);
    letter-spacing: .02em;
}

.envbanner__dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ink);
    opacity: .55;
}

/* ----------------------------------------------------------------- health house gate */

.gate {
    max-width: 620px;
    margin: 60px auto 0;
    text-align: center;
}

.gate__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 20px;
    background: #fff;
    border: 1px solid var(--card-border);
    color: var(--brand);
}

.gate__title {
    margin: 22px 0 10px;
    font: 500 30px/1.15 var(--font);
    letter-spacing: -.02em;
}

.gate__lead {
    margin: 0 0 26px;
    font: 400 17px/1.6 var(--font);
    color: var(--muted);
    text-wrap: pretty;
}

.gate__choices {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
}

.gate__choice {
    border: 1px solid var(--hairline-strong);
    background: #fff;
    color: var(--deep);
    padding: 14px 22px;
    border-radius: var(--radius-control);
    font: 500 16px/1 var(--font);
    cursor: pointer;
}

.gate__choice:hover {
    background: var(--brand);
    color: #fff;
}

/* ----------------------------------------------------------------- sign-in */

.login {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    width: 100%;
    padding: 40px;
    background: var(--brand);
}

.login__card {
    width: 100%;
    max-width: 470px;
    background: #fff;
    border-radius: var(--radius-card-lg);
    padding: 44px 46px 38px;
    box-shadow: 0 24px 60px rgba(28, 26, 18, .24);
}

.login__mark {
    display: inline-flex;
    padding: 9px;
    border-radius: var(--radius-control);
    background: var(--brand);
    color: #fff;
}

.login__title {
    margin: 26px 0 8px;
    font: 500 32px/1.1 var(--font);
    letter-spacing: -.02em;
}

.login__lead {
    margin: 0 0 30px;
    font: 400 17px/1.55 var(--font);
    color: var(--muted);
    text-wrap: pretty;
}

/* Layout only: the colour is .btn--primary's, because orange is the one action colour and the
   sign-in button is not an exception to it. The external button is .btn--ghost - the same third
   weight the top bar's sign-out uses - so the two read as one primary way in and one alternative. */
.login__button {
    width: 100%;
    justify-content: center;
}

.login__actions {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Under the external button: who it is for, in one line. */
.login__hint {
    margin: -4px 0 0;
    font: 400 14px/1.5 var(--font);
    color: var(--muted-soft);
    text-align: center;
}

.login__foot {
    margin: 30px 0 0;
    padding-top: 22px;
    border-top: 1px solid var(--hairline);
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.login__help {
    margin: 0;
    font: 400 15px/1.5 var(--font);
    color: var(--muted-soft);
}

/*
    The directory's own error text, verbatim. Monospaced and wrapped rather than styled, because it
    is the only evidence of what went wrong - the browser has already left this app by the time the
    directory refuses anything, so nothing here logged it.
*/
.authfailure {
    margin: 16px 0 0;
    padding: 12px 14px;
    border-radius: var(--radius-control);
    background: var(--surface-neutral);
    border: 1px solid var(--border);
    font: 400 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--deep);
    white-space: pre-wrap;
    word-break: break-word;
}

/* ----------------------------------------------------------------- additions to the primitives */

/*
    A bordered, transparent button. ui.css has .btn and .btn--primary; the top bar's sign-out needs
    the third weight the artboards use. Kept here until a second app needs it, which is when it earns
    a place in the design system rather than before.
*/
.btn--ghost {
    border: 1px solid var(--hairline-strong);
    background: transparent;
    color: var(--brand);
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

.btn--ghost:hover {
    background: var(--canvas);
}

/* ----------------------------------------------------------------- dashboard */

.dash {
    max-width: 1520px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 24px;
}

.dash__head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px 24px;
}

/*
    The right side of a head, once it holds more than one thing. A lone button needs no wrapper; a
    button beside an <a class="btn"> does, because an anchor is inline and only lines up with the
    button once both are flex items.
*/
.dash__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

/* The one figure on the screen that answers "is there anything to do at all?" - so it sits beside
   the title rather than in the tile row, where it would read as a fifth statistic. */
.dash__pill {
    padding: 9px 16px;
    border-radius: 999px;
    background: var(--surface-orange);
    color: var(--rust);
    font: 500 15px/1 var(--font);
}

/* 470px rather than a column count: two cards fit side by side from about 1000px and the rows
   inside them stay readable, which a three-up layout on a 1280px screen does not.

   auto-fill, not auto-fit. Cards with nothing in them are not drawn, so on a quiet day one or two
   are all that is left - and auto-fit collapses the empty tracks, stretching a lone card across
   the full 1520px with its rows sprawling to match. auto-fill keeps the tracks, so the last card
   standing is exactly as wide as it was with five beside it. */
.dash__cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(470px, 1fr));
    gap: 20px;
    align-items: start;
}

/* The all-clear: what replaces the cards grid once every queue has loaded and come back empty. The
   same anatomy as .gate (icon tile, title, lead) and deliberately not the same class - the gate is
   a blocker somebody has to get past, this is the reward for having nothing to get past, and one
   family serving both would have every tweak to one land on the other. The tile is the sand tint,
   which appears nowhere else on this screen. */
.allclear {
    max-width: 620px;
    margin: 48px auto 0;
    text-align: center;
}

.allclear__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 20px;
    background: var(--surface-gold);
    color: var(--brand);
}

.allclear__title {
    margin: 22px 0 10px;
    font: 500 30px/1.15 var(--font);
    letter-spacing: -.02em;
}

.allclear__lead {
    margin: 0;
    font: 400 17px/1.6 var(--font);
    color: var(--muted);
    text-wrap: pretty;
}

/* Every card is gone but the questions - which have a tile and no card - are still waiting. One
   line where the grid was, pointing at the screen that has them. */
.allclear__only {
    margin: 24px 0 0;
    text-align: center;
    font: 400 17px/1.5 var(--font);
    color: var(--muted);
}

.allclear__only a {
    color: var(--brand);
    text-decoration: none;
}

.allclear__only a:hover {
    text-decoration: underline;
}

.dashcard {
    background: #fff;
    border: 1px solid var(--card-border);
    border-radius: 18px;
    padding: 22px 24px 12px;
}

.dashcard__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 12px;
    padding-bottom: 14px;
}

.dashcard__icon {
    width: 34px;
    height: 34px;
    flex: none;
    border-radius: 10px;
    background: var(--canvas);
    color: var(--brand);
    display: flex;
    align-items: center;
    justify-content: center;
}

.dashcard__title {
    margin: 0;
    flex: 1;
    min-width: 0;
    font: 500 20px/1.25 var(--font);
}

.dashcard__count {
    padding: 5px 11px;
    border-radius: 999px;
    background: var(--canvas);
    color: var(--deep);
    font: 500 14px/1.2 var(--font);
}

/* The tint is the signal, so it is reserved for the cards where the number means somebody is
   waiting - not applied to every count, which would make it mean nothing. */
.dashcard__count--urgent {
    background: var(--surface-orange);
    color: var(--rust);
}

.dashcard__tabs {
    display: flex;
    gap: 6px;
    padding: 8px 0 4px;
}

.dashcard__foot {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px 16px;
    padding: 14px 0 10px;
    border-top: 1px solid var(--hairline);
}

/*
    An opened card scrolls its own rows.

    Without the cap, opening a queue of two hundred pushes the five cards below it a screen and a
    half down - which is the thing the five-row default exists to prevent, reintroduced by the button
    that undoes it. Same overhang allowance as .scrolllist, for the same reason.
*/
.dashcard__rows--all {
    max-height: 460px;
    overflow-y: auto;
    padding: 0 10px;
    margin: 0 -10px;
}

/* The remainder as a control. Sized and coloured as the link beside it, so the footer reads as one
   row of two choices rather than a button and an afterthought. */
.dashcard__more--button {
    border: 0;
    background: transparent;
    padding: 0;
    cursor: pointer;
    font: 500 16px/1 var(--font);
    color: var(--brand);
    text-decoration: underline;
}

.dashcard__more--button:hover {
    color: var(--accent);
}

.dashcard__link {
    font: 500 16px/1 var(--font);
}

/* ----------------------------------------------------------------- dashboard rows */

/*
    The negative margin widens the hover band to the card's padding edge, so the highlight reads as
    a full-width row rather than a floating strip - while the text stays aligned with the heading
    above it.
*/
/*
    ONE LINE, ALWAYS. The row wrapped before, and the thing it wrapped was whatever came last - the
    action button, or the date and the button together. Three cards abreast makes each card narrower
    than two does, so the widest screens were the ones where a queue row broke in half and its button
    sat alone underneath.

    Wrapping is the wrong give anyway: the button is why the row exists, and the date and the amount
    are columns that only mean something lined up with the rows above. The text is what has slack, so
    the text is what truncates.
*/
.dashrow {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 16px;
    padding: 14px 10px;
    margin: 0 -10px;
    border-top: 1px solid var(--hairline);
    border-radius: 10px;
}

.dashrow:hover {
    background: var(--surface-neutral);
}

/* Basis 0, not a pixel width: a flex line is broken up using the items' bases before any shrinking
   is considered, so a basis is a wrapping threshold as much as a size. Two of these share a row on
   the waiting list, and 190px each was enough to push the buttons off it. */
.dashrow__main {
    flex: 1 1 0;
    min-width: 0;
}

/* What gives way when the row is tight. Reset on .dashrow__title--note, which is prose rather than a
   label and needs its own wrapping in both of its states. */
.dashrow__title,
.dashrow__sub {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dashrow__title {
    margin: 0;
    font: 500 17px/1.3 var(--font);
}

.dashrow__sub {
    margin: 3px 0 0;
    font: 400 15px/1.35 var(--font);
    color: var(--muted);
}

.dashrow__sub--numeric {
    color: var(--muted-soft);
    font-variant-numeric: tabular-nums;
}

/* Structured payment references are long strings of digits that only line up when the figures are
   the same width. */
.dashrow__ref {
    flex: 1 1 200px;
    min-width: 0;
    font: 400 16px/1.35 var(--font);
    font-variant-numeric: tabular-nums;
    color: var(--deep);
    overflow-wrap: anywhere;
}

/*
    The trailing cells are COLUMNS, and columns need a width.

    A row is flex with one growing cell and the rest sized to their content, so every trailing cell's
    left edge depended on the total width of the ones after it - and the last of them is a status
    pill whose text changes per row. "Betaald" is narrower than "Openstaand", so the amount above it
    sat a few pixels further right, and a column of figures came out ragged. Same for the dates
    beside a mail's status.

    A min-width holds the column while still letting an unusually long value push out rather than
    truncate; right-alignment keeps the digits under each other whatever the width ends up being.
*/
.dashrow__amount {
    flex: none;
    min-width: 92px;
    text-align: right;
    font: 500 17px/1.3 var(--font);
    font-variant-numeric: tabular-nums;
}

/* What is still due, under the amount of a request that has been paid in part - the one case where
   the amount alone misleads. A second line rather than a second column: the figure people compare
   against a statement stays the request's own. */
.dashrow__outstanding {
    display: block;
    font: 400 13px/1.3 var(--font);
    color: var(--muted);
    white-space: nowrap;
}

/* margin: 0 because the dashboard's missing-measurements queue stacks this as a <p> under
   .dashrow__trailing, where the browser's own paragraph margin put the trainer's name a line below
   the item name beside it and cost every row a third of its height. The title and sub above zero
   theirs for the same reason. */
.dashrow__date {
    flex: none;
    min-width: 104px;
    margin: 0;
    text-align: right;
    font: 400 16px/1.3 var(--font);
    color: var(--muted-soft);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
    The pill is the last cell and therefore the one that moves everything else. Held to a width so a
    row of "Betaald" and a row of "Openstaand" put their neighbours in the same place; centred,
    because a label that is short for its box reads better centred than adrift on one side.
*/
.dashrow > .statuspill {
    flex: none;
    min-width: 124px;
    justify-content: center;
}

/* Content-sized and nowrap by default, so a long session name would push the row wider than its
   card instead of giving way like the rest of the text. */
.dashrow > .badge {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dashrow__trailing {
    flex: 0 1 auto;
    min-width: 0;
    text-align: right;
}

/* A trailing block already stacks and right-aligns its own lines, so its date is not a column of its
   own and must not be held open as one. */
.dashrow__trailing .dashrow__date {
    min-width: 0;
}

/*
    A trailing block with a state pill stacked under the figures - a member's products, where the
    pill reads "Gestopt · <reason>" and the reason is whatever was typed. As a column of its own it
    was the one cell that could not give way, so the title and dates did, down to nothing.

    Under the figures it costs the row a line instead, and what gives way is the REASON: it truncates
    to an ellipsis while the label stays whole, and the pill's title still carries the full text on
    hover. The cap on the block is what guarantees the name its share of the row - without it the
    stack is content-sized and the main cell, at basis 0, shrinks first.

    The figures keep their column widths so the amounts of consecutive rows still line up.
*/
.dashrow__trailing--stack {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    max-width: 60%;
}

.dashrow__figures {
    display: flex;
    align-items: baseline;
    gap: 16px;
}

.dashrow__trailing--stack .dashrow__date {
    min-width: 104px;
}

.dashrow__trailing--stack .statuspill {
    max-width: 100%;
}

.dashrow__trailing--stack .statuspill__reason {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dashrow__days {
    flex: none;
    padding: 5px 11px;
    border-radius: 999px;
    background: var(--surface-orange);
    color: var(--rust);
    font: 500 13px/1.2 var(--font);
    white-space: nowrap;
}

.dashrow__action {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border: 1px solid var(--hairline-strong);
    border-radius: 10px;
    background: #fff;
    color: var(--brand);
    cursor: pointer;
}

.dashrow__action:hover {
    background: var(--brand);
    color: #fff;
}

/* A warning glyph after a row's name - the session roster's "betaald tot has passed". Rust, the tone
   the measurement pill on the same row uses for "needs somebody": --error is a failure and --red is
   the calendar's full hour, and this is neither. Inline with the text, so it sits on the name's
   baseline rather than floating above it. */
.dashrow__warn {
    display: inline-block;
    margin-left: 6px;
    vertical-align: -3px;
    color: var(--rust);
}

/* ----------------------------------------------------------------- dialogs */

/*
    Radzen owns the frame (chrome, overlay, close button); this is only what goes inside one. The
    design brief caps modal depth at one level, so a dialog is always a leaf - it never opens
    another - which is why none of these carry a back affordance.
*/
.dialog {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.dialog__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    padding-top: 4px;
}

/*
    A sentence sharing the action row with the buttons.

    flex-basis rather than a bare auto width: a sentence's natural width is the whole row, and in a
    wrapping flex container that pushed each button onto a line of its own. With a basis it shares the
    line when there is room and takes its own line when there is not - and the buttons stay together
    either way, which is the thing that was wrong.
*/
.dialog__actions-note {
    flex: 1 1 240px;
    min-width: 0;
    margin-right: auto;
    font: 400 14px/1.35 var(--font);
    color: var(--muted);
}

/*
    A dialog that owns its height, so that its buttons cannot scroll away.

    The ordinary dialog grows with its content and Radzen scrolls the lot - fine for a form, wrong
    for a document viewer, where the content is arbitrarily long and Sluiten went off the bottom
    with it. Here the frame is fixed (DialogOptions.Height), the footer takes exactly the height it
    needs, and everything left over goes to the mail.

    :has() is what lets a component style the Radzen boxes it was dropped into without a global
    override that would change every other dialog - the same trick .shell--bare uses for /login.

    BOTH boxes have to be told, and forgetting the inner one is what made this look broken: a fixed
    height on .rz-dialog does not reach .rz-dialog-content, which kept sizing to its content. The
    frame was 82vh, the mail filled half of it, and the rest was empty space under the buttons.
*/
.rz-dialog:has(.dialog--tall) {
    display: flex;
    flex-direction: column;
}

.rz-dialog:has(.dialog--tall) .rz-dialog-content {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* The body scrolls, not this. Two nested scrollbars is the failure mode here. */
    overflow: hidden;
}

.dialog--tall {
    flex: 1 1 auto;
    min-height: 0;
}

.dialog__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* Exactly as tall as the buttons, and no part of what scrolls. */
.dialog--tall > .dialog__actions {
    flex: none;
}

/*
    A ROW of facts draws one rule under the group, and reads left to right.

    Both of those undo something the stacked list wants and a row does not. The design system gives
    every .fact its own bottom border - in a row that draws a second short line a few pixels above
    the container's own. And it pushes label and value apart with space-between, which is the point
    in a full-width list and, on a row-spanning fact like the address, strands the value against the
    far edge with a gap in the middle.
*/
.linkresult__facts .fact,
.emailfacts .fact {
    border-bottom: 0;
    padding: 0;
    justify-content: flex-start;
}

.linkresult__facts .fact__value,
.emailfacts .fact__value {
    text-align: left;
}

/* A picker, not a dropdown: each option carries a second line the reader needs (a measurement's
   period), and a native select cannot show one. Capped and scrolled because a health house can have
   a dozen measurements open at once. */
.dialog__choices {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 320px;
    overflow-y: auto;
}

.dialog__choice {
    display: flex;
    flex-direction: column;
    gap: 4px;
    text-align: left;
    padding: 14px 16px;
    border: 1px solid var(--card-border);
    border-radius: var(--radius-control);
    background: #fff;
    cursor: pointer;
}

.dialog__choice:hover {
    border-color: var(--olive);
    background: var(--surface-neutral);
}

/* --selected uses the brand rather than --selected (#BABA80): that token is for a row in a dense
   table, where a full-strength brand fill would shout. Here there is exactly one choice highlighted
   and it should be unmistakable. */
.dialog__choice--selected {
    border-color: var(--brand);
    background: var(--surface-neutral);
    box-shadow: inset 0 0 0 1px var(--brand);
}

.dialog__choice-name {
    font: 500 17px/1.3 var(--font);
}

.dialog__choice-meta {
    font: 400 15px/1.3 var(--font);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/*
    The release notes, as RadzenMarkdown renders CHANGELOG.md. Radzen ships no CSS for it, so what
    it emits arrives with browser defaults over ui.css's 24px h2. The file has exactly three shapes -
    a release is an h2, "Features" / "Bug Fixes" an h3, entries plain bullets, plus the one sentence
    a release with neither gets - because scripts/generate-changelog.sh writes it and nobody edits it.
*/
.changelog h2 {
    margin: 28px 0 0;
    padding-top: 24px;
    border-top: 1px solid var(--hairline);
    font: 500 17px/1.3 var(--font);
    font-variant-numeric: tabular-nums;
    /* Radzen stamps rz-text-h2 on the heading it emits, which carries Material's #212121 and a
       -0.5px tracking; both are named here so the brand ink wins. */
    color: var(--ink);
    letter-spacing: 0;
}

/* The newest release sits straight under the dialog's title: no rule, no gap. */
.changelog h2:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

/* The same caption the sidebar groups use, in ink colours. */
.changelog h3 {
    margin: 18px 0 8px;
    font: 500 12px/1 var(--font);
    color: var(--muted-soft);
    text-transform: uppercase;
    letter-spacing: .12em;
}

.changelog ul {
    margin: 0;
    padding-left: 20px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.changelog li {
    font: 400 15px/1.45 var(--font);
    color: var(--ink);
}

/* "No user-facing changes in this release." - the only paragraph the generator writes. */
.changelog p {
    margin: 12px 0 0;
    font: 400 15px/1.45 var(--font);
    color: var(--muted);
}

/* ----------------------------------------------------------------- measure items */

.measureitems {
    display: flex;
    flex-direction: column;
}

/*
    One item: name, value, unit, and why it is empty.

    The unit column is a FIXED width, and the two cleverer versions of this both failed. Sizing it to
    the content (`auto`) resolves per row, so "kg" and "ml/kg/min" still started the next column in
    different places. Making the whole list one grid with display:contents fixed that and broke
    something worse: the cells then need their own padding and rule, and `.field__input` already sets
    a border and padding of its own - so the inputs lost their boxes and every column drew its own
    line. A fixed track needs no cell styling at all, which is why it is the one that works.

    88px holds the longest unit in use; anything longer wraps inside its cell rather than pushing the
    column, so the rows stay aligned and only that row gets taller.
*/
.measureitems__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 120px 88px minmax(150px, 1fr);
    gap: 8px 14px;
    align-items: center;
    padding: 10px 4px;
    border-top: 1px solid var(--hairline);
}

.measureitems__name {
    min-width: 0;
    font: 400 16px/1.35 var(--font);
}

.measureitems__value {
    font: 500 17px/1.3 var(--font);
    font-variant-numeric: tabular-nums;
}

.measureitems__date {
    font: 400 15px/1.3 var(--font);
    color: var(--muted-soft);
    font-variant-numeric: tabular-nums;
}

/* The whole reason the dialog opens: which items are still outstanding. Tinted rather than merely
   labelled, so the gaps are countable at a glance instead of read line by line. */
.measureitems__row--missing {
    background: var(--surface-orange);
    border-radius: 8px;
}

.measureitems__missing {
    font: 500 15px/1.3 var(--font);
    color: var(--rust);
}

/* ----------------------------------------------------------------- capped cards and row buttons */

.dashcard__more {
    flex: 1;
    font: 400 15px/1 var(--font);
    color: var(--muted-soft);
}

.dashcard__foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 16px;
}

/* A labelled button sitting directly on a row - the voucher payout, and anything like it. Sized to
   its own text like the other columns, so the text beside it is what gives way. */
.dashrow > .btn {
    flex: none;
}

.dashrow__buttons {
    flex: none;
    display: flex;
    gap: 8px;
}

.dashrow__action--primary {
    border-color: var(--brand);
    background: var(--brand);
    color: #fff;
}

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

/* The destructive controls on this screen - off the waiting list, and throwing away unlinked device
   data - and the only place --error is used on the dashboard. Outlined until hover: a row of solid
   red buttons down a list reads as an alarm rather than as an option. */
.dashrow__action--danger {
    border-color: var(--error);
    color: var(--error);
}

.dashrow__action--danger:hover {
    background: var(--error);
    border-color: var(--error);
    color: #fff;
}

/* ----------------------------------------------------------------- editable measure items */

/* Widened from the read-only version: an input needs room, and the normal range sits under the
   name rather than beside it so a long item name does not squeeze the field. */
.measureitems__normal {
    display: block;
    margin-top: 2px;
    font: 400 14px/1.3 var(--font);
    color: var(--muted-soft);
    font-variant-numeric: tabular-nums;
}

.measureitems__box {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* The reason a value is absent, which is prose rather than a number. */
.measureitems__reason {
    font: 400 15px/1.3 var(--font);
}

/* Wraps rather than overflows: a unit longer than its track makes one row taller instead of pushing
   every row's columns out of line. */
.measureitems__unit {
    min-width: 0;
    font: 400 15px/1.3 var(--font);
    color: var(--muted);
    overflow-wrap: anywhere;
}

/* The value cell of a device-written row lines up with the boxes above it. */
.measureitems__row .measureitems__value,
.measureitems__row .measureitems__missing {
    text-align: right;
}

/* Narrow: the reason drops to a line of its own under the three that have to stay together. */
@media (max-width: 720px) {
    .measureitems__row {
        grid-template-columns: minmax(0, 1fr) 100px 70px;
    }

    .measureitems__reason {
        grid-column: 1 / -1;
    }
}

/* Device-written rows. Muted rather than hidden: the value is real and worth reading, it just
   cannot be typed over here. */
.measureitems__locked {
    font: 400 13px/1.3 var(--font);
    color: var(--muted-soft);
    white-space: nowrap;
}

/* ----------------------------------------------------------------- linking unlinked data */

.linkresult__facts {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 32px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--hairline);
}

/* Collapsed by default. It is a diagnostic for the rare case where two anonymous records arrived in
   the same minute, not something to read every time. */
.linkresult__raw summary {
    cursor: pointer;
    font: 500 15px/1.4 var(--font);
    color: var(--muted);
}

.linkresult__raw summary:hover {
    color: var(--brand);
}

.linkresult__raw pre {
    margin-top: 10px;
    max-height: 200px;
    overflow: auto;
}

/* ----------------------------------------------------------------- list toolbar */

.listbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 12px;
}

/* A row of tabs with a search box at its end, for a screen whose card heads are already full.
   The tabs keep their own padding; the box is vertically centred on them. */
.tabsbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px 12px;
}

.tabsbar .tabs {
    flex: 1 1 auto;
}

.listbar__search {
    position: relative;
    display: flex;
    align-items: center;
    flex: 1 1 260px;
    max-width: 340px;
    color: var(--muted-soft);
}

.listbar__search svg {
    position: absolute;
    left: 15px;
    pointer-events: none;
}

.listbar__search input {
    width: 100%;
    padding: 13px 15px 13px 44px;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-control);
    font: 400 16px/1.2 var(--font);
    background: #fff;
    color: var(--ink);
}

/* A segmented control rather than loose pills: these five are mutually exclusive, and grouping them
   in one bordered track says so before anything is clicked. */
.segmented {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 4px;
    background: #fff;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-control);
}

.segmented__item {
    border: 0;
    background: transparent;
    padding: 9px 15px;
    border-radius: 9px;
    cursor: pointer;
    font: 500 15px/1 var(--font);
    color: var(--deep);
}

.segmented__item:hover {
    background: var(--surface-neutral);
}

.segmented__item--active {
    background: var(--canvas);
}

/* ----------------------------------------------------------------- members table */

.card--table {
    padding: 6px 24px 18px;
    overflow-x: auto;
}

/*
    One grid template, declared once and inherited by the head and every row through the shared
    min-width. A table element would be the obvious choice, but the rows are buttons - the whole row
    is the click target - and a button cannot be a table row.
*/
.memtable__head,
.memtable__row {
    display: grid;
    grid-template-columns: minmax(210px, 1.6fr) 100px minmax(120px, .9fr) minmax(120px, .9fr) minmax(130px, 1fr) 130px 34px;
    gap: 16px;
    min-width: 960px;
    align-items: center;
}

.memtable__head {
    padding: 18px 8px 12px;
    border-bottom: 1px solid var(--card-border);
}

.memtable__sort {
    border: 0;
    background: transparent;
    padding: 0;
    text-align: left;
    cursor: pointer;
    font: 500 13px/1.2 var(--font);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}

.memtable__sort:hover {
    color: var(--brand);
}

.memtable__sort--active {
    color: var(--brand);
}

.memtable__caret {
    margin-left: 4px;
}

.memtable__row {
    width: 100%;
    text-align: left;
    cursor: pointer;
    padding: 14px 8px;
    border: 0;
    border-bottom: 1px solid var(--hairline);
    background: transparent;
    border-radius: 10px;
}

.memtable__row:hover {
    background: var(--surface-neutral);
}

.memtable__member {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.memtable__avatar {
    flex: none;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--canvas);
    color: var(--brand);
    display: flex;
    align-items: center;
    justify-content: center;
    font: 500 14px/1 var(--font);
}

.memtable__names {
    min-width: 0;
}

.memtable__name {
    display: block;
    font: 500 17px/1.3 var(--font);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The second line under a name. Members and Onboardings put the e-mail address here (a generated
   one is long enough to run out under the postcode), Trainings a short code, Meting deelnemers the
   measurement's type. Truncated like the name above it. */
.memtable__code {
    display: block;
    margin-top: 2px;
    font: 400 14px/1.3 var(--font);
    color: var(--muted-soft);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.memtable__cell {
    font: 400 16px/1.3 var(--font);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* Not activated yet. Lighter rather than red: it is a queue, not a fault. */
.memtable__cell--absent {
    color: var(--muted-soft);
    font-style: italic;
}

.memtable__status {
    justify-self: start;
}

.memtable__chevron {
    justify-self: end;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    color: var(--muted-soft);
}

.pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    padding-top: 18px;
}

.pager__position {
    font: 400 15px/1 var(--font);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* The export link under the members table. Right-aligned and set as a quiet text link in --muted,
   not a .btn: the table is the work and the file is a by-product, and a bordered button at the foot
   of every list would read as the next thing to do. It carries its own count because the export is
   the FILTERED list, and the number is what tells somebody the filter took. */
.memtable__foot {
    display: flex;
    justify-content: flex-end;
    padding-top: 14px;
}

.memtable__export {
    border: 0;
    background: transparent;
    padding: 0;
    cursor: pointer;
    font: 400 14px/1.3 var(--font);
    color: var(--muted);
    text-decoration: underline;
    text-underline-offset: 2px;
    font-variant-numeric: tabular-nums;
}

.memtable__export:hover,
.memtable__export:focus-visible {
    color: var(--brand);
}

.memtable__export:disabled {
    cursor: default;
    opacity: .5;
}

/* ----------------------------------------------------------------- member detail */

.backlink {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    align-self: flex-start;
    border: 0;
    background: transparent;
    color: var(--brand);
    padding: 0;
    font: 500 16px/1 var(--font);
    cursor: pointer;
}

.backlink:hover {
    color: var(--accent);
}

.memberhead {
    display: flex;
    align-items: center;
    gap: 18px;
}

.memberhead__avatar {
    flex: none;
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background: var(--olive);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font: 500 22px/1 var(--font);
}

.memberhead__title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
}

.memberhead__title .page__title {
    margin: 0;
}

/* Two columns where there is room, one where there is not. The cards are independent sections, so
   they reflow rather than sitting in a fixed layout that squeezes the forms. */
.detailgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(440px, 1fr));
    gap: 20px;
    align-items: start;
}

/*
    A card that keeps to the right-hand column whatever sits, or does not sit, on its left: the
    registration code on the member and onboarding details, which lands in the same place on both
    even though only one of them has a card beside it.

    Negative lines count from the end of the EXPLICIT grid, and auto-fit's repetitions are explicit
    tracks, so "-2 / -1" is the last column however many the width allows - the right one at two
    across, and simply the one column when the grid has collapsed to a single column. No media query
    to keep in step with the 440px above.
*/
.detailgrid__right {
    grid-column: -2 / -1;
}

/*
    The dossier: notes down the left, the three reference lists stacked on the right.

    Equal-height columns were the wrong answer here. The four lists are not comparable - a member has
    one band and a handful of documents, and hundreds of notes - so matching them left the short ones
    padded with air and the long one still cut off at five rows. Giving the notes the column instead
    means the list people actually read down gets the room, and the rest take what they need.
*/
.dossiergrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(440px, 1fr));
    gap: 20px;
    align-items: start;
}

/*
    Roughly twice the old height. A floor rather than a fixed height so a member with three notes
    does not get a card of empty space, and a ceiling so a member with three hundred scrolls inside
    the card instead of turning the tab into a page.
*/
.dossiergrid__notes {
    display: flex;
    flex-direction: column;
    min-height: 420px;
    max-height: 860px;
    /* The list does the scrolling. Without this the card scrolls too, because .card--table sets
       overflow-x and a single non-visible axis drags the other one to auto. */
    overflow: hidden;
}

/* Only the list gives: a squeezed card must lose rows, not squash the heading or the note box. */
.dossiergrid__notes > :not(.scrolllist) {
    flex: none;
}

/* The reference lists size to their content, capped so a long one cannot push its neighbours off. */
.detailstack .scrolllist {
    max-height: 300px;
}

/*
    A row of actions, whether it is one button or three.

    The gap only became visible when a second button arrived: with a lone Opslaan there was nothing
    to sit beside, so three buttons on the onboarding detail came out touching. Wrapping matters for
    the same reason - three labels plus French expansion is wider than it looks.
*/
.detailgrid__actions {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
}

.formgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 16px;
}

/*
    Wider columns, for a form whose labels are longer than its inputs.

    200px fits "Naam" and "Code" and truncates "Soort meting" - and a truncated LABEL is worse than a
    truncated value, because the value can at least be read by clicking into the field.
*/
.formgrid--roomy {
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}

/* A field that is the whole row: a free-text note reads badly in a 200px column. */
.field--wide {
    grid-column: 1 / -1;
}

/*
    Two fields sharing one row, whatever the grid around them auto-fits to. The roomy grid packs three
    250px columns into an 860px dialog, so two related pickers - a product's contract and its follow-up -
    would sit beside a third, unrelated one; this puts them side by side across the whole row instead.
*/
.formgrid__pair {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 16px;
}

/* A checkbox whose label carries an explanation: the two stack beside the box rather than run on. */
.field--check .field__text {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

/* A value shown where a control would be, so a read-only fact lines up with the fields around it. */
.field__static {
    padding: 12px 0 4px;
    font: 500 17px/1.3 var(--font);
}

/*
    Two cards that stack in one grid cell.

    CSS grid fills row by row, so a third card lands under the FIRST one - back in the left column,
    under whichever card happens to be tallest. Wrapping the pair in a stack makes them one item, and
    the item goes where an item goes.
*/
.detailstack {
    display: flex;
    flex-direction: column;
    gap: 20px;
    min-width: 0;
}

.notebox {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding-bottom: 14px;
}

.notebox textarea {
    flex: 1;
    resize: vertical;
}

/*
    A card heading with something to press next to it. The heading keeps its own top margin, so the
    baseline stays where it is on the cards that have no action; the padding below is the button's,
    since .card__subhead's 6px leaves it sitting on the first row's rule.
*/
.cardhead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px 20px;
    flex-wrap: wrap;
    padding-bottom: 12px;
}

/*
    The heading that OPENS a card has no content above it to be separated from, so it does not want
    .card__subhead's 24px top margin - the card's own 28px of padding is the space.

    Left in place everywhere else: the margin exists to part a second heading from what precedes it,
    and .card--table deliberately pads only 6px at the top BECAUSE the heading brings 24px of its
    own. Stacking the two put 52px above the heading and 18px below it, lopsided enough on the
    Gegevens cards and the trainers card to read as a rendering fault.
*/
.card:not(.card--table) > .card__subhead:first-child,
.card:not(.card--table) > .cardhead:first-child .card__subhead {
    margin-top: 0;
}

/*
    The third weight of button ui.css does not have: same treatment as .btn, sized for a card header
    rather than a form footer. Kept here until a second app needs it, like .btn--ghost above.
*/
.btn--small {
    padding: 9px 16px;
    border-radius: 10px;
    font: 500 15px/1 var(--font);
}

/*
    Caps a dossier list at roughly six rows and scrolls the rest.

    A member of five years has hundreds of notes and dozens of mails, and four such lists stacked in
    one tab is a page nobody reaches the bottom of - the brief's "lists are long, with no pagination"
    friction, in the one place where paging each of four lists separately would be worse than the
    problem. The cards stay the same height as each other whatever the member's history.
*/
.scrolllist {
    /* Fills its card and stops there. min-height:0 is what lets a flex item shrink below its
       content - without it the list reports its full height and nothing ever scrolls. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /*
        Room on BOTH sides for what the rows hang outside themselves.

        .dashrow is padded 10px and pulled back by the same margin, so its hover tint and a pinned
        note's background reach the card's edge while the text stays aligned with the heading. A
        scroll container clips that: overflow-y:auto computes overflow-x to auto as well, so the tint
        was being cut off flush with the text and a pinned note looked like it had no padding at all.
        Matching padding here gives the overhang somewhere to live, and the negative margin puts the
        list back where it was. The right side also keeps the scrollbar off the status pills.
    */
    padding: 0 10px;
    margin: 0 -10px;
}

/*
    A note is the one .dashrow__title that is prose rather than a label: it wraps in both of its
    states, and .clamp only adds the three-line cap on top. Without this, opening a note out dropped
    it back onto the single-line nowrap truncation every other title gets, so "toon meer" showed less
    than "toon minder" did. Declared before .clamp so that rule's overflow: hidden wins the tie.

    pre-line rather than normal: a note is typed in a textarea and the API stores its line breaks
    verbatim, which HTML then collapses - so three paragraphs about a knee read as one run-on line.
    pre-line keeps the breaks and still collapses runs of spaces, where pre-wrap would preserve every
    stray double space too. The clamp counts rendered lines, forced breaks included, which is why
    LineClamp in Services estimates the same way rather than counting characters alone.
*/
.dashrow__title--note {
    white-space: pre-line;
    overflow: visible;
    text-overflow: clip;
    /* A pasted URL has no break opportunity and would push the pin off the card. */
    overflow-wrap: anywhere;
}

/*
    Three lines, then "toon meer". Long enough that an ordinary note is never truncated, short enough
    that one essay cannot own the card.
*/
.dashrow__title.clamp {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/*
    A payment's bank description opened out with "toon meer". Only the OPEN state needs a rule: closed
    is the single-line ellipsis every .dashrow__sub gets, which is why this is not --note plus .clamp -
    a payment list is dense on purpose, and a note's three lines would cost every row two. Wraps
    anywhere for the same reason a note does: an IBAN or a reference is one unbroken run that would
    otherwise push the amount and the date off the card.
*/
.dashrow__sub--open {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    overflow-wrap: anywhere;
}

.dashrow__more {
    flex: none;
    border: 0;
    background: transparent;
    padding: 0;
    cursor: pointer;
    font: 500 15px/1.2 var(--font);
    color: var(--brand);
    text-decoration: underline;
}

.dashrow__more:hover {
    color: var(--accent);
}

/*
    A note row's tools: the pin, the pencil and the trash, as one cluster at the end of the row.

    Quiet by design - borderless and muted until hovered, unlike the outlined .dashrow__action - because
    every note carries all three and a column of outlined boxes down a list of prose reads as a toolbar
    rather than as notes. The cluster is one flex item so the three never wrap one by one when the
    session-note dialog's narrower column gets tight; what gives way is the text beside them.

    The pin is both the control and the state: drawn in outline when the note is loose and filled when
    it is pinned, so a glance down the column reads without a legend - the same argument StatusPill
    makes for never letting colour be the only signal.
*/
.dashrow__tools {
    flex: none;
    display: flex;
    align-items: center;
    gap: 2px;
}

.dashrow__pin,
.dashrow__tool {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: var(--muted-soft);
    cursor: pointer;
}

.dashrow__pin:hover:not(:disabled),
.dashrow__tool:hover:not(:disabled) {
    background: var(--canvas);
    color: var(--brand);
}

.dashrow__pin--on {
    color: var(--accent);
}

/* Red on hover only, for the reason .dashrow__action--danger is outlined until hovered: a red glyph on
   every row would read as an alarm rather than as an option. */
.dashrow__tool--danger:hover:not(:disabled) {
    color: var(--error);
}

.dashrow__pin:disabled,
.dashrow__tool:disabled {
    opacity: .45;
    cursor: progress;
}

/* A pinned note keeps a tint under it, so it still reads as pinned once scrolled past its own pin. */
.dashrow--pinned {
    background: var(--surface-orange);
}

/*
    A row that is itself the click target.

    .dashrow is a flex container and a button can be one, so the row keeps its layout; what a button
    brings with it - a border, centred text, its own font - has to be undone. The children become
    blocks because inside a button they are spans: a <p> cannot legally live in one.
*/
.dashrow--button {
    width: 100%;
    border: 0;
    border-top: 1px solid var(--hairline);
    background: transparent;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.dashrow--button .dashrow__title,
.dashrow--button .dashrow__sub {
    display: block;
}

/* ----------------------------------------------------------------- a member's Analyse */

/* The picker above the detail, laid out as the MY Results page: filters on the left, the window
   and its "show older" on the right, the strip of cards (.resultstrip, ui.css) under both. */
.analysis__lead {
    margin: 0;
}

.analysis__picker .cardhead {
    padding-bottom: 12px;
}

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

.analysis__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* ----------------------------------------------------------------- rfid capture */

/*
    Wide, monospaced and tabular: the value is a long run of digits with no words in it, and reading
    one off the screen to compare against the number printed on a band is the only reason to look.
*/
.rfidinput {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    letter-spacing: .04em;
}

/* A small button sitting after a fact's value - the parameters dialog's "restore the default" and
   "restore the previous value" pair. Inline rather than on its own row, because the value and the way
   to put it back read as one statement; the margin is the whole point, since .fact__value is plain
   right-aligned text and without it the button reads as part of the value: "15Zet terug op standaard".
   Here rather than in ui.css: .fact is shared with the MY portal, which has no such buttons. */
.fact__action {
    margin-left: 12px;
    vertical-align: baseline;
}

/* A pill sitting after a fact's value - the finish review's "Jij" / "Niet jij" beside the trainer's
   name. The same problem .fact__action solves one rule up: without the margin the pill reads as the
   end of the name. Invisible where a pill is the value's only content, as in the e-mail detail: a
   right-aligned value has nothing to the pill's left. */
.fact__value > .statuspill {
    margin-left: 10px;
}

/*
    The finish review: a dialog that has to fit above the fold on a 768px-tall laptop, because the
    trainer line - the one thing it exists to show - is at the top. So its facts sit tighter than the
    design system's stacked list, the group's own bottom margin gives way to the dialog's gap, and
    the roster wraps INSIDE its row rather than as a paragraph after the group, where that margin
    plus the gap stranded it 46px under its count.
*/
.finishreview {
    margin-bottom: 0;
}

.finishreview .fact {
    padding: 10px 0;
}

.finishreview__roster {
    flex-wrap: wrap;
    row-gap: 4px;
}

.finishreview__names {
    flex-basis: 100%;
    font: 400 15px/1.45 var(--font);
    color: var(--muted);
}

.dialog__hint {
    margin: 0;
    font: 400 15px/1.45 var(--font);
    color: var(--muted);
}

.dialog__error {
    margin: 0;
    padding: 12px 14px;
    border-radius: 10px;
    background: var(--surface-orange);
    color: var(--rust);
    font: 500 15px/1.4 var(--font);
}

/*
    The second line of a dialog error: what specifically is in the way.

    Blocks rather than inline, because it is a list - the moments a trainer is still booked for - and
    it has to be readable next to the sentence that says why it matters.
*/
.dialog__detail {
    display: block;
    margin-top: 6px;
    font: 400 14px/1.45 var(--font);
    color: var(--rust);
}

.dialog__raw {
    margin: 0;
    max-height: 160px;
    overflow: auto;
    font: 400 13px/1.5 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    color: var(--muted);
    white-space: pre-wrap;
    word-break: break-word;
}

/* ----------------------------------------------------------------- sent mail */

/*
    The mail's header: the address on its own line, then the date and the status beside each other.

    Laid out rather than left to wrap, because the wrap point was deciding the arrangement - which of
    the two you got depended on how long the member's e-mail address happened to be.
*/
.emailfacts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px 32px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--hairline);
}

.emailfacts__wide {
    flex-basis: 100%;
}

/*
    A DOCUMENT, not markup pasted into the page, and both halves of that matter.

    Safety: the template is ours but its values are the member's, and inlining it would run any
    script that ever reached one against this origin - where the admin's token lives. The empty
    sandbox denies everything: no scripts, no forms, no navigation, no same-origin.

    Layout: a mail template ships its own <style>, written for Outlook, full of table resets and
    absolute widths. Inlined, that leaks out and wrecks the dialog around it. A document boundary is
    the only thing that reliably contains it.
*/
.emailbody {
    width: 100%;
    /* Takes the rest of the fixed frame, so the mail scrolls inside its own document rather than
       the dialog scrolling around it - one scrollbar instead of two nested ones. */
    flex: 1 1 auto;
    min-height: 240px;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    background: #fff;
}

/* ----------------------------------------------------------------- onboardings */

/*
    The retention notice.

    Stated rather than left to be discovered: the API removes an unactivated registration after three
    months, and a registration quietly disappearing is the kind of thing found out months later by
    the member ringing up. Warning-toned, not error - nothing is wrong yet.
*/
.notice {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 16px 20px;
    border-radius: var(--radius-card-sm);
    background: var(--surface-orange);
}

.notice__mark {
    flex: none;
    width: 26px;
    height: 26px;
    border-radius: 999px;
    background: var(--accent);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font: 500 16px/1 var(--font);
}

.notice__title {
    margin: 0;
    font: 500 17px/1.35 var(--font);
}

.notice__body {
    margin: 2px 0 0;
    font: 400 16px/1.4 var(--font);
    color: var(--muted);
}

/*
    One column fewer than the members table and no avatar: a registration has no photo, no code and
    no membership status, so borrowing .memtable's template would leave two empty columns.
*/
.onbtable__head,
.onbtable__row {
    display: grid;
    grid-template-columns: minmax(220px, 1.8fr) 110px minmax(120px, 1fr) minmax(160px, 1.2fr) 140px 34px;
    gap: 16px;
    min-width: 900px;
    align-items: center;
}

.onbtable__head {
    padding: 18px 8px 12px;
    border-bottom: 1px solid var(--card-border);
}

.onbtable__row {
    width: 100%;
    padding: 14px 8px;
    border: 0;
    border-bottom: 1px solid var(--hairline);
    background: transparent;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.onbtable__row:hover {
    background: var(--surface-neutral);
}

/* A field and the button that acts on it, on one line. */
.inputrow {
    display: flex;
    align-items: center;
    gap: 10px;
}

.inputrow .field__input {
    flex: 1;
    min-width: 0;
}

/* A date never needs the whole row: it is ten characters and a picker, and stretched across a card it
   reads as a text field. Sized to the date, and the button sits right after it. */
.inputrow > .field__input[type="date"] {
    flex: 0 1 220px;
}

/* The verdict under a field, in the tone of the answer rather than as a notification. */
.field__note {
    font: 400 15px/1.4 var(--font);
    padding-top: 6px;
}

.field__note--good {
    color: var(--green-text);
}

.field__note--bad {
    color: var(--rust);
}

.field__hint {
    font-weight: 400;
    color: var(--muted-soft);
}

/* A checkbox reads left to right, unlike every other field on the form. */
.field--check {
    flex-direction: row;
    align-items: center;
    gap: 10px;
}

.field--inline {
    min-width: 220px;
}

/*
    The registration code, which is read aloud down a telephone. Monospaced and spaced out so a
    letter cannot be confused with a digit, and selectable because it is more often copied than read.
*/
.codebox {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

/* A URL rather than a seven-character code: smaller, and allowed to wrap anywhere, since it has no spaces. */
.codebox__code--url {
    font-size: 14px;
    word-break: break-all;
    user-select: all;
}

/* The signing-link dialog: open / copy under the link, and the QR the member scans below them. */
.signinglink__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 14px;
}

.signingqr {
    display: flex;
    justify-content: center;
    margin: 20px 0 8px;
}

.signingqr svg {
    width: 220px;
    height: 220px;
    border-radius: 12px;
    background: #fff;
}

.codebox__code {
    flex: 1;
    min-width: 140px;
    padding: 12px 16px;
    border-radius: 12px;
    background: var(--canvas);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 22px;
    letter-spacing: .14em;
    user-select: all;
}

/* A code shown as a fact - the member's referral code. Selectable in one click, for the same reason
   .codebox__code is: it is more often copied than read. */
.fact__value--code {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    letter-spacing: .1em;
    user-select: all;
}

/* Destructive, and coloured as such only on hover - a red button in a row of three reads as danger
   before anyone has decided to do anything. */
.btn--danger:hover:not(:disabled) {
    background: var(--error);
    border-color: var(--error);
    color: #fff;
}

/* ----------------------------------------------------------------- trainings */

/*
    One column more than the members table: a series is read by when it runs and who takes it, and
    both are as load-bearing as its name.
*/
.tratable__head,
.tratable__row {
    display: grid;
    grid-template-columns: minmax(200px, 1.6fr) minmax(150px, 1.2fr) minmax(130px, 1fr) 90px 80px 150px 34px;
    gap: 16px;
    min-width: 1020px;
    align-items: center;
}

.tratable__head {
    padding: 18px 8px 12px;
    border-bottom: 1px solid var(--card-border);
}

.tratable__row {
    width: 100%;
    padding: 14px 8px;
    border: 0;
    border-bottom: 1px solid var(--hairline);
    background: transparent;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.tratable__row:hover {
    background: var(--surface-neutral);
}

/*
    How full a series is.

    The fraction is always there and the bar is the second signal - the same rule StatusPill follows,
    and the reason the number is not replaced by a colour. What the bar buys is scanning a column of
    twenty and seeing which ones have no room, without reading twenty fractions.
*/
.occupancy {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

.occupancy__label {
    font: 400 15px/1.2 var(--font);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.occupancy__track {
    height: 6px;
    border-radius: 999px;
    background: var(--hairline);
    overflow: hidden;
}

.occupancy__fill {
    display: block;
    height: 100%;
    border-radius: 999px;
}

.occupancy__fill--ok {
    background: var(--green-text);
}

.occupancy__fill--high {
    background: var(--accent);
}

/* Full is not an error - a full series is one doing its job - so this is the attention tint rather
   than --error, which is reserved for something being wrong. */
.occupancy__fill--full {
    background: var(--rust);
}

/* A count beside a card's heading, as on the trainings members card. */
.card__count {
    font: 400 16px/1.2 var(--font);
    color: var(--muted-soft);
    font-variant-numeric: tabular-nums;
}

/* ----------------------------------------------------------------- calendar */

/*
    The week's controls: trainer filter, week stepper, "Deze week".

    Right-aligned in the page head beside the title, and allowed to wrap as one block rather than
    letting the stepper break away from the button beside it.
*/
.calbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 12px;
}

/*
    A stepper reads as one control, so the two arrows and the label share a border rather than being
    three things that happen to sit together.
*/
.weeknav {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px;
    border: 1px solid var(--card-border);
    border-radius: 12px;
    background: #fff;
}

.weeknav__step {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: var(--brand);
    cursor: pointer;
}

.weeknav__step:hover:not(:disabled) {
    background: var(--surface-neutral);
}

.weeknav__step:disabled {
    color: var(--disabled);
    cursor: default;
}

.weeknav__label {
    padding: 0 10px;
    font: 500 16px/1 var(--font);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.callegend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 22px;
}

.callegend__item {
    display: flex;
    align-items: center;
    gap: 8px;
    font: 400 15px/1 var(--font);
    color: var(--muted);
}

/* The swatch carries the same left bar the cards do, which is what the legend is naming. */
.calkey {
    width: 14px;
    height: 14px;
    border-radius: 4px;
    border: 1px solid var(--card-border);
}

.calkey--free {
    background: #fff;
    box-shadow: inset 3px 0 0 var(--olive);
}

.calkey--full {
    background: var(--surface-red);
    box-shadow: inset 3px 0 0 var(--red);
}

/*
    What goes INSIDE a planner card. The frame - the fill, the border, the left bar, the radius, the
    hover - is the design system's .planner__item; these are the pieces this screen puts in it, and the
    state modifiers below recolour that frame. They stay single-class on purpose: the planner puts
    planner__item on the same element, and a single-class rule loaded after ui.css wins on order alone.
*/
.planner__item--short .calcard__foot {
    display: none;
}

/*
    Full is the one state read from across the room - "add them" against "find another hour" - so it
    is the one that gets a colour nothing else in the grid uses. The brand's brick red rather than
    --error: a full hour is a fact about the room, not a failure. It used to share the neutral tint
    with finished and appointment, and the three were told apart only by reading.
*/
.calcard--full {
    background: var(--surface-red);
    border-left-color: var(--red);
}

.calcard__name {
    display: block;
    font: 500 14px/1.25 var(--font);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.calcard__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    margin-top: 4px;
}

.calcard__trainer {
    font: 400 13px/1.2 var(--font);
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.calcard__pill {
    flex: none;
    padding: 2px 7px;
    border-radius: 999px;
    background: var(--surface-green);
    color: var(--olive);
    font: 500 12px/1.4 var(--font);
    font-variant-numeric: tabular-nums;
}

.calcard--full .calcard__pill {
    background: #fff;
    color: var(--red);
}

/*
    A private session says who may be in the room, which is more consequential to misread than how
    full it is - so it wins the tint when a session is both.
*/
.calcard--private {
    background: var(--surface-orange);
    border-left-color: var(--accent);
}

.calcard--private .calcard__pill {
    background: #fff;
    color: var(--rust);
}

/*
    A finished session is history: its hours are billed and nothing in it is still a decision. Drawn
    back rather than marked up - a week of closed sessions should recede so the ones that still need
    something stand out against them.
*/
.calcard--finished {
    background: var(--surface-neutral);
    border-left-color: var(--card-border);
    color: var(--muted);
}

.calcard--finished .calcard__name {
    font-weight: 400;
}

.calcard--finished .calcard__pill {
    background: #fff;
    color: var(--muted-soft);
}

.calkey--finished {
    background: var(--surface-neutral);
    box-shadow: inset 3px 0 0 var(--card-border);
}

/*
    An appointment is a different KIND of thing, not a differently-loaded session: one person, from
    the practice's own agenda. Dashed, so it never reads as a session with an unusual state.
*/
.calcard--appointment {
    background: var(--surface-neutral);
    border-style: dashed;
    border-left: 3px solid var(--muted);
}

/*
    The two kinds the sync files an appointment under. Each rides on .calcard--appointment, which
    keeps the dashed sides and the bar's width, and recolours only - the dash says "appointment", the
    colour says which. Blue is the one hue the palette has no tone of, which is what lets an intake
    read as a different kind of thing from every session state around it; gold is the brand's own
    third tone. Same specificity as the base rule, so these MUST come after it.

    The pill stays white on muted for both: gold text on white does not pass contrast, and the bar
    and the tint already carry the kind twice.
*/
.calcard--intake {
    background: var(--surface-blue);
    border-left-color: var(--blue);
}

.calcard--intro {
    background: var(--surface-gold);
    border-left-color: var(--gold);
}

.calcard__pill--appointment {
    background: #fff;
    color: var(--muted);
}

.calkey--private {
    background: var(--surface-orange);
    box-shadow: inset 3px 0 0 var(--accent);
}

.calkey--intake {
    background: var(--surface-blue);
    border-style: dashed;
    box-shadow: inset 3px 0 0 var(--blue);
}

.calkey--intro {
    background: var(--surface-gold);
    border-style: dashed;
    box-shadow: inset 3px 0 0 var(--gold);
}

/* The trainer filter holds a full name, and a short box puts it under the clear cross. */
.calbar .fieldselect {
    min-width: 230px;
}

/* A checkbox and its label read as one control, not as a field with a box under it. */
.field--check {
    flex-direction: row;
    align-items: center;
    gap: 11px;
}

.field--check input {
    width: 20px;
    height: 20px;
    flex: none;
}

/*
    The session-note dialog: today's note beside everything already written about the member.

    Nearly full screen because the history is the reason to open it - a note about a knee that played
    up is worth little alone and a lot next to the three before it. The editor is held to a readable
    column and the history takes the rest; below a laptop width they stack, editor first, since that
    is what the button was pressed for.
*/
.notedialog__grid {
    display: grid;
    grid-template-columns: minmax(320px, 2fr) minmax(340px, 3fr);
    gap: 24px;
    align-items: stretch;

    /* .dialog__body scrolls itself by default; here the history does, and two nested scrollbars is
       the failure mode. */
    overflow: hidden;
}

.notedialog__editor,
.notedialog__history {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-height: 0;
}

.notedialog__editor > .card__subhead,
.notedialog__history > .card__subhead {
    margin-top: 0;
}

/* The box grows into whatever the dialog's height leaves, rather than being a fixed rows="6". */
.notedialog__field {
    flex: 1 1 auto;
    min-height: 160px;
}

.notedialog__field textarea {
    flex: 1 1 auto;
    resize: none;
}

/* The history scrolls, not the dialog: two nested scrollbars is the failure mode here. */
.notedialog__list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

@media (max-width: 1000px) {
    .notedialog__grid {
        grid-template-columns: 1fr;
    }

    .notedialog__list {
        max-height: 40vh;
    }
}

/* ------------------------------------------------------- measurement participants */

/*
    The measurements list. Same shape as .tratable, different columns: the progress cell is wide
    because it holds a sentence as well as a bar, and the fraction is the reason the row is read.
*/
.meastable__head,
.meastable__row {
    display: grid;
    grid-template-columns: minmax(210px, 1.5fr) minmax(170px, 1.1fr) 110px minmax(180px, 1.2fr) 130px 34px;
    gap: 16px;
    min-width: 940px;
    align-items: center;
}

.meastable__head {
    padding: 18px 8px 12px;
    border-bottom: 1px solid var(--card-border);
    font: 500 13px/1.2 var(--font);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}

.meastable__row {
    padding: 15px 8px;
    border-bottom: 1px solid var(--hairline);
    border-radius: 10px;
    text-decoration: none;
    color: inherit;
}

.meastable__row:hover {
    background: var(--surface-neutral);
}

/*
    Nothing to do yet, in either direction: a measurement that has not started, or one with nobody
    in it. Drawn as an empty track rather than a zero-width fill of the busy colour, which would
    read as "none of them done" when the truth is "there is nothing to be done".

    Only for those two. A measurement that ENDED half-finished has a real fraction, and drawing it
    blank threw away the difference between 25 of 28 and none at all.
*/
.occupancy__fill--idle {
    background: transparent;
}

/*
    A fraction that is now history: the window closed with gaps in it.

    Muted rather than absent, and muted rather than urgent - the proportion is the record of what
    happened, and nothing about it is still a decision.
*/
.occupancy__fill--past {
    background: var(--muted-soft);
}

/*
    A note that is telling you something rather than explaining something.

    The plain .card__note is muted, which is right for an aside and wrong for "three people here need
    measuring" or "this will be created empty". Same rust the rest of the portal uses for attention,
    on the same tint - never the error colour, because nothing is wrong yet.
*/
.card__note--accent {
    padding: 10px 13px;
    border-radius: 10px;
    background: var(--surface-orange);
    color: var(--rust);
    font-weight: 500;
}

/* A search box that lives in a card header rather than in the page's filter bar. */
.listbar__search--inline {
    flex: 0 1 220px;
    max-width: 240px;
}

/* A card that takes the whole detail grid rather than a column of it. */
.detailgrid__wide {
    grid-column: 1 / -1;
}

/* ---------------------------------------------------------------- cash flow */

/*
    A fact appended to a row's sub-line: "reminded on the 12th".

    Tinted rather than plain, because it is the visible result of an action somebody just took - and
    an action whose only effect is invisible reads as one that did not work.
*/
.dashrow__flag {
    margin-left: 8px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--surface-orange);
    color: var(--rust);
    font: 500 13px/1.5 var(--font);
    white-space: nowrap;
}

/* A card header whose right-hand side holds more than one control. */
.cardhead__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

/*
    One suggested pair: what came in, what was asked for, why we think they belong together.

    Side by side rather than stacked, because the judgement being asked for is a COMPARISON - the
    amounts and the dates are read against each other, and a vertical list makes that a scroll.
*/
.matchrow {
    display: grid;
    grid-template-columns: minmax(200px, 1fr) 44px minmax(200px, 1fr) 160px auto;
    gap: 16px;
    align-items: center;
    padding: 16px 8px;
    border-top: 1px solid var(--hairline);
}

.matchrow__side {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.matchrow__label {
    font: 500 13px/1.2 var(--font);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}

.matchrow__amount {
    font: 500 18px/1.3 var(--font);
    font-variant-numeric: tabular-nums;
}

.matchrow__sub {
    font: 400 15px/1.35 var(--font);
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.matchrow__arrow {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--olive);
}

.matchrow__actions {
    justify-self: end;
}

/* Narrow: the two sides stack and the arrow turns into the seam between them. */
@media (max-width: 900px) {
    .matchrow {
        grid-template-columns: 1fr;
        gap: 10px;
    }

    .matchrow__arrow {
        justify-content: flex-start;
        transform: rotate(90deg);
    }

    .matchrow__actions {
        justify-self: start;
    }
}

/* ----------------------------------------------------------------- measurement setup */

/*
    Periods and templates, as cards.

    Narrower than .detailgrid's 440px on purpose: these are summaries with four short lines each, not
    forms, so three fit across a desktop where two of the wider ones would. align-items:start keeps a
    card with a long name from stretching its neighbours to match.
*/
.setupgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: 18px;
    /*
        Stretch, NOT align-items:start.

        These cards differ by a line or two - a name that wraps, a code that is absent, an automatic
        template's extra note - and sized to their own content they came out a few pixels apart, which
        reads as a rendering fault rather than as a difference in the data. Stretching makes a row of
        them match, and .setupcard__foot's margin-top:auto is what puts the actions on one line across
        the row instead of leaving the shorter cards with a gap under their buttons.
    */
    align-items: stretch;
}

.setupcard {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.setupcard__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}

/* min-width:0 is what lets the name below actually ellipsize: a flex item defaults to min-content
   width, so a long measurement name pushes the status pill off the card instead of truncating. */
.setupcard__headtext {
    min-width: 0;
}

.setupcard__title {
    margin: 0;
    font: 500 19px/1.25 var(--font);
    color: var(--ink);
}

.setupcard__sub {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 8px;
    margin: 4px 0 0;
    font: 400 15px/1.35 var(--font);
    color: var(--muted);
}

/* The code is an identifier, so it gets the identifier treatment: monospace-ish tracking and the
   third level of text weight, because it is what you search for and never what you read first. */
.setupcard__code {
    font: 500 13px/1.3 var(--font);
    color: var(--muted-soft);
    letter-spacing: .06em;
}

/*
    What a template does on its own.

    Two tones, because the two states are opposites rather than degrees: a template that creates
    periods by itself is the one worth noticing, and the ordinary manual one recedes into the card.
    Never the error colour - nothing is wrong, something is simply happening.
*/
.setupcard__note {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 13px 15px;
    border-radius: 12px;
    background: var(--surface-neutral);
    border: 1px solid var(--hairline);
    font: 400 15px/1.4 var(--font);
    color: var(--muted);
}

.setupcard__note--auto {
    background: var(--surface-orange);
    border-color: transparent;
    color: var(--rust);
}

.setupcard__note svg {
    flex: none;
}

/* Pushed to the bottom so cards in a row line their actions up even when their names wrap to two
   lines. The rule above it is the card's own divider, matching every other footed card. */
.setupcard__foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px 12px;
    margin-top: auto;
    padding-top: 14px;
    border-top: 1px solid var(--hairline);
}

.setupcard__meta {
    font: 400 15px/1.35 var(--font);
    color: var(--muted);
}

.setupcard__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/*
    The onderdelen table.

    Same shape as .meastable, different columns. Origin is the wide one because it holds a formula or
    a device name rather than a word, and it clips rather than wraps - a formula is not read off this
    table, it is recognised, and a wrapping one would make every row a different height. The last
    column holds two icon buttons: the informative texts, then the row's own edit.
*/
.itemtable__head,
.itemtable__row {
    display: grid;
    grid-template-columns: minmax(200px, 1.4fr) 110px minmax(190px, 1.2fr) 150px 130px 84px;
    gap: 16px;
    min-width: 990px;
    align-items: center;
}

.itemtable__head {
    padding: 18px 8px 12px;
    border-bottom: 1px solid var(--card-border);
    font: 500 13px/1.2 var(--font);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}

.itemtable__row {
    padding: 13px 8px;
    border-bottom: 1px solid var(--hairline);
    border-radius: 10px;
}

.itemtable__row:hover {
    background: var(--surface-neutral);
}

.itemtable__name {
    min-width: 0;
}

.itemtable__label {
    display: block;
    font: 500 17px/1.3 var(--font);
    color: var(--ink);
}

.itemtable__code {
    display: block;
    margin-top: 3px;
    font: 400 14px/1.3 var(--font);
    color: var(--muted-soft);
    letter-spacing: .04em;
}

.itemtable__cell {
    font: 400 16px/1.35 var(--font);
    color: var(--muted);
}

.itemtable__cell--clip {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* An aside that is telling you something rather than explaining something - the dialog counterpart
   of .card__note--accent, and the same rust for the same reason. */
.dialog__hint--warn {
    padding: 11px 14px;
    border-radius: 10px;
    background: var(--surface-orange);
    color: var(--rust);
    font-weight: 500;
}

/*
    The normal values, as age bands.

    Ten columns before the actions - two ages, then four numbers per sex - so it scrolls sideways
    inside the dialog rather than squeezing eight number inputs into nothing. The two header rows are
    load-bearing: eight columns headed "van" and "tot" cannot be told apart, so the sexes span theirs.
*/
.normtable {
    overflow-x: auto;
    padding-bottom: 4px;
}

.normtable__group,
.normtable__head,
.normtable__row {
    display: grid;
    grid-template-columns: 78px 62px repeat(8, minmax(84px, 1fr)) 38px;
    gap: 8px;
    min-width: 940px;
    align-items: center;
}

/* The spanning row. Each label sits over the columns it describes, which is what makes the four
   repeated "van/tot" headings underneath readable. */
.normtable__group {
    padding: 0 2px 6px;
}

.normtable__span {
    font: 500 13px/1.2 var(--font);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}

.normtable__span--age {
    grid-column: span 2;
}

.normtable__span--male,
.normtable__span--female {
    grid-column: span 4;
    padding-left: 2px;
    border-left: 1px solid var(--hairline);
}

.normtable__head {
    padding: 0 2px 10px;
    border-bottom: 1px solid var(--card-border);
    font: 500 12px/1.2 var(--font);
    color: var(--muted-soft);
}

.normtable__row {
    padding: 8px 2px;
    border-bottom: 1px solid var(--hairline);
}

/* Compact enough that eight of them fit a row, and tabular so a column of numbers lines up. */
.normtable__input {
    padding: 8px 10px;
    font-size: 15px;
    font-variant-numeric: tabular-nums;
}

/* A derived age, and the first band's pinned start. Shown as text because it is not a decision -
   an input nobody may type into reads as a field that is broken. */
.normtable__fixed {
    padding-left: 2px;
    font: 500 15px/1.2 var(--font);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.normtable__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    margin-top: 14px;
}

.normtable__hint {
    font: 400 14px/1.35 var(--font);
    color: var(--muted);
}

/* The status pill doubling as a button. Whether a norm exists and setting one are the same question,
   so the pill IS the control - a second pencil beside it would look like the row's own edit. */
.normbutton {
    justify-self: start;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    border-radius: 999px;
}

.normbutton:hover:not(:disabled) {
    box-shadow: 0 0 0 3px var(--surface-neutral);
}

.normbutton:disabled {
    cursor: default;
}

/* Two icon buttons in the row's last cell: the informative texts, then the row's own edit. */
.itemtable__actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* The informative-text boxes grow downwards only: a sideways drag would push a box out of its grid
   column, and three of them stacked is the whole point of the dialog. */
.textsdialog__input {
    resize: vertical;
}

/* ----------------------------------------------------------------- questions & experiences */

/*
    Questions are a COLUMN, experiences a grid.

    Not a styling preference: a question is a thing to work through, read top to bottom and replied
    to one at a time, so it gets the full width and a comfortable measure. An experience is read
    across - you scan five of them looking for one worth quoting - which is what .setupgrid already
    does for cards.
*/
.fb__list {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.fb__card {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.fb__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.fb__who {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
}

/* The sidebar's avatar at row scale. Initials rather than a photo because there are none, and a
   coloured circle with nothing in it is worse than no circle. */
.fb__avatar {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--canvas);
    color: var(--brand);
    font: 500 13px/1 var(--font);
}

.fb__name {
    font: 500 17px/1.3 var(--font);
    color: var(--ink);
}

.fb__name:hover {
    color: var(--accent);
}

.fb__when {
    font: 400 15px/1.3 var(--font);
    color: var(--muted-soft);
}

/*
    A measure, not a width.

    These are sentences somebody typed, and a line running the full 1520px of a desktop is genuinely
    hard to read - the eye loses its place on the way back. 80ch is the design's own figure and about
    where prose stops being comfortable.
*/
.fb__question {
    margin: 0;
    max-width: 80ch;
    font: 400 18px/1.55 var(--font);
    color: var(--ink);
    text-wrap: pretty;
}

.fb__quote {
    margin: 0;
    font: 400 18px/1.6 var(--font);
    color: var(--ink);
    text-wrap: pretty;
}

/* The answer, set apart from the question rather than following it: they were written by different
   people days apart, and running them together reads as one voice. */
.fb__answer {
    padding: 16px 18px;
    border-radius: 12px;
    background: var(--surface-neutral);
    border: 1px solid var(--hairline);
}

.fb__answerhead {
    display: block;
    margin-bottom: 6px;
    font: 500 13px/1.2 var(--font);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}

.fb__answertext {
    margin: 0;
    max-width: 80ch;
    font: 400 17px/1.55 var(--font);
    color: var(--deep);
    text-wrap: pretty;
}

.fb__reply {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.fb__reply textarea {
    line-height: 1.55;
    resize: vertical;
}

.fb__replyfoot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 10px 14px;
}

/* Pushed to the left of the buttons so it is read on the way to them. */
.fb__note {
    margin-right: auto;
    font: 400 14px/1.35 var(--font);
    color: var(--muted);
}

.fb__member {
    color: var(--muted);
}

.fb__member:hover {
    color: var(--accent);
}

/* ----------------------------------------------------------------- statistics */

/*
    Every chart on this screen is CSS.

    Not thrift: a charting library in a WebAssembly bundle is a download on every first paint and a JS
    interop boundary, for four static shapes that never animate, never zoom and are never hovered for
    a tooltip. A bar is a height percentage and a dial is a conic-gradient, which is what the design
    itself draws them as.

    The three series carry ONE colour each across the whole page - the chart, the breakdown bars and
    the dials - so "orange" means "stopped" everywhere rather than in one card.
*/
.chartlegend {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
}

.chartlegend__item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font: 400 15px/1.2 var(--font);
    color: var(--deep);
}

.chartlegend__swatch {
    width: 12px;
    height: 12px;
    border-radius: 3px;
}

.chartlegend__swatch--new,
.breakdown__fill--new {
    background: var(--brand);
}

.chartlegend__swatch--paused,
.breakdown__fill--paused {
    background: var(--gold);
}

.chartlegend__swatch--quit,
.breakdown__fill--quit {
    background: var(--accent);
}

/*
    The bar chart.

    A fixed height with the columns growing from the bottom, so the baseline is a real line rather
    than wherever the tallest month happens to end. The rule under it is the axis.
*/
/*
    A daily series is up to 62 columns where a monthly one is at most twelve.

    So the chart SCROLLS rather than shrinking: sixty columns in the width of eight leaves bars a
    hairline wide with no gap between the three, which is a texture rather than a chart. The minimum
    width is what forces the scrollbar; a monthly series never reaches it and never scrolls.
*/
/*
    The chart fills this and sets its own minimum, so this only scrolls when the buckets need more room
    than the card has. See Statistics.FlowChartStyle.

    THE TOP PADDING IS FOR THE TOOLTIP, and the negative margin puts the chart back where it was.
    overflow-x:auto computes overflow-y to auto as well - the same trap .scrolllist carries a note
    about - and Radzen's tooltip sits ABOVE the column it belongs to, so on a tall bar it reaches past
    the top of the chart's own box and would be clipped there with no way to scroll to it.
*/
.flowscroll {
    overflow-x: auto;
    padding: 48px 0 4px;
    margin-top: -48px;
}

/* Radzen's chart, in this portal's palette rather than the theme's. The series colours are passed
   per series in the markup, from BrandColors; these are the parts that have no attribute - the
   axis, its labels, the gridlines and the tooltip. The gauges take no part of this: their track and
   their fill are both passed as Fill in the markup, and their size is geometry on the scale (see
   Statistics.razor), not CSS. */
.rz-chart {
    --rz-chart-axis-color: var(--card-border);
    --rz-chart-axis-label-color: var(--muted-soft);
    --rz-chart-grid-line-color: var(--hairline);
    --rz-chart-tooltip-background: var(--deep);
    --rz-chart-tooltip-color: #fff;
}

.dial__gauge {
    flex: none;
}

/* A breakdown row: the label and its count on one line, the bar under it. */
.breakdown {
    display: flex;
    flex-direction: column;
    gap: 7px;
    margin-bottom: 14px;
}

.breakdown__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.breakdown__label {
    font: 400 16px/1.35 var(--font);
    color: var(--deep);
}

.breakdown__value {
    font: 400 16px/1.35 var(--font);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.breakdown__track {
    display: block;
    height: 8px;
    border-radius: 999px;
    background: var(--canvas);
    overflow: hidden;
}

.breakdown__fill {
    display: block;
    height: 100%;
}

.dialgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 18px;
}

.dial {
    display: flex;
    align-items: center;
    gap: 16px;
}

/*
    The number in the middle of the gauge. Text only, with no box of its own: the gauge positions its
    value template at the centre, so a sized circle here would fight that placement rather than help
    it.
*/
.dial__value {
    font: 500 18px/1 var(--font);
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

.dial__text {
    min-width: 0;
}

.dial__label {
    display: block;
    font: 500 16px/1.3 var(--font);
    color: var(--ink);
}

.dial__note {
    display: block;
    margin-top: 4px;
    font: 400 15px/1.35 var(--font);
    color: var(--muted);
}

/*
    The range picker: the presets, and the two date inputs that appear BESIDE them when none of the
    presets is the period somebody wants.

    Beside rather than beneath. The inputs are a continuation of the same choice, and a row that grows
    downwards pushes everything under it - four tiles and a chart - down the page every time Custom is
    picked. Wrapping is what keeps that safe on a narrow window, where they drop to their own line
    rather than squeezing the six presets.
*/
.rangebar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: flex-end;
    gap: 10px 14px;
}

.rangebar__custom {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
}

/* ----------------------------------------------------------------- configuration */

/*
    One table for eight lists, so its columns come in as a custom property rather than a class per
    tab. Setting --cfg-cols inline is what lets the descriptor in the code-behind own the layout
    alongside the columns it describes, instead of the two drifting apart in different files.
*/
.cfgtable__head,
.cfgtable__row {
    display: grid;
    grid-template-columns: var(--cfg-cols);
    gap: 16px;
    min-width: 620px;
    align-items: center;
}

.cfgtable__head {
    padding: 18px 8px 12px;
    border-bottom: 1px solid var(--card-border);
    font: 500 13px/1.2 var(--font);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}

/*
    The whole row opens the form, and the NAME is the button that does it: a row that is itself a
    <button> cannot hold the companies' second action, and a role="button" on the row would hide that
    action from assistive tech. So the row takes the mouse's click, the name is what a keyboard reaches,
    and the pointer cursor covers the row so nobody hunts for the one clickable pixel.
*/
.cfgtable__row {
    width: 100%;
    padding: 14px 8px;
    border-bottom: 1px solid var(--hairline);
    border-radius: 10px;
    cursor: pointer;
}

.cfgtable__row:hover {
    background: var(--surface-neutral);
}

/* The name as a button, drawn exactly as the cell beside it. */
.cfgtable__open {
    padding: 0;
    border: 0;
    background: transparent;
    text-align: left;
    cursor: pointer;
}

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

/* A row's action, a column of its own after the cells: right-aligned, and its label stays whole. */
.cfgtable__action {
    justify-self: end;
    white-space: nowrap;
}

/* The head's copy of it: takes the same width, draws nothing. The head sets its titles in spaced
   capitals, which .btn does not undo - and capitals are wider, so without these two resets the copy
   measured more than the button and the titles sat left of their columns. */
.cfgtable__action--spacer {
    visibility: hidden;
    text-transform: none;
    letter-spacing: normal;
}

.cfgtable__cell {
    min-width: 0;
    font: 400 16px/1.35 var(--font);
    color: var(--deep);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The support tab: cards that are a sentence and a control rather than a list. */
.supportgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
    gap: 18px;
    align-items: start;
}

.supportcard {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/*
    The heading's own top margin, trimmed.

    .card__subhead carries 24px above it so headings inside a card breathe, and the existing trim only
    reaches a :first-child of the card itself - this one is nested a level down, so the margin stacked
    on the card's 28px padding and left a band of empty space above every support card's title. Same
    defect the Gegevens cards had, one level deeper.
*/
.supportcard__text {
    min-width: 0;
}

.supportcard__text > .card__subhead:first-child {
    margin-top: 0;
}

.supportcard__action {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: auto;
}

/* The band field takes the room and the button keeps its own width, so a long number does not push
   the action off the card. */
.supportcard__action .field__input {
    flex: 1 1 200px;
    min-width: 0;
}

/* A warning under a field, naming what is wrong with it rather than leaving the save to fail. */
.field__hint {
    font: 400 14px/1.35 var(--font);
    color: var(--muted);
}

.field__hint--warn {
    color: var(--rust);
}

/*
    Supplier contacts: rows inside the form rather than a dialog of their own.

    The update replaces the whole list, so they travel on every save regardless - editing them here
    costs nothing extra, and the alternative was the second modal level the brief rules out.
*/
.contacts {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 4px;
}

.contacts__row {
    display: grid;
    grid-template-columns: minmax(120px, 1.4fr) minmax(140px, 1.6fr) minmax(110px, 1.1fr) minmax(100px, 1fr) 38px;
    gap: 8px;
    align-items: center;
}

/* Pushed to the far left of the action row, away from Bewaren: a destructive button next to the one
   everybody aims for is a misclick waiting to happen. */
.dialog__actions-left {
    margin-right: auto;
}

/* An input whose value will not do. Ringed rather than replaced by a message, because a contact row
   has no room for one and the field is the thing to look at. */
.field__input--warn {
    border-color: var(--rust);
}

/* A contact cell that holds the input and nothing else, so the grid still owns the widths. */
.contacts__cell {
    min-width: 0;
}

/*
    Company members: a dialog of their own, reached from the row - linking and unlinking are endpoints
    of their own and write at once, so a row is a name, a house and a way off the list, and there is
    no Bewaren.
*/
.members {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 4px;
}

/* The picker and its button on one line, the button level with the control rather than the label. */
.members__add {
    display: grid;
    grid-template-columns: minmax(200px, 1fr) auto;
    gap: 12px;
    align-items: center;
}

/* Capped and scrolled: a corporate customer can have a hundred staff training here, and the picker
   above and the button below must stay in reach. Same overhang allowance as .scrolllist. */
.members__list {
    max-height: 50vh;
    overflow-y: auto;
    padding: 0 10px;
    margin: 0 -10px;
}

/*
    The onderdelen of a template or a measurement period, in the order they will be filled in.

    The position number IS the Order the API stores (i + 1), which is why it is printed rather than
    left to be counted. Same shape as .members above - a picker that adds, rows that carry their own
    way off the list - plus two arrows, because here the order is the point.
*/
.orderlist {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 4px;
}

/* Capped and scrolled like .members__list, lower because a whole form sits above it in the same
   dialog. Same overhang allowance for .dashrow's negative margin. */
.orderlist__rows {
    max-height: 40vh;
    overflow-y: auto;
    padding: 0 10px;
    margin: 0 -10px;
}

/* Right-aligned tabular digits, so 9 and 10 line up and every name starts on the same edge. */
.orderlist__pos {
    flex: none;
    width: 24px;
    text-align: right;
    font: 500 15px/1 var(--font);
    font-variant-numeric: tabular-nums;
    color: var(--muted-soft);
}

/* The first row's up and the last row's down are disabled rather than hidden, so the cluster keeps
   its width and the buttons stay under each other down the list. .dashrow__action has no disabled
   state of its own - nothing else disables one - and its hover would still tint. */
.orderlist .dashrow__action:disabled {
    opacity: .35;
    cursor: default;
}

.orderlist .dashrow__action:disabled:hover {
    background: #fff;
    color: var(--brand);
}

/*
    The date slot on a request's sub-line: the period it covers, or the day it was made.

    ONE OR THE OTHER, never both, so this needs no colour of its own to be told apart from anything -
    it inherits the sub-line's. Two dates a shade apart was the first attempt and it read as noise.

    Plain, unlike .dashrow__flag beside it. That one is tinted because it marks something somebody just
    DID whose effect is otherwise invisible - a reminder sent - and a second tinted thing on the row
    made it stop standing out, which was the whole point of tinting it.
*/
.dashrow__period {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The member a scanned band turned out to belong to: the avatar from the questions list, their name
   and the house they train in - because a band resolves across every one of them, and "which house"
   is the first thing somebody at a desk in another one needs. */
.scanresult {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 18px;
    border-radius: 12px;
    background: var(--surface-neutral);
    border: 1px solid var(--hairline);
}

.scanresult__text {
    min-width: 0;
}

.scanresult__name {
    display: block;
    font: 500 18px/1.3 var(--font);
    color: var(--ink);
}

.scanresult__house {
    display: block;
    margin-top: 3px;
    font: 400 15px/1.35 var(--font);
    color: var(--muted);
}

/* -----------------------------------------------------------------
    the navigation becomes a drawer

    1366px, which is an iPad Pro 12.9 in landscape - so every iPad, in either orientation, gets the
    272px back, and so does a 1366x768 laptop. Below the design brief's own 1280px floor this is no
    longer a question: the screens here are wide tables, several of which already scroll sideways at
    that width, and a permanent fifth of the viewport spent on navigation is the first thing to give.

    The shell keeps its fixed viewport height. The sidebar goes position:fixed and out of flow, so
    .shell__main simply becomes full width - and the cards that scroll internally (.scrolllist) keep
    the bounded height they need, which switching the shell to a block layout would have taken away.
*/

@media (max-width: 1366px) {
    .sidebar {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        z-index: 60;
        width: min(var(--sidebar-width), 84vw);
        height: auto;
        transform: translateX(-100%);
        transition: transform .22s ease;
        box-shadow: 0 0 40px rgba(28, 26, 18, .28);
    }

    .sidebar--open {
        transform: translateX(0);
    }

    /* Above the top bar's own z-index of 20, below the drawer. Tapping it is the other way out,
       and the only one when the drawer was opened by mistake. */
    .sidebar-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 50;
        background: rgba(28, 26, 18, .5);
        opacity: 0;
        pointer-events: none;
        transition: opacity .22s ease;
    }

    .sidebar-backdrop--visible {
        opacity: 1;
        pointer-events: auto;
    }

    .navtoggle {
        display: inline-flex;
    }

    /* The space the drawer buys, spent rather than left as margin. */
    .topbar {
        padding: 12px 20px;
    }

    .shell__body {
        padding: 24px 20px 48px;
    }

    .envbanner {
        padding: 8px 20px;
    }
}

/*
    An iPad in portrait, where the top bar's own row is the next thing to run out. The "HEALTH HOUSE"
    caption goes and the picker narrows - the same trade .langpicker already makes in ui.css, and for
    the same reason: the control's own value says what it is.
*/

@media (max-width: 900px) {
    .topbar__label {
        display: none;
    }

    .housepicker .rz-dropdown {
        min-width: 168px;
    }
}

/* A reduced-motion preference stops the slide, not the drawer. */

@media (prefers-reduced-motion: reduce) {
    .sidebar,
    .sidebar-backdrop {
        transition: none;
    }
}
