/* =============================================================================
   Ressourcenplanung — application styles
   Consumes the semantic layer of tokens.css only. No primitive values here
   except where a value is genuinely one-off geometry.
   ============================================================================= */

/* ==========================================================================
   1 · Base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

/*
 * The gutter for the page scrollbar is always reserved. The planning grid
 * measures the page width while it builds its column template, and on a fresh
 * load the page is still short: no scrollbar, a page 15px wider, a Projekt
 * column 15px wider — until the next render measured the page with the
 * scrollbar the grid itself had brought, and the column snapped narrower.
 * Every render now sees the same width, scrollbar or not.
 */
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }

body {
  margin: 0; min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column;
  background: var(--color-page-bg);
  color: var(--color-text-body);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
}

/*
 * Headings inherit size and weight. Left to the UA, .pgrouphead — which sets
 * font-weight but never font-size — fell through to h2's 1.5em and rendered at
 * an off-scale 19.5px.
 */
h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
h1, h2, h3, p, dl, dd, figure { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: var(--color-text-link); text-decoration: underline; text-underline-offset: 2px; }
a:hover { color: var(--color-text-link-hover); }

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

:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-ring-offset); border-radius: var(--radius-xs); }
/*
 * These sit inside something with overflow:hidden, which ate the whole 5px ring
 * box — .prow__title lost it entirely. Drawn inside, it survives the clip.
 */
.prow__title:focus-visible,
.sorthead:focus-visible,
.pcell--val:focus-visible,
.segmented button:focus-visible,
.pgrouphead__toggle:focus-visible { outline-offset: -3px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.skip-link {
  position: absolute; left: var(--space-4); top: -3rem; z-index: var(--z-toast);
  padding: var(--space-4) var(--space-6); border-radius: var(--radius);
  background: var(--color-surface); color: var(--color-text-strong);
  box-shadow: var(--shadow-menu); text-decoration: none; font-weight: var(--font-semibold);
  transition: top var(--dur) var(--ease);
}
.skip-link:focus { top: var(--space-4); }

.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.icon { flex: 0 0 auto; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; }

/* Numbers are tabular everywhere — a system rule, not an option. */
.kpi__value, .pcell, .barlist__value, .colchart__val,
.log__date, .log__value, .sorthead__label, .capband__cell,
.minigrid__v { font-variant-numeric: tabular-nums; }

/* ==========================================================================
   2 · Boot / error states
   ========================================================================== */

.boot {
  display: flex; align-items: center; gap: var(--space-5);
  max-width: 640px; margin: 15vh auto; padding: var(--space-11);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-lg); background: var(--color-surface);
  color: var(--color-text-muted);
}
.boot--error { display: block; color: var(--color-text); }
.boot--error strong { color: var(--color-danger-fg); }
.boot--error p { margin-top: var(--space-4); }
.boot__hint { font-size: var(--text-sm); color: var(--color-text-muted); }
.boot__hint code {
  font-family: var(--font-mono); font-size: .92em;
  background: var(--color-surface-sunken); padding: 1px var(--space-3); border-radius: var(--radius-sm);
}
.boot__spinner {
  width: 16px; height: 16px; border-radius: var(--radius-pill);
  border: 2px solid var(--color-border-rule); border-top-color: var(--color-accent);
  animation: spin var(--dur-spin) linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   3 · Shell — navy header, content band, footer
   ========================================================================== */

.shell-header { background: var(--color-brand); color: var(--color-brand-fg); }

.shell-header__inner {
  position: relative;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-10);
  height: var(--header-height);
  max-width: var(--layout-width); margin: 0 auto; padding: 0 var(--shell-pad-x);
}

/*
 * The brand yields before the page does. Rigid, it set a 494px floor that made
 * every view scroll horizontally on a phone; the four header controls are
 * 32-34px each and stay rigid.
 */
.brand {
  display: flex; align-items: center; gap: var(--space-6);
  color: inherit; text-decoration: none; flex: 0 1 auto; min-width: 0;
}
.brand__text, .brand__sub {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.brand:hover { color: inherit; }
.brand__text {
  display: block; font-size: var(--text-base); font-weight: var(--font-semibold);
  line-height: var(--leading-snug);
}
.brand__sub {
  display: block; font-weight: var(--font-regular); color: var(--color-brand-fg-muted);
}

/*
 * Centred on the bar, not between its neighbours: as a flex child it drifted
 * whenever the brand or the action group changed width. Taken out of the flow
 * it stays put, and 40 % keeps it clear of both even at 1024px, below which it
 * is hidden anyway.
 */
.proto-pill {
  position: absolute; left: 50%; transform: translateX(-50%);
  max-width: 40%; padding: var(--space-3) var(--space-7);
  border: var(--border-hairline) solid var(--color-brand-control-border);
  border-radius: var(--radius-pill);
  background: var(--color-brand-control-bg-strong);
  font-size: var(--text-sm); font-weight: var(--font-semibold);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.shell-header__actions { display: flex; align-items: center; gap: var(--space-5); flex: 0 0 auto; }

.hdr-btn {
  display: inline-flex; align-items: center; gap: var(--space-3);
  height: var(--control-height-lg); padding: 0 var(--space-6);
  border: var(--border-hairline) solid var(--color-brand-control-border);
  border-radius: var(--radius);
  background: var(--color-brand-control-bg); color: var(--color-brand-fg);
  font-size: var(--text-base); font-weight: var(--font-semibold);
  cursor: pointer;
}
.hdr-btn:hover { background: var(--color-brand-control-bg-strong); }
.hdr-btn--icon { width: var(--control-height-lg); padding: 0; justify-content: center; color: var(--color-brand-fg-muted); }
.hdr-btn--icon:hover { color: var(--color-brand-fg); }

/* .dd already supplies the positioning context the badge anchors to. */
.hdr-notify { flex: 0 0 auto; }

/*
 * A notification row is wider than a menu item: a mark, two lines of text and a
 * date. It keeps .dd__item's hover, focus and press states — only the inside
 * differs — so the panel stays one system.
 */
/* .dd__item centres its children and is declared later, so this needs the
   extra class to win — a two-line row must hang its mark on the title. */
.dd__item.notify {
  align-items: flex-start; gap: var(--space-6); padding: var(--space-6) var(--space-5);
  border-radius: 0;
}
.notify + .notify { border-top: var(--border-hairline) solid var(--color-border-subtle); }
.notify__mark { margin-top: 5px; }   /* optical centre of the title line */
.notify__mark { flex: 0 0 auto; }
.notify__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* Title and date share a line so the message below gets the panel's full width. */
.notify__line {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-5);
}
.notify__title {
  font-weight: var(--font-semibold); color: var(--color-text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.notify__text { color: var(--color-text-secondary); font-size: var(--text-sm); white-space: normal; }
.notify__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-5);
  padding: var(--space-3) var(--space-5) var(--space-5);
  font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-muted);
}
.notify__none {
  margin: 0; padding: var(--space-8) var(--space-5);
  color: var(--color-text-muted); text-align: center;
}
.notify__all {
  margin-top: var(--space-3); border-top: var(--border-hairline) solid var(--color-border);
  border-radius: 0 0 var(--radius) var(--radius);
  color: var(--color-text-link);
}
.notify__all > span { flex: 1; }
.hdr-notify__badge {
  position: absolute; top: -6px; right: -6px;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 4px;
  border: var(--border-emphasis) solid var(--color-brand);
  border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-text-inverse);
  font-size: var(--text-2xs); font-weight: var(--font-bold); font-variant-numeric: tabular-nums;
}

.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--avatar-size); height: var(--avatar-size); flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-accent-fg);
  font-size: var(--text-sm); font-weight: var(--font-semibold);
}

/* Expandable search — an icon that becomes a field in place. */
.xsearch {
  display: inline-flex; align-items: center; flex: 0 0 auto;
  height: var(--control-height-lg); border-radius: var(--radius);
  border: var(--border-hairline) solid var(--color-border-strong);
  background: transparent; color: var(--color-text-secondary);
  cursor: pointer; overflow: hidden;
}
.xsearch:not(.is-open) { width: var(--control-height-lg); justify-content: center; padding: 0; position: relative; }
.xsearch:not(.is-open):hover { background: var(--color-surface); }
/*
 * No entrance animation. A render replaces every node, so an animation on the
 * open field restarted on each debounced keystroke — the field pumped between
 * 134px and 250px about three times a second while typing. Animations here have
 * to be driven by a class that outlives the render, the way .is-leaving is.
 */
/*
 * Open, the field gives way before the labelled triggers do. At a fixed 250px
 * it was the one thing in the row that could not shrink, and the 216px it adds
 * over the closed icon were never in the row's budget — section 19 measured
 * the row with the search closed — so on a wide screen the group trigger
 * cropped to «Gruppieren ... Ke...». The floor is sized for the placeholder
 * «Projekt, ID oder Person» at 14px with the icon and the close button, about
 * 210px; an estimate, not yet measured on the rendered field.
 */
.xsearch.is-open {
  flex: 0 1 250px; min-width: 210px; padding-inline: var(--space-4); gap: var(--space-3); cursor: text;
  background: var(--color-surface);
  border-color: var(--color-border-accent);
}

.xsearch input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font-size: var(--text-btn); color: var(--color-text-strong);
}
.xsearch input::-webkit-search-cancel-button { display: none; }
.xsearch__icon { display: inline-flex; flex: 0 0 auto; color: var(--color-text-muted); }
.xsearch__close {
  display: inline-flex; flex: 0 0 auto; border: 0; padding: 2px; background: none;
  color: var(--color-text-muted); cursor: pointer; border-radius: var(--radius-sm);
}
.xsearch__close:hover { background: var(--color-surface-sunken); color: var(--color-text-strong); }

/* A dot marks a query that is still active while the field is collapsed. */
.xsearch__dot {
  position: absolute; top: 6px; right: 6px; width: 6px; height: 6px;
  border-radius: var(--radius-pill); background: var(--color-accent);
}

/* body and #app both stretch so the footer can sit at the bottom of a short page. */
.app { display: flex; flex-direction: column; flex: 1 0 auto; }
.app > main { flex: 1 0 auto; }

/*
 * A scroller with more to show fades at that edge. Driven by scroll position,
 * so it never promises content that is not there.
 */
.scrollbox { position: relative; }

/*
 * No scrollbar. Stepping the window with the arrows beside «Heute» is the way
 * through time; a bar under every card only competed with them for attention.
 * The wheel and the trackpad still work.
 */
[data-scroll] { scrollbar-width: none; }
[data-scroll]::-webkit-scrollbar { width: 0; height: 0; }


.wrap { max-width: var(--layout-width); margin: 0 auto; padding-inline: var(--shell-pad-x); }
/*
 * A table wants width, prose wants a measure. On a 1920 or 2560 screen the
 * planning grid was an island: 1440px used, 480 to 1120px empty.
 *
 * One measure for every page that carries the toolbar. The dashboard used to
 * keep the narrower one, and the toolbar — the same control row on every tab
 * — changed width on the way there, which read as a different page rather
 * than a different tab. The API reference kept 1440px for a while, as prose;
 * but it opens from the same shell, and a page that steps in by 240px on each
 * side read as a different site.
 */
:root[data-tab="overview"],
:root[data-tab="dashboard"],
:root[data-tab="history"],
:root[data-tab="api"],
:root[data-tab="export"] { --layout-width: var(--layout-width-grid); }

/*
 * Ground rules: navy chrome, one white strip that answers "where am I", then a
 * grey canvas on which every white box is content. Three grounds, one meaning each.
 */

.shell-footer {
  margin-top: auto;
  border-top: var(--border-hairline) solid var(--color-border);
  background: var(--color-surface);
  font-size: var(--text-sm); color: var(--color-text-muted);
}
.shell-footer__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-8); flex-wrap: wrap; padding-block: var(--space-4);
  min-height: 40px; font-size: var(--text-xs);
}
.shell-footer__links { display: flex; align-items: center; gap: var(--space-8); }
.shell-footer__links a { display: inline-flex; align-items: center; min-height: var(--target-min); color: var(--color-text-secondary); }

/* ==========================================================================
   4 · Page header, tabs, KPI strip
   ========================================================================== */

.page-header { padding-block: var(--space-6) 0; }


.page-header__row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-11); margin-top: var(--space-4);
}
/* 1.5 is for running text; a heading set on it wastes a row of the laptop. */
h1, h2, h3,
.page-title, .bi-card__title,
.modal__title, .kpi__value, .sheet__title { line-height: var(--leading-snug); }

/* Below ~20px the correction is under a fifth of a pixel per character and
   buys nothing but an inconsistency, so only the two top steps carry it. */
.page-title { letter-spacing: var(--tracking-h2); }
.modal__title, .kpi__value { letter-spacing: var(--tracking-h3); }

.page-title {
  font-size: var(--text-2xl); font-weight: var(--font-semibold);
  color: var(--color-text-strong);
}
.page-header__actions { display: flex; align-items: center; gap: var(--space-5); flex: 0 0 auto; }

.tabs {
  display: flex; gap: var(--space-2); margin-top: var(--space-6);
  box-shadow: inset 0 -1px 0 var(--color-border);
  /* Below 411px the fourth tab was simply unreachable. */
  overflow-x: auto; scrollbar-width: none; scroll-padding-inline: var(--space-8);
}
.tabs::-webkit-scrollbar { width: 0; height: 0; }
.tabs__tab { flex: 0 0 auto; }
.tabs__tab {
  display: inline-flex; align-items: center;
  height: var(--tabbar-height); padding: 0 var(--space-8);
  border-bottom: var(--border-emphasis) solid transparent;
  border-radius: var(--radius) var(--radius) 0 0;
  background: transparent; color: var(--color-text-secondary);
  font-size: var(--text-tab); font-weight: var(--font-regular);
  text-decoration: none; cursor: pointer;
}
/*
 * The bar's rule is an inset shadow on .tabs, so a hover background clipped to
 * the border box would paint straight over it. Clipping to the padding box
 * leaves the tab's own 2px border strip unpainted and the rule runs through.
 */
.tabs__tab:hover {
  color: var(--color-text-strong);
  background: var(--color-surface-hover); background-clip: padding-box;
}
.tabs__tab.is-active {
  color: var(--color-text-strong); font-weight: var(--font-semibold);
  border-bottom-color: var(--color-text-strong);
}

/* The tiles share the row: four of them, each a quarter of the width. */
.kpi-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-8);
}
.kpi {
  padding: var(--space-10);
  border-radius: var(--radius-lg); background: var(--color-surface);
  box-shadow: var(--shadow-raised);
}
.kpi__label { font-size: var(--text-base); font-weight: var(--font-regular); color: var(--color-text-secondary); }
.kpi__value {
  margin-top: var(--space-3); font-size: var(--text-xl);
  font-weight: var(--font-semibold); color: var(--color-text-strong);
}
.kpi__note { margin-top: var(--space-1); font-size: var(--text-sm); color: var(--color-text-muted); }

/* ==========================================================================
   5 · Buttons, switches, chips, dropdowns
   ========================================================================== */

.btn {
  display: inline-flex; align-items: center; gap: var(--space-3);
  height: var(--control-height-lg); padding: 0 var(--space-7);
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius);
  background: transparent; color: var(--color-text);
  font-size: var(--text-btn); font-weight: var(--font-regular);
  cursor: pointer; white-space: nowrap;
}
.btn:hover { background: var(--color-surface); border-color: var(--color-border-emphasis); }
.btn.is-open { border-color: var(--color-border-contrast); }

/*
 * One disabled register for the family, on the base class.
 *
 * Only .btn--primary had one, so a disabled timebar arrow rendered every channel
 * identical to its live neighbour — and because :hover carries no :not(:disabled)
 * guard, it lit up under the cursor and gave press feedback while doing nothing.
 * The second rule is therefore not decoration; it is what stops an inert control
 * answering the pointer.
 */
.btn:disabled {
  background: var(--color-neutral-bg-alt); border-color: var(--color-neutral-bg-alt);
  color: var(--color-text-secondary); cursor: not-allowed;
}
.btn:disabled:hover, .btn:disabled:active:active {
  background: var(--color-neutral-bg-alt); border-color: var(--color-neutral-bg-alt);
}

.btn--primary {
  background: var(--color-accent); border-color: var(--color-accent);
  color: var(--color-accent-fg); font-weight: var(--font-semibold);
}
.btn--primary:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }
/*
 * The base rule above carries this now. Kept as a note, because it records why:
 * an explicit disabled pair rather than a fade — opacity .45 composited the
 * white label onto the blue fill at 1.97:1, which is not de-emphasised, it is
 * unreadable, and it still read as a washed-out primary.
 */

/*
 * One height for every button. The actions in the tab line were 38px over a
 * toolbar of 34px controls — a weight difference from when they sat in the
 * page header, and 20px apart it read as a mistake.
 */
.btn--square { width: var(--control-height-lg); height: var(--control-height-lg); padding: 0; justify-content: center; }

/*
 * A press has to register visually. With a full re-render on every click, a
 * click landing on a slow frame otherwise gave no confirmation at all.
 *
 * The extra :is() carries one class of specificity, so a later :hover written
 * at the same weight no longer wins on source order — seven of these ten had
 * silently stopped responding to a press.
 */
:is(.btn, .iconbtn, .dd__item, .tabs__tab, .segmented button,
    .pgrouphead__toggle, .sheetbar button,
    .pop__step, .toolbar__check, .modal__close):active:active {
  background: var(--color-surface-pressed);
}

/*
 * A press that replaces a fill sets its foreground in the same breath. The rule
 * above only ever set `background`, so on the three filled controls the press
 * swapped a blue or a near-black for a near-white and left the label white.
 *
 * Doubled class, because `:is()` takes the specificity of its strongest
 * argument: `.sheetbar button` is a class *and* a type, so the rule above weighs
 * (0,3,1) and quietly outranks a plain `.btn--primary:active:active` at (0,3,0).
 * Measured while that was true: the border took blue-800 from here and the fill
 * took grey-300 from there — half the rule applied, which is worse than none.
 */
.btn.btn--primary:active:active {
  background: var(--color-accent-pressed); border-color: var(--color-accent-pressed);
  color: var(--color-accent-fg);
}
:is(.segmented button.is-on, .sheetbar button.is-on):active:active {
  background: var(--color-toggle-on-pressed); border-color: var(--color-toggle-on-pressed);
  color: var(--color-toggle-on-fg);
  /* The hover brightness applies while the pointer is down too, so it is turned
     off here — otherwise the press is measured from a lightened hover and the
     step collapses to 1.21. */
  filter: none;
}
/* A light press is invisible on the navy header, so those two darken instead. */
.hdr-btn:active:active, .avatar[data-act]:active:active {
  background: var(--alpha-white-25);
}

/* A chosen control still answers the pointer; without this it read as dead. */

/*
 * The only action in the app with no way back looks like it — and its edge is
 * visible. At --color-danger-border it measured 1.77:1 in the modal footer.
 * The token itself is left alone: the overload chart bar uses it as a *fill*
 * against a red-600 border, and repointing it would collapse that pair.
 */
.iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: var(--border-hairline) solid transparent; border-radius: var(--radius);
  background: transparent; color: var(--color-text-secondary); cursor: pointer;
}
.iconbtn:hover, .iconbtn.is-open {
  background: var(--color-surface-sunken); border-color: var(--color-border-strong);
}

.linkbtn {
  min-height: var(--target-min); border: 0; padding: 0; background: none;
  color: var(--color-text-link); font-size: var(--text-base); font-weight: var(--font-semibold);
  text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px;
}
.linkbtn:hover { color: var(--color-text-link-hover); }

.switch {
  position: relative; flex: 0 0 auto;
  width: var(--switch-width); height: var(--switch-height);
  border-radius: var(--radius-pill); background: var(--color-toggle-off);
  transition: background var(--dur-fast) var(--ease);
}
.switch__knob {
  position: absolute; top: 2px; left: 2px;
  width: var(--switch-knob); height: var(--switch-knob);
  border-radius: var(--radius-pill); background: var(--color-toggle-knob);
  transition: left var(--dur-fast) var(--ease);
}
.switch.is-on { background: var(--color-toggle-on); }
.switch.is-on .switch__knob { left: calc(var(--switch-width) - var(--switch-knob) - 2px); }

.badge-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: var(--radius-pill);
  background: var(--color-text-strong); color: var(--color-text-inverse);
  font-size: var(--text-xs); font-weight: var(--font-semibold);
}
.count-pill {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 22px; padding: 0 var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--color-neutral-bg-alt); color: var(--color-text);
  font-size: var(--text-sm); font-weight: var(--font-semibold);
}

.chip {
  display: inline-flex; align-items: center; gap: var(--space-3);
  height: var(--control-height-sm); padding: 0 var(--space-4) 0 var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--fill-chip); color: var(--color-text);
  font-size: var(--text-base); font-weight: var(--font-semibold);
  /* A search term is free text and a chip repeats it. Without a ceiling one
     long unbroken token sets the width of the whole document. */
  max-width: 22rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The glyph stays small; the hit area is the one the design system requires. */
.chip__x, .xsearch__close {
  min-width: var(--target-min); min-height: var(--target-min);
  display: inline-flex; align-items: center; justify-content: center;
}

.chip__x {
  display: inline-flex; border: 0; padding: 0; background: none;
  color: var(--color-text-muted); cursor: pointer;
}
.chip__x:hover { color: var(--color-danger-solid); }

.segmented { display: inline-flex; border: var(--border-hairline) solid var(--color-border-strong); border-radius: var(--radius); overflow: hidden; }
.segmented button {
  height: var(--control-height); padding: 0 var(--space-7);
  border: 0; border-left: var(--border-hairline) solid var(--color-border-strong);
  background: transparent; color: var(--color-text);
  font-size: var(--text-btn); cursor: pointer;
}
.segmented button:first-child { border-left: 0; }
.segmented button:hover { background: var(--color-surface); }
/* A chosen segment still answers the pointer; without this it read as dead. */
.segmented button.is-on:hover { filter: brightness(1.12); }
.segmented button.is-on {
  background: var(--color-toggle-on); color: var(--color-toggle-on-fg); font-weight: var(--font-semibold);
}

/* Dropdowns ---------------------------------------------------------------- */

.dd { position: relative; }
/*
 * Fixed, not absolute — and positioned entirely by positionMenu().
 *
 * On a phone the toolbar is a sideways scroller, and a scroll container clips
 * its children on both axes: `overflow-x: auto` forces the other axis from
 * visible to auto. The panel opened, was positioned correctly, and was cut off
 * by a 34px-high box — seven of nine menus were invisible below 700px, which is
 * every filter and the grouping. A fixed box is not clipped by an ancestor's
 * overflow, and nothing in the tree establishes a containing block that would
 * take that away.
 *
 * The alignment classes below no longer position anything; they say which edge
 * the panel prefers to line up with, and positionMenu() reads them.
 */
.dd__panel {
  position: fixed; z-index: var(--z-popover);
  width: var(--dd-w, 244px);
  /* never wider or taller than the window leaves room for */
  max-width: calc(100vw - 2 * var(--space-8));
  max-height: min(70vh, var(--dd-max-h, 70vh));
  padding: var(--space-4);
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-menu-lg);
  display: flex; flex-direction: column; gap: 1px;
  overflow-y: auto;
}

.dd__item {
  display: flex; align-items: center; gap: var(--space-5);
  width: 100%; min-height: var(--control-height); padding: var(--space-3) var(--space-5);
  border: 0; border-radius: var(--radius);
  background: transparent; color: var(--color-text);
  font-size: var(--text-base); text-align: left; cursor: pointer;
}
.dd__item > span:first-child:not(.dd__tickbox):not(.notify__mark) { flex: 1; min-width: 0; }
.dd__item:hover { background: var(--color-surface-hover); }
.dd__item.is-checked:hover { background: var(--blue-100); }
.dd__item.is-checked {
  background: var(--color-surface-selected); color: var(--color-text-strong);
  font-weight: var(--font-semibold);
}
/* Greyed, not gone: a switch that cannot apply right now still has to be
   read to be understood. 4.8:1 — the subtle tone at 2.5:1 was a shape. */
.dd__item[aria-disabled="true"], .dd__item:disabled {
  color: var(--color-text-disabled); cursor: default; background: transparent;
}
.dd__label { flex: 1; min-width: 0; }
.dd__meta { flex: 0 0 auto; color: var(--color-text-muted); font-size: var(--text-sm); font-weight: var(--font-regular); }
.dd__check { flex: 0 0 auto; display: inline-flex; color: var(--color-accent); }

.dd__tickbox {
  flex: 0 0 16px; width: 16px; height: 16px; box-sizing: border-box;
  border: var(--border-control) solid var(--color-border-emphasis);
  border-radius: var(--radius-sm); background: var(--color-surface);
}
.dd__item.is-checked .dd__tickbox {
  background: var(--color-accent); border-color: var(--color-accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-size: 12px; background-position: center; background-repeat: no-repeat;
}

.dd__grouplabel {
  padding: var(--space-2) var(--space-5) var(--space-3);
  font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-muted);
}
.dd__bulk {
  display: flex; align-items: center; gap: var(--space-4);
  padding: 0 var(--space-5) var(--space-4); color: var(--color-text-muted);
}
.dd__bulk button {
  border: 0; padding: 0; background: none;
  color: var(--color-text-link); font-size: var(--text-sm); font-weight: var(--font-semibold); cursor: pointer;
}
.dd__divider { height: 1px; margin: var(--space-3) 0; background: var(--color-border); }
.dd__segmented { padding: 0 var(--space-5) var(--space-5); }

/* Search inside a menu — the lead list has to survive a real directory. */
.dd__search { padding: 0 var(--space-5) var(--space-4); }
.dd__searchfield {
  display: flex; align-items: center; gap: var(--space-4);
  height: var(--control-height); padding: 0 var(--space-5);
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius); background: var(--color-surface);
  color: var(--color-text-muted);
}
.dd__searchfield:focus-within { border-color: var(--color-border-accent); }
.dd__searchfield input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font-size: var(--text-base); color: var(--color-text-strong);
}
.dd__searchfield input::-webkit-search-cancel-button { display: none; }

/* The list scrolls; the search, the bulk row and the footnote stay put. */
.dd__scroll { max-height: 214px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.dd__empty { padding: var(--space-6) var(--space-5); font-size: var(--text-base); color: var(--color-text-muted); }
.dd__segmented .segmented { width: 100%; }
.dd__segmented .segmented button { flex: 1; }

/* ==========================================================================
   6 · Toolbar / filter bar / time controls
   ========================================================================== */

.content { display: flex; flex-direction: column; gap: var(--space-8); padding: var(--space-6) 0 var(--space-13); }
/*
 * Toolbar, filter summary and time scale are one cluster; spaced like the other
 * peers they read as four unrelated bars and cost 20px of a 800px laptop.
 */
.toolbar + .filterbar,
.filterbar + .sheetbar, .toolbar + .sheetbar { margin-top: calc(var(--space-5) - var(--space-8)); }

.toolbar {
  display: flex; align-items: center; gap: var(--space-4);
  flex-wrap: nowrap; min-width: 0;
}
/* Labelled triggers give up width before the row ever breaks. */
.toolbar > .dd { min-width: 0; }
.toolbar > .dd > .btn { max-width: 100%; }
.toolbar .btn > :not(.icon):not(.badge-count) {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.toolbar .btn { min-width: 0; }
/* A filter you switch on, not a mode you enter — so it reads as a checkbox. */
.toolbar__check {
  display: inline-flex; align-items: center; gap: var(--space-4);
  min-height: var(--control-height-lg); padding-inline: var(--space-4);
  border-radius: var(--radius); cursor: pointer;
  font-size: var(--text-base); color: var(--color-text-secondary);
  white-space: nowrap;
}
.toolbar__check:hover { background: var(--color-surface-hover); color: var(--color-text-strong); }
.toolbar__check input { width: 16px; height: 16px; margin: 0; accent-color: var(--color-brand); cursor: pointer; }
.toolbar__check:has(input:checked) { color: var(--color-text-strong); font-weight: var(--font-semibold); }

.toolbar__sep { width: 1px; height: var(--space-11); background: var(--color-border); }
.toolbar__spacer { flex: 1; }

/*
 * A text row between two rows of controls. The min-height keeps it from
 * reading as a caption pressed against the bar above it.
 */
.filterbar {
  display: flex; align-items: center; gap: var(--space-5); flex-wrap: wrap;
  min-height: var(--control-height);
  font-size: var(--text-base); color: var(--color-text-muted);
}
.filterbar__label { flex: 0 0 auto; }
.filterbar__none { color: var(--color-text-muted); }
.filterbar__count { font-size: var(--text-sm); color: var(--color-text-muted); }

/* What the search matched, in the cells that carry the searched text. The
   ground is widened by a pixel with a shadow rather than with padding, so the
   mark takes no room of its own in a cell that truncates. */
.hit {
  background: var(--color-mark-bg); color: var(--color-mark-fg);
  font-weight: var(--font-semibold);
  border-radius: var(--radius-xs); box-shadow: 0 0 0 1px var(--color-mark-bg);
}

/* Says what the window could not hold. A statement of fact, not a warning. */
.dropped-note {
  display: flex; align-items: center; gap: var(--space-4);
  margin: 0;
  font-size: var(--text-sm); color: var(--color-text-muted);
}
.dropped-note .icon { flex: 0 0 auto; }



/* ==========================================================================
   7 · Pensum grid
   ========================================================================== */

.grid-card { display: flex; flex-direction: column; }

/*
 * Each group and the footer are their own card, and each card wraps its own
 * scroller. The frame therefore belongs to the page and stays put while the
 * time axis moves inside it; built the other way round it travelled with the
 * content and its edges left the window. app.js keeps the scrollers in step,
 * so every card still shows the same time window.
 */
.pblock {
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
}
.pblock + .pgroup, .pgroup + .pblock { margin-top: var(--space-13); }
.pblock > .prow:first-child { border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
.pblock > .prow:last-child { border-radius: 0 0 var(--radius-lg) var(--radius-lg); border-bottom: 0; }
.pblock > .prow:first-child > :last-child { border-top-right-radius: var(--radius-lg); }
.pblock > .prow:last-child > :last-child { border-bottom-right-radius: var(--radius-lg); }

.pgrid { position: relative; overflow-x: auto; }

/*
 * One track for the whole grid. Every row is its own CSS grid, so they only
 * line up when they are all exactly as wide as the track — letting each row
 * size itself to its own content pulls the header out of step with the cells.
 */
.pgrid__track { min-width: max-content; }   /* overridden inline with the measured width */

.prow {
  display: grid; align-items: center; width: 100%;
  min-height: var(--row-height);
  border-bottom: var(--border-hairline) solid var(--color-border-subtle);
  background: var(--row-bg, var(--color-surface));
  --row-bg: var(--color-surface);
  font-size: var(--text-base);
}
.prow:hover:not(.prow--head):not(.prow--sum):not(.prow--foot):not(.prow--load) {
  --row-bg: var(--color-surface-hover);
}

/*
 * The identifying columns stay put while the quarters scroll under them.
 * Frozen cells need an opaque ground, hence --row-bg on every row.
 */
.is-frozen {
  position: sticky; z-index: var(--z-frozen);
  /* Full row height, or the quarters would show through above and below. */
  align-self: stretch;
  background: var(--row-bg, var(--color-surface));
}
/* A summary label spans the whole lead pane, so it is both first and last. */
.prow--sum .is-frozen, .prow--foot .is-frozen, .prow--load .is-frozen { left: 0; }

/*
 * Both edges of a scrolling card carry the same ramp, mirrored: on the left
 * where the frozen column ends and the axis passes underneath, on the right
 * where the axis continues past the card. One strip per card, not one per
 * cell — drawn per cell it broke at every row separator.
 */
.pblock { position: relative; }
.pblock::before,
.pblock::after {
  content: ''; position: absolute; top: 0; bottom: 0;
  width: var(--scroll-edge-w);
  pointer-events: none; opacity: 0;
  z-index: var(--z-overlay);
}
/* Left: anchored to the right edge of the frozen pane. */
.pblock::after {
  left: var(--frozen-w);
  background: linear-gradient(to right, var(--scroll-edge), transparent);
}
/* Right: anchored to the card's own edge. */
.pblock::before {
  right: 0;
  background: linear-gradient(to left, var(--scroll-edge), transparent);
}
.pblock { --frozen-w: var(--sticky-w); }

.has-less::after { opacity: 1; }
.has-more::before { opacity: 1; }

.prow--head {
  min-height: 30px; --row-bg: var(--color-surface);
  border-bottom: var(--border-hairline) solid var(--color-border-rule);
  font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-secondary);
}
.prow > .pcell:first-child,
.prow--head > :first-child { padding-left: var(--space-6); }
/*
 * No rule around the master-data headings either. A line that stops after one
 * row claims a structure the table does not have.
 */
.prow--head .pcell--num { padding-right: var(--space-4); }
/*
 * The time axis is centred, in both grids and in every row that spans it. A
 * quarter column is a slot, not a number column: the label names the whole
 * column, and the bar plan beside it has always centred its own. --num keeps
 * meaning «a number in the master data» — Kredit CHF and Soll stay flush right,
 * where their digits line up against each other.
 */
.prow--head .pcell--period { text-align: center; padding-right: 0; }
/*
 * The current period, marked the same way in both grids and in the same colour
 * as the today line in the bar plan: now is a position, not a problem. Not on
 * paper — a printed sheet carries its date in the letterhead, and a
 * highlighted column there reads as emphasis on the data.
 */
.prow--head .pcell--num.is-today,
.gantt__quarters .is-today {
  background: var(--color-surface-sunken); color: var(--color-text-strong);
  box-shadow: inset 0 2px 0 var(--color-brand);
}

/*
 * Groups are built exactly like the Termine tab: a label on the page ground,
 * the rows in a card below it. The header sticks left so a horizontal scroll
 * never takes the label with it.
 */
.pgroup + .pgroup { margin-top: var(--space-13); }   /* matches .gantt's gap */
/* The whole label is the toggle — a wide, forgiving target. */
.pgrouphead { margin: 0; padding-bottom: var(--space-6); font-weight: inherit; }
.pgrouphead__toggle {
  display: flex; align-items: center; gap: var(--space-5);
  width: 100%; min-height: var(--control-height);
  border: 0; padding: 0 var(--space-4); border-radius: var(--radius);
  background: none; cursor: pointer; text-align: left;
}
.pgrouphead__toggle:hover { background: var(--color-surface-hover); }
.pgrouphead__name {
  min-width: 0; margin: 0;
  font-size: var(--text-tab); font-weight: var(--font-semibold); color: var(--color-text-strong);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.caret { display: inline-flex; color: var(--color-text-secondary); transition: transform var(--dur-fast) var(--ease); }
.caret.is-collapsed { transform: rotate(-90deg); }

/* Placeholders take the muted tier; the browser default is a grey of its own
   choosing, lighter than anything else on the page. */
::placeholder { color: var(--color-text-muted); opacity: 1; }

/* Cells -------------------------------------------------------------------- */

.pcell { display: flex; align-items: center; min-width: 0; font-size: var(--text-base); }
/* Air on both sides of a text column, heading and cell alike: a name that
   started on the column edge sat against the ring when the cell was being
   edited, and against the hairline of the column before it. */
.pcell--text, .pcell--title, .pcell--lead, .pcell--phase {
  padding-inline: var(--space-4);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block;
  line-height: var(--row-height);
}
.pcell--id { padding-right: var(--space-4); color: var(--color-text-muted); font-size: var(--text-sm); }
.pcell--title { padding-right: var(--space-4); }
.prow__title {
  display: block; width: 100%; border: 0; padding: 0; background: none; text-align: left;
  /* The darker ink already makes the name the row's anchor; the weight on top
     of it only added mass to 111 rows. Its neighbours stay secondary. */
  color: var(--color-text-strong); font-size: var(--text-base);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;
}
.prow__title:hover { color: var(--color-text-link); text-decoration: underline; text-underline-offset: 2px; }
.pcell--phase { color: var(--color-text-secondary); }
.pcell--lead { color: var(--color-text); }
.pcell--lead.is-none { color: var(--color-text-muted); }
.leadbtn {
  display: block; width: 100%; min-height: var(--target-min);
  border: 0; padding: 0; background: none; text-align: left;
  color: inherit; font: inherit; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Editable at rest looks like any other value; the pointer and the hover say
   it is a control, which is as much as the reader asked to be told. */
.leadbtn:hover { color: var(--color-text-link); text-decoration: underline solid; text-underline-offset: 3px; }
/* Block-level, so it is exactly one line box tall and no row grows for it. */
.lead-open {
  display: flex; align-items: center; gap: var(--space-3);
  font-style: italic;
}
.lead-open::before {
  content: ""; flex: 0 0 auto; width: 6px; height: 6px;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-warn-solid);
}
/* The same right padding every other column has, so a figure sits at the same
   distance from its column edge in both grids. */
.pcell--credit {
  padding-right: var(--space-4);
  color: var(--color-text-secondary);
}
/* The person table's figures — Anst., Proj., Spitze — the same way: they had
   no padding at all, and ran to the column edge 8px past their headings. */
.pcell--target { padding-right: var(--space-4); }

/*
 * No rule between quarters. Every value sits in its own column, right-aligned
 * under its own heading, so there is nothing for a line to help the eye find —
 * and twelve hairlines lay a grid over the heat fill, which is what this table
 * is actually saying. Equal values now read as one band, which is the point.
 *
 * The bar plan is the other case: a bar spans columns, so reading where it ends
 * means projecting down from the heading. There the rules stay.
 */
/*
 * The fill spans the row, not a fixed 38px inside it. A row is 38px unless
 * one of its frozen cells is taller — the «nicht zugewiesen» marker made a
 * few 40px — and a 38px cell centred in a 40px row left a white hairline
 * above and below the colour on exactly those rows, which read as a rule
 * nobody had drawn.
 */
.pcell--val {
  justify-content: center; gap: 3px;
  align-self: stretch; min-height: var(--row-height); padding: 0 var(--space-3);
  border: 0;
  font-weight: var(--font-regular); cursor: default;
}
/*
 * The one vertical line worth drawing here. One border, not two: cells sit edge
 * to edge, so a left and a right rule at the same boundary render as 2px — and
 * with different colours, as a 1px dark line beside a 1px light one.
 */
.pcell--val.is-yearstart, .pcell--sum.is-yearstart, .pcell--foot.is-yearstart,
.pcell--load.is-yearstart, .prow--head .is-yearstart {
  border-left: var(--border-hairline) solid var(--color-border);
}
/* Scoped to the pensum grid, not to .pcell--val: the person table on the
   dashboard draws the same cell and is read-only. */
.pgrid--editable .pcell--val:hover { filter: brightness(.93); }

.pcell--val.is-editing { position: relative; z-index: var(--z-raised); }
.pcell--val.is-edited { box-shadow: inset 0 -3px 0 var(--color-accent); }

button.pcell--val { cursor: pointer; }

.heat-null { background: var(--heat-null-bg); color: var(--heat-null-fg); }
.heat-neg  { background: var(--heat-neg-bg);  color: var(--heat-neg-fg); }
.heat-0    { background: var(--heat-0-bg);    color: var(--heat-0-fg); }
.heat-1    { background: var(--heat-1-bg);    color: var(--heat-1-fg); }
.heat-2    { background: var(--heat-2-bg);    color: var(--heat-2-fg); }
.heat-3    { background: var(--heat-3-bg);    color: var(--heat-3-fg); }
.heat-4    { background: var(--heat-4-bg);    color: var(--heat-4-fg); }


.ampel {
  display: inline-block; width: var(--dot-size); height: var(--dot-size);
  border-radius: var(--radius-pill); border: var(--border-control) solid;
}
/*
 * Shape carries the four states, not hue: disc / ring / diamond / outline.
 * Fill alone put "ok" and "knapp" 1.03:1 apart in greyscale.
 */
.ampel--ok    { background: var(--ampel-ok-bg); border-color: var(--ampel-ok-border); }
.ampel--tight {
  background: var(--ampel-tight-bg); border-color: var(--ampel-tight-border);
  border-width: 3px;
}
.ampel--over  {
  background: var(--ampel-over-bg); border-color: var(--ampel-over-border);
  border-radius: var(--radius-xs); transform: rotate(45deg);
}
.ampel--none  { background: var(--color-surface); border-color: var(--color-text-muted); }

/* Footer rows -------------------------------------------------------------- */

.prow--sum, .prow--foot, .prow--load { --row-bg: var(--color-surface-frame); }
.prow--sum {
  min-height: var(--row-height-sum);
  border-top: var(--border-hairline) solid var(--color-border-rule);
  font-size: var(--text-base); font-weight: var(--font-semibold); color: var(--color-text-strong);
}
.prow--foot { min-height: 30px; font-size: var(--text-sm); color: var(--color-text-secondary); }
/*
 * A group sum closes its own card, so it must not read as loudly as the total
 * that closes the table. Same structure, one step quieter, and no rule above
 * it — the row is inside the card, not a footer under it.
 */
.prow--groupsum {
  min-height: var(--row-height);
  border-top: var(--border-hairline) solid var(--color-border);
  font-weight: var(--font-semibold); color: var(--color-text-secondary);
}
.prow__sumlabel, .prow__footlabel {
  display: flex; align-items: center; gap: var(--space-5); padding-left: var(--space-6);
}
.prow__sumlabel .linkbtn { font-size: var(--text-sm); }
/* Full row height, or the year rule reads as a tick with air above and below. */
.pcell--sum, .pcell--foot, .pcell--load { align-self: stretch; }
.pcell--sum, .pcell--foot { justify-content: center; padding-inline: var(--space-3); }

/*
 * 700 is reserved for over-capacity, which marks itself with a triangle rather
 * than with weight or hue; nothing else in this table may claim the weight.
 * The frame fill and the rule above carry this row's rank instead.
 */
.prow--load {
  min-height: var(--row-height-load); align-items: stretch;
  border-top: var(--border-hairline) solid var(--color-border);
  font-size: var(--text-base); font-weight: var(--font-semibold);
}
.prow--load .prow__sumlabel { display: flex; align-items: center; }
.pcell--load {
  align-items: center; justify-content: center;
  padding-inline: var(--space-3);
  background: var(--color-neutral-bg); color: var(--color-neutral-fg);
}
.pcell--load.is-danger  { background: var(--color-danger-bg); color: var(--color-danger-fg); }
.pcell--load.is-warn    { background: var(--color-warn-bg);   color: var(--color-warn-fg); }
.pcell--load.is-ok      { background: var(--color-ok-bg);     color: var(--color-ok-fg); }
.pcell--load.is-neutral { background: var(--color-neutral-bg); color: var(--color-neutral-fg); }

/* Column headers that sort. The arrow only shows on the active column. */
/*
 * No gap by default: the direction glyph is always in the markup but empty on
 * every unsorted column, and a flex gap is charged for an empty child — every
 * right-aligned heading sat 4px left of the numbers it labels.
 */
.sorthead {
  display: flex; align-items: center;
  width: 100%; height: 100%; min-width: 0;
  border: 0; padding: 0; background: none; cursor: pointer;
  font: inherit; color: inherit; text-align: inherit;
}
.pcell--num .sorthead { justify-content: flex-end; }
.pcell--period .sorthead { justify-content: center; }
.sorthead__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sorthead__dir { flex: 0 0 auto; color: var(--color-accent); font-size: var(--text-xs); }
.sorthead__dir:not(:empty) { margin-left: var(--space-2); }
.sorthead:hover .sorthead__label { color: var(--color-text-strong); text-decoration: underline; text-underline-offset: 3px; }
.prow--head .is-sorted { color: var(--color-text-strong); }

/* One legend, shared by the screen and the print sheet. */
/* A view with nothing in it explains itself and offers the way back. */
.empty__title { margin: 0; font-size: var(--text-sub); font-weight: var(--font-semibold); color: var(--color-text-strong); }
.empty__text { margin: 0; font-size: var(--text-base); color: var(--color-text-muted); }
.empty .btn { margin-top: var(--space-3); }

/*
 * Every row names what it encodes. A wall of symbols under one «Legende» left
 * the blue steps unexplained — the label is the useful word, so it takes the
 * heading's place instead of following it.
 */
.legend {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-5) var(--space-10);
  margin: 0; padding: var(--space-5) var(--space-7);
  border: var(--border-hairline) solid var(--color-border); border-radius: var(--radius-lg);
  font-size: var(--text-sm); color: var(--color-text-muted);
}
/* Each label stays with the marks it names, whatever the row wraps to. */
.legend__group { display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--space-5); }
.legend dt { font-weight: var(--font-semibold); color: var(--color-text-secondary); }
.legend dd { display: inline-flex; align-items: center; gap: var(--space-6); flex-wrap: wrap; margin: 0; }
.legend__item { display: inline-flex; align-items: center; gap: var(--space-3); }
.legend__swatch {
  width: 11px; height: 11px; flex: 0 0 auto;
  border: var(--border-hairline) solid var(--color-border-rule);
}
.legend__swatch.is-nolead { background: var(--pattern-no-lead); border-color: var(--color-warn-solid); }
.legend__swatch.is-delay { background: var(--pattern-delay); border-color: var(--color-danger-solid); }
.legend .diamond, .legend .ampel { flex: 0 0 auto; }

/* ==========================================================================
   8 · Edit popover
   ========================================================================== */

.pop {
  position: fixed; z-index: var(--z-modal); width: var(--pop-width);
  padding: var(--space-7);
  border: var(--border-hairline) solid var(--color-border-emphasis);
  border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-modal);
}
.pop__kicker { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-muted); }
.pop__who { margin-top: var(--space-2); font-size: var(--text-btn); font-weight: var(--font-semibold); color: var(--color-text-strong); }
.pop__what { font-size: var(--text-sm); color: var(--color-text-secondary); line-height: var(--leading-compact); }

.pop__stepper { display: flex; align-items: center; gap: var(--space-4); margin-top: var(--space-6); }
.pop__step {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-height-xl); height: var(--control-height-xl); flex: 0 0 auto;
  border: var(--border-hairline) solid var(--color-border-strong); border-radius: var(--radius);
  background: var(--color-surface); color: var(--color-text-strong); cursor: pointer;
}
.pop__step:hover { background: var(--color-surface-hover); }
.pop__input {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2); flex: 1;
  height: var(--control-height-xl);
  border: var(--border-emphasis) solid var(--color-accent); border-radius: var(--radius);
}
.pop__input input {
  width: 4.5ch; border: 0; background: none; outline: none; text-align: right;
  font-size: var(--text-lg); font-weight: var(--font-bold); color: var(--color-text-strong);
  font-variant-numeric: tabular-nums;
  -moz-appearance: textfield;
}
.pop__input input::-webkit-outer-spin-button,
.pop__input input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.pop__unit { font-size: var(--text-btn); font-weight: var(--font-semibold); color: var(--color-text-secondary); }

.pop__warn {
  margin-top: var(--space-5); padding: var(--space-4) var(--space-5);
  border: var(--border-hairline) solid; border-radius: var(--radius);
  font-size: var(--text-sm); line-height: var(--leading-compact); font-weight: var(--font-semibold);
}
.pop__warn.is-none { background: var(--color-warn-bg-subtle);   border-color: var(--color-warn-border);          color: var(--color-warn-fg); }

.pop__reason { display: block; margin-top: var(--space-5); }
.pop__reasonlabel { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-secondary); }
.pop__reasonlabel span { font-weight: var(--font-regular); color: var(--color-text-muted); }
.pop__reason textarea {
  display: block; width: 100%; margin-top: var(--space-2); padding: var(--space-3) var(--space-4);
  border: var(--border-hairline) solid var(--color-border-strong); border-radius: var(--radius);
  background: var(--color-surface); font-size: var(--text-sm); line-height: var(--leading-compact);
  resize: vertical;
}
.pop__rebook { margin-top: var(--space-5); }
.pop__rebook .linkbtn { font-size: var(--text-sm); }
.pop__actions { display: flex; gap: var(--space-4); margin-top: var(--space-6); }
.pop__actions .btn { height: var(--control-height-xl); }
.pop__actions .btn--primary { flex: 1; justify-content: center; }

/* ==========================================================================
   9 · Modal
   ========================================================================== */

.scrim {
  position: fixed; inset: 0; z-index: var(--z-scrim);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 6vh var(--space-8);
  background: var(--color-scrim); overflow-y: auto;
}
.modal {
  width: min(var(--modal-width), 100%);
  border-radius: var(--radius-lg); background: var(--color-surface);
  box-shadow: var(--shadow-modal); overflow: hidden;
}
.modal__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-8);
  padding: var(--space-11) var(--space-11) var(--space-8);
  border-bottom: var(--border-hairline) solid var(--color-border);
}
.modal__kicker { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-muted); }
.modal__title {
  margin-top: var(--space-4); font-size: var(--text-xl); font-weight: var(--font-semibold);
  color: var(--color-text-strong);
}
.modal__close {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: var(--control-height); height: var(--control-height);
  border: var(--border-hairline) solid var(--color-border-strong); border-radius: var(--radius);
  background: var(--color-surface); color: var(--color-text-secondary); cursor: pointer;
}
.modal__close:hover { background: var(--color-surface-hover); }
.modal__lead { padding: var(--space-8) var(--space-11) 0; color: var(--color-text-secondary); }

.modal__section { padding: var(--space-8) var(--space-11); border-bottom: var(--border-hairline) solid var(--color-border); }
.modal__section h3 {
  margin-bottom: var(--space-5);
  font-size: var(--text-base); font-weight: var(--font-semibold); color: var(--color-text-strong);
}

.minigrid { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: var(--space-2); }
.minigrid__col { display: flex; flex-direction: column; gap: var(--space-2); text-align: center; }
.minigrid__q { font-size: var(--text-xs); font-weight: var(--font-semibold); color: var(--color-text-muted); }
/* Live text, so it clears AA; --color-text-subtle is 2.54:1 and belongs to
   disabled items, which the contrast rules exempt. */
.minigrid__q span { color: var(--color-text-muted); font-weight: var(--font-regular); }
.minigrid__v {
  display: flex; align-items: center; justify-content: center; height: var(--control-height-lg);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: var(--text-base); font-weight: var(--font-semibold);
}

.modal__log li {
  display: grid; grid-template-columns: 100px 120px minmax(0, 1fr) 130px;
  gap: var(--space-5); padding: var(--space-3) 0;
  border-top: var(--border-hairline) solid var(--color-border-subtle);
  font-size: var(--text-sm); color: var(--color-text-secondary);
}
.modal__log li:first-child { border-top: 0; }

.modal__foot {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--space-4);
  padding: var(--space-8) var(--space-11);
  background: var(--color-surface-frame);
}

/* Project modal — the five facts the wireframe fixes in order. */
.facts { padding: var(--space-4) var(--space-11) var(--space-8); }
.facts__row {
  display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: var(--space-8);
  align-items: baseline; padding: var(--space-5) 0;
  border-bottom: var(--border-hairline) solid var(--color-border-subtle);
}
.facts__row:last-child { border-bottom: 0; }
.facts dt { font-size: var(--text-base); color: var(--color-text-muted); }
.facts dd {
  font-size: var(--text-btn); font-weight: var(--font-semibold); color: var(--color-text-strong);
}
.facts dd.is-danger { color: var(--color-danger-fg); }
.facts dd.is-warn { color: var(--color-warn-fg-alt); }
.facts__sub {
  display: block; margin-top: var(--space-1);
  font-size: var(--text-sm); font-weight: var(--font-regular); color: var(--color-text-muted);
}

/* Rebooking — von / Pensum / Dauer, then the person picker, then the reason. */
.rebook {
  display: grid; grid-template-columns: minmax(0, 1fr) 130px 150px;
  gap: var(--space-8); padding: var(--space-8) var(--space-11) var(--space-6);
}
.rebook__field { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.rebook__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-muted); }
.rebook__readonly {
  display: flex; align-items: center; height: var(--control-height-xl); padding: 0 var(--space-5);
  border: var(--border-hairline) solid var(--color-border); border-radius: var(--radius);
  background: var(--color-surface-frame);
  font-size: var(--text-btn); font-weight: var(--font-semibold); color: var(--color-text-strong);
}
.rebook__amount { display: flex; align-items: center; gap: var(--space-4); font-size: var(--text-base); color: var(--color-text-secondary); }
.rebook__amount input {
  width: 72px; height: var(--control-height-xl); padding: 0 var(--space-4);
  border: var(--border-hairline) solid var(--color-border-strong); border-radius: var(--radius);
  text-align: right; font-variant-numeric: tabular-nums;
}

.rebook__to { display: flex; flex-direction: column; gap: var(--space-3); padding: 0 var(--space-11) var(--space-6); }
.rebook__list {
  max-height: 176px; overflow-y: auto;
  border: var(--border-hairline) solid var(--color-border); border-radius: var(--radius);
}
/* Two columns, name and unit, at 60/40: the units line up down the list and
   read as a column of their own, and none of them sits at the far edge, which
   is where figures go. */
.rebook__list li {
  display: grid; grid-template-columns: 3fr 2fr; align-items: center; column-gap: var(--space-5);
  min-height: var(--control-height-xl); padding: var(--space-3) var(--space-6);
  border-bottom: var(--border-hairline) solid var(--color-border-subtle);
  font-size: var(--text-base); cursor: pointer;
}
.rebook__list li:last-child { border-bottom: 0; }
.rebook__list li:hover { background: var(--color-surface-hover); }
.rebook__list li.is-on {
  background: var(--color-surface-selected); color: var(--color-text-strong); font-weight: var(--font-semibold);
}
.rebook__role { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text-muted); font-size: var(--text-sm); font-weight: var(--font-regular); }
.rebook__empty { color: var(--color-text-muted); cursor: default; }
.rebook__reason { padding: 0 var(--space-11) var(--space-8); }

.share { display: flex; gap: var(--space-4); padding: var(--space-8) var(--space-11) var(--space-11); }
.share__field { flex: 1; min-width: 0; }
.share__field input {
  width: 100%; height: var(--control-height-xl); padding: 0 var(--space-6);
  border: var(--border-hairline) solid var(--color-border-strong); border-radius: var(--radius);
  background: var(--color-surface-frame); color: var(--color-text-secondary);
  font-family: var(--font-mono); font-size: var(--text-sm);
}
.share .btn { height: var(--control-height-xl); flex: 0 0 auto; }

.modal__meta-line { margin-top: var(--space-2); font-size: var(--text-base); color: var(--color-text-muted); }

/*
 * Editing is a mode, and the wireframe wants that said out loud. It sits dead
 * centre of the bar rather than after the crumbs, so it does not move when the
 * path gets longer. Below the layout width it goes back into the flow.
 */

/* ==========================================================================
   10 · Gantt
   ========================================================================== */

.gantt {
  /* --gantt-lead comes from the view: it is the width of whichever columns the
     Attribute menu has switched on, and the overlay and the min-width follow it. */
  display: flex; flex-direction: column; gap: var(--space-13);
}

/*
 * Only scroll when the quarters genuinely no longer fit — and put the minimum
 * on what the scroller contains, not on the scroller itself. On the scroller it
 * simply grew past the card, whose overflow:hidden then ate 96px with no
 * scrollbar. .capband__row already does it the right way.
 */
/*
 * The time axis is the same width in both tabs, so a quarter reads the same
 * whichever way you are looking at it — and the bar plan scrolls in the same
 * situations the pensum grid does.
 */

/*
 * The same two lead columns the pensum grid uses, at the same widths, so a
 * project sits in the same place whichever tab you are reading.
 */
/* The template itself is set per render, from the column registry. */
.gantt__axis,
.gantt__row { display: grid; }
.gantt__axis {
  min-height: var(--row-height);
  background: var(--color-surface);
  border-bottom: var(--border-hairline) solid var(--color-border-rule);
}
/*
 * The same padding as the pensum grid, because the two read the same width
 * tokens: at 12px on both sides a 152px column lost 8px and «33 Bewilligungs-
 * verfahren» truncated in one tab while fitting in the other.
 */
.gantt__axislabel {
  display: flex; align-items: center; padding-right: var(--space-4);
  font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-secondary);
}

/*
 * A project keeps its name while its bars scroll under it. Without this the
 * axis moved and the rows the reader was following moved with it. The offset is
 * inline, because which columns precede this one is a question of the menu.
 */
.gantt__axislabel, .gantt__rowlabel {
  position: sticky; z-index: var(--z-frozen);
  /* Full row height, or the bars and the today marker show through the gaps
     between rows — the same reason .is-frozen stretches in the pensum grid. */
  align-self: stretch;
  background: var(--color-surface);
}

.gantt__quarters {
  display: grid; grid-template-columns: repeat(var(--gantt-cols, 8), minmax(0, 1fr));
  align-items: center; text-align: center;
  font-size: var(--text-sm); font-weight: var(--font-semibold);
  color: var(--color-text-secondary);
}
/*
 * A heading is a row of labels in both grids, so both are ruled the same way:
 * years only. The bodies differ because their content does — a bar spans
 * columns and needs the reference, a value sits inside its own.
 */
.gantt__quarters > * {
  align-self: stretch; display: flex; align-items: center; justify-content: center;
}
.gantt__quarters .is-yearstart { border-left: var(--border-hairline) solid var(--color-border); }

/*
 * Paper has no "today": the sheet carries its date in the letterhead, and a
 * highlighted column there reads as emphasis on the data instead.
 */
.sheet .prow--head .pcell--num.is-today,
.sheet .gantt__quarters .is-today {
  background: none; font-weight: inherit; box-shadow: none;
}

/*
 * The today marker belongs to the time axis, so it is clipped to it. Left in
 * the row it scrolled underneath the frozen column and showed through the
 * hairline between rows.
 */
/*
 * Centred on the axis/body boundary rather than sitting below it, so the marker
 * costs no vertical space of its own.
 */

.gantt__row { align-items: center; min-height: var(--row-height); }
/*
 * The separator sits on the cells, not on the row: the frozen label is opaque
 * across the full row height, and a border on the row would leave a hairline
 * it cannot cover — through which the today marker showed while scrolling.
 */
.gantt__row > * {
  align-self: stretch;
  border-bottom: var(--border-hairline) solid var(--color-border-subtle);
}
.gantt__row:last-child > * { border-bottom: 0; }
.gantt__row.is-delayed { box-shadow: var(--rail-danger); }
.gantt__row.is-unassigned { box-shadow: var(--rail-warn); }

.gantt__rowlabel {
  display: flex; align-items: center; min-width: 0;
  padding-right: var(--space-4);
}
/* The block is inset from the card edge, exactly as the pensum grid's is. */
.gantt__axis > *:first-child,
.gantt__row > *:first-child { padding-left: var(--space-6); }
.gantt__rowtitle {
  display: block; min-width: 0; flex: 1; min-height: var(--target-min);
  border: 0; padding: 0; background: none; text-align: left;
  font-size: var(--text-base); color: var(--color-text-strong);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
}
.gantt__rowtitle:hover { color: var(--color-text-link); text-decoration: underline; text-underline-offset: 2px; }
.gantt__row .gantt__col--id {
  font-size: var(--text-sm); color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.gantt__row .gantt__col--phase,
.gantt__row .gantt__col--lead,
.gantt__row .gantt__col--portfolio,
.gantt__row .gantt__col--organisation,
.gantt__row .gantt__col--credit {
  font-size: var(--text-base); color: var(--color-text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block;
  line-height: var(--row-height);
}

.gantt__track {
  display: grid; grid-template-columns: repeat(var(--gantt-cols, 8), minmax(0, 1fr));
  grid-template-rows: calc(var(--row-height) - 2px); align-items: center;
  /* The bars are placed on the track itself, not in its columns. */
  position: relative;
}
/*
 * Years only, as in the pensum grid — heading and cells alike, in both tabs.
 * The gridline element stays: it is what the year rule hangs on.
 */
.gantt__gridline { grid-row: 1; height: 100%; }
.gantt__gridline.is-yearstart { border-left: var(--border-hairline) solid var(--color-border); }

.gantt__bar {
  /*
   * Positioned to the quarter rather than snapped to a column, so a phase that
   * ends mid-year ends mid-column and the next one starts where it left off.
   */
  position: absolute; top: 50%; transform: translateY(-50%); z-index: var(--z-raised);
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  height: var(--bar-height); padding: 0 var(--space-4);
  border: var(--border-hairline) solid var(--color-bar-border);
  background: var(--color-bar-bg);
  color: var(--color-text-body);
  /* Regular weight: a hundred bold labels across the plan read as clutter,
     and the bar itself already carries the phase. */
  font-size: var(--text-xs);
  border-left: 0; min-width: 0;
}
.gantt__bar { cursor: pointer; text-align: left; transition: filter var(--dur-fast) var(--ease); }
.gantt__bar:hover { filter: brightness(.95); }
.gantt__bar:active { filter: brightness(.92); }
.gantt__bar:focus-visible { outline-offset: -3px; }

.gantt__bar.is-first { border-left: var(--border-hairline) solid var(--color-bar-border); border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.gantt__bar.is-last { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.gantt__bar.is-first.is-last { border-radius: var(--radius-sm); }
.gantt__barlabel { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.gantt__bar.is-delay {
  background: var(--pattern-delay); border-color: var(--color-danger-solid);
  color: var(--color-danger-fg);
}
.gantt__bar.is-unassigned {
  background: var(--pattern-no-lead); border-color: var(--color-warn-solid);
  border-left: var(--border-hairline) solid var(--color-warn-solid);
}
/*
 * A gate is a control, so it answers the pointer. The hit area is larger than
 * the mark — an 8px diamond is not a target.
 */
.gantt__gate {
  /*
   * Centred on the moment it is due, like the bars are drawn to theirs. Placed
   * by column it snapped to that column's right edge, which at year scale threw
   * a gate up to three quarters away from its date.
   */
  position: absolute; top: 50%; z-index: var(--z-label);
  transform: translate(calc(-50% - var(--gate-shift, 0px)), -50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--gate-size); height: var(--gate-size);
  border: 0; padding: 0; background: none; cursor: pointer;
}
.gantt__gate .diamond { transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease); }
.gantt__gate:hover .diamond { transform: rotate(45deg) scale(1.25); }
.gantt__gate:active .diamond { transform: rotate(45deg) scale(1.1); }
.gantt__gate:focus-visible { outline-offset: -3px; }

.gantt__more { display: inline-flex; color: var(--color-text-muted); }

.gantt__rail {
  position: absolute; top: 50%; z-index: var(--z-base);
  height: 1px; border-top: 2px dashed var(--color-border-rule);
}
.gantt__railcaption {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: var(--z-raised);
  display: flex; align-items: center; justify-content: center;
}
.gantt__railcaption span {
  padding: 0 var(--space-4); background: var(--color-surface);
  font-size: var(--text-xs); color: var(--color-text-muted);
}

.diamond {
  flex: 0 0 auto; width: 11px; height: 11px;
  background: var(--color-milestone); transform: rotate(45deg);
}


/* ==========================================================================
   11 · Dashboard
   ========================================================================== */

/*
 * Two columns. The quarter chart takes the row; every other card is one tile,
 * so the cards pair up and each pair shares a height — a mosaic rather than a
 * stagger. Stretching, not starting: a short list next to a long one keeps
 * the same bottom edge.
 */
.bi-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-10); }
.bi-card {
  display: flex; flex-direction: column; gap: var(--space-7);
  padding: var(--space-10);
  border-radius: var(--radius-lg); background: var(--color-surface);
  box-shadow: var(--shadow-raised);
}
.bi-card--full { grid-column: 1 / -1; }
.bi-card__head { display: flex; align-items: flex-start; gap: var(--space-6); }
.bi-card__head > div:first-child { flex: 1; min-width: 0; }
.bi-card__title { font-size: var(--text-sub); font-weight: var(--font-semibold); color: var(--color-text-strong); }
.bi-card__sub { margin-top: var(--space-1); font-size: var(--text-sm); color: var(--color-text-muted); }
.bi-card__tools { margin-left: auto; }
.bi-card__section { padding-top: var(--space-8); border-top: var(--border-hairline) solid var(--color-border-subtle); }
.bi-card__subtitle { font-size: var(--text-base); font-weight: var(--font-semibold); color: var(--color-text-strong); }
/* The two dashboard sections: a control row of its own, under the KPI strip. */
.bibar { display: flex; align-items: center; gap: var(--space-6); }

/* The person table's summary label carries a second line of context. */
/*
 * The note says which population the row counts. Set apart rather than merely
 * appended, because «Bedarf total · Auswahl» sitting above «Auslastung ·
 * Gesamtportfolio» read as one running total.
 */
.prow__sumnote {
  margin-left: var(--space-5); padding-left: var(--space-5);
  border-left: var(--border-hairline) solid var(--color-border);
  font-weight: var(--font-regular); color: var(--color-text-muted); font-size: var(--text-sm);
}

.colchart { display: flex; flex-direction: column; }
/*
 * Wide enough for every column to keep its floor, and scrolled when the card is
 * narrower than that. Left to fill the card, forty quarters shared it out and
 * the labels collided; a chart that runs past its card is the same bargain the
 * grids make, and the reader already knows how to pan one.
 */
.colchart__scroll {
  display: flex; flex-direction: column; gap: var(--space-4);
  overflow-x: auto; overscroll-behavior-x: contain;
}
.colchart__plot, .colchart__axis {
  min-width: calc(var(--chart-cols, 8) * var(--chart-col)
    + (var(--chart-cols, 8) - 1) * var(--space-4));
}
.colchart__plot { position: relative; height: var(--chart-h, 150px); }
.colchart__ref { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--color-neutral-solid); }
/*
 * The label of the reference line sits at the right edge of what is on
 * screen, not of the track: the track is twice the card, and a label at its
 * end was seen only by whoever scrolled to 2036. A row as wide as the track
 * holds the line's height; inside it the label is sticky to the scroller's
 * right edge, so it stays put while the columns pass under it.
 */
.colchart__refrow {
  position: absolute; left: 0; right: 0;
  display: flex; justify-content: flex-end;
  pointer-events: none;
}
.colchart__reflabel {
  position: sticky; right: 0;
  padding: 0 var(--space-2);
  background: var(--color-surface);
  font-size: var(--text-xs); color: var(--color-text-muted); white-space: nowrap;
}
.colchart__grid { display: grid; grid-auto-columns: minmax(var(--chart-col), 1fr); grid-auto-flow: column; gap: var(--space-4); height: 100%; align-items: end; }
.colchart__col { display: flex; flex-direction: column; justify-content: flex-end; gap: var(--space-2); height: 100%; }
.colchart__val { font-size: var(--text-xs); font-weight: var(--font-semibold); text-align: center; }
.colchart__bar { border-radius: var(--radius-sm) var(--radius-sm) 0 0; border: 1px solid; }
.colchart__axis { display: grid; grid-auto-columns: minmax(var(--chart-col), 1fr); grid-auto-flow: column; gap: var(--space-4); font-size: var(--text-xs); color: var(--color-text-secondary); text-align: center; }
/* A value belongs on one line; the column is sized so that it fits. */
.colchart__val { white-space: nowrap; }

.colchart__col.is-overload .colchart__val { color: var(--color-danger-fg); }
/*
 * Height is the measurement. In greyscale four of the five fills collapsed into
 * 7.7 luma, so only the state that needs an alarm keeps a colour.
 */
.colchart__col.is-overload .colchart__bar { background: var(--color-danger-border); border-color: var(--color-danger-solid); }
.colchart__col.is-deficit .colchart__val { color: var(--color-danger-fg); }
.colchart__col.is-deficit .colchart__bar { background: var(--color-danger-bg); border-color: var(--color-danger-solid); }
.colchart__col.is-tight .colchart__val { color: var(--color-warn-fg); }
.colchart__col.is-tight .colchart__bar { background: var(--color-bar-bg); border-color: var(--color-bar-border); }
.colchart__col.is-ok .colchart__val { color: var(--color-ok-fg); }
.colchart__col.is-ok .colchart__bar { background: var(--color-bar-bg); border-color: var(--color-bar-border); }
.colchart__col.is-free .colchart__val { color: var(--color-text-secondary); }
.colchart__col.is-free .colchart__bar { background: var(--color-bar-bg); border-color: var(--color-bar-border); }

.colchart__panel + .colchart__panel { margin-top: var(--space-8); }
/* Sticks to the left edge, so a heading inside the scroller stays put while
   the columns scroll under it. */
.colchart__panelhead { position: sticky; left: 0; width: max-content; margin-bottom: var(--space-4); }

.barlist { display: flex; flex-direction: column; }
.barlist__row { display: flex; flex-direction: column; gap: var(--space-2); }
.barlist__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-5); font-size: var(--text-base); }
.barlist__label { color: var(--color-text); min-width: 0; }
.barlist__value { flex: 0 0 auto; font-weight: var(--font-semibold); color: var(--color-text-strong); }
.barlist__track { height: 10px; border-radius: var(--radius-sm); background: var(--color-surface-sunken); overflow: hidden; }
.barlist__fill { display: block; height: 100%; background: var(--color-accent); }
.barlist__fill.is-danger { background: var(--color-danger-solid); }

/* ==========================================================================
   12 · Change log
   ========================================================================== */

.table-card {
  border-radius: var(--radius-lg); background: var(--color-surface); overflow: hidden;
  box-shadow: var(--shadow-raised);
}

.log {
  display: grid; align-items: center; gap: var(--space-6);
  min-height: var(--row-height-list); padding: var(--space-4) var(--space-8);
  border-top: var(--border-hairline) solid var(--color-border-subtle);
  font-size: var(--text-base);
}
.log--history {
  grid-template-columns:
    minmax(0, 110px) minmax(0, 150px) minmax(0, 1.4fr) minmax(0, 140px) minmax(0, 1.2fr) minmax(0, 150px);
}
.log--head {
  min-height: 40px; border-top: 0; background: var(--color-surface-frame);
  font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-muted);
}
.log.is-zebra { background: var(--color-surface-alt); }
.log:hover:not(.log--head) { background: var(--color-surface-hover); }
.log--empty { display: block; padding: var(--space-11); text-align: center; color: var(--color-text-muted); }
.log__date { color: var(--color-text-secondary); }
.log__project { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.log__project .linkbtn { font-weight: var(--font-regular); text-align: left; }
.log__change { color: var(--color-text-secondary); }
.log__value { font-weight: var(--font-semibold); color: var(--color-text-strong); }
/* The size menu and the arrows answer the same question, so they sit together. */
.log__pager { display: flex; align-items: center; gap: var(--space-5); }
.log__pageno { font-variant-numeric: tabular-nums; white-space: nowrap; }

.log__foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-8);
  flex-wrap: wrap; padding: var(--space-6) var(--space-8);
  border-top: var(--border-hairline) solid var(--color-border);
  background: var(--color-surface-frame);
  font-size: var(--text-sm); color: var(--color-text-muted);
}

.fieldtag {
  display: inline-flex; align-items: center; height: 22px; padding: 0 var(--space-4);
  border-radius: var(--radius-pill); background: var(--color-surface-sunken);
  font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text-secondary);
}

/* ==========================================================================
   13 · API reference — Swagger UI brings its own styles; this only frames it
   ========================================================================== */

#swagger { margin-inline: calc(-1 * var(--space-10)); }
/* Swagger caps its own wrapper at 1460px and centres it; the shell sets the measure. */
#swagger .swagger-ui .wrapper { max-width: none; }
#swagger .swagger-ui { font-family: var(--font-sans); }
/*
 * Swagger's own scale put a 36px h2 above the page's 24px h1 and ran prose to
 * 163 characters a line. Size and measure only — its colours stay its own.
 */
#swagger .swagger-ui .info .title { font-size: var(--text-xl); font-weight: var(--font-semibold); }
#swagger .swagger-ui .info .title small { vertical-align: middle; }
#swagger .swagger-ui .opblock-tag { font-size: var(--text-sub); font-weight: var(--font-semibold); }
#swagger .swagger-ui .opblock-tag a { display: inline-flex; align-items: center; min-height: var(--target-min); }
#swagger .swagger-ui p { max-width: 68ch; }
#swagger .swagger-ui .topbar { display: none; }

/*
 * Swagger UI keeps its own look — these are contrast repairs only. Its default
 * method badges put white text on light fills (PATCH lands at 1.6:1), and the
 * version pill and licence link are similarly thin. Fills darkened until the
 * white text clears AA; nothing else about the widget is touched.
 */
/* Inline code in the introduction: Swagger sets it violet at 4.5:1 on its own
   grey; the text colour on the sunken surface reads like the rest of the page. */
#swagger .swagger-ui .markdown code, #swagger .swagger-ui .renderedMarkdown code {
  color: var(--color-text-body); background: var(--color-surface-sunken);
}
#swagger .swagger-ui .opblock-get .opblock-summary-method    { background: var(--method-get); }
#swagger .swagger-ui .opblock-post .opblock-summary-method   { background: var(--method-post); }
#swagger .swagger-ui .opblock-patch .opblock-summary-method  { background: var(--method-patch); }
#swagger .swagger-ui .opblock-put .opblock-summary-method    { background: var(--method-put); }
#swagger .swagger-ui .opblock-delete .opblock-summary-method { background: var(--method-delete); }
#swagger .swagger-ui .info .title small pre.version { background: var(--method-neutral); }
#swagger .swagger-ui .info a,
#swagger .swagger-ui .info .base-url { color: var(--color-text-link); }
#swagger .swagger-ui .info a { display: inline-flex; align-items: center; min-height: var(--target-min); }
#swagger .swagger-ui .authorization__btn { min-height: var(--target-min); }
/*
 * Swagger caps its prose at a readable measure with `margin: 14px auto`, which
 * also centres it — so the description floated away from the heading it
 * belongs to. Keep the measure, drop the centring.
 */
#swagger .swagger-ui .info .description p,
#swagger .swagger-ui .opblock-tag small p,
#swagger .swagger-ui .opblock-description-wrapper p { margin-inline: 0 auto; }
/*
 * The introduction is the exception to the measure: it is the one text the
 * reader meets before the operation list, and at 68ch beside an empty right
 * half of the pane it read as a sidebar. It takes the pane.
 */
#swagger .swagger-ui .info .description p { max-width: none; }

#swagger .swagger-ui .btn.authorize { color: var(--method-auth); border-color: var(--method-auth); }
#swagger .swagger-ui .btn.authorize svg { fill: var(--method-auth); }

/* --------------------------------------------------------------------------
 * The prototype notice
 *
 * Built from the same parts as the rest of the shell: the card surface, the
 * hairline, the raised shadow, the info mark the dropped-columns note uses, and
 * a plain button. It is not the page's main action, so it is not primary.
 * -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
 * Column alignment, for both grids
 *
 * Alignment belongs to the column, not to the grid drawing it: declared once in
 * the registry and read by both. The Ampel sat centred in the table and left in
 * the bar plan; the Kredit heading right in one and left in the other. Declared
 * here, after both grids, so it is not overruled by a rule of the same weight.
 * -------------------------------------------------------------------------- */
.align-end, .align-end .sorthead { justify-content: flex-end; text-align: right; }
.align-center, .align-center .sorthead { justify-content: center; text-align: center; }
/* Centred means centred in the cell, so it cannot carry padding on one side. */
.align-center { padding-inline: 0; }
/*
 * The last frozen column meets the time axis, and a figure 8px from that
 * edge read as the first quarter's neighbour rather than the master data's
 * last. A step more air there — for the heading and the cell alike, so the
 * two stay flush with each other.
 */
.prow--head .pcell--num.is-frozen-last,
.pcell.align-end.is-frozen-last { padding-right: var(--space-6); }

/* ==========================================================================
   14 · Print layout
   ========================================================================== */

/* The sheet format sits where the time scale sits: below the filters, left. */
.sheetbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-8); flex-wrap: wrap;
}
/* Which sheet of paper and which way round are one decision, so they sit
   together rather than being spread across the bar. */
.sheetbar__paper { display: flex; align-items: center; gap: var(--space-5); }

.sheet {
  /* Paper is white by definition, so this one stays literal. */
  flex: 0 0 auto; box-sizing: border-box; background: var(--color-paper);
  box-shadow: var(--shadow-sheet);
  display: flex; flex-direction: column;
  color: var(--color-text-strong);
}
/* The mount is a stack of sheets, not one sheet. */
/*
 * safe center keeps the stack centred while there is room and falls back to
 * flex-start once the sheet is wider than the frame — plain center pushes the
 * overflow onto both sides, and scrollLeft cannot go negative to reach it.
 */
.mount {
  display: flex; flex-direction: column; align-items: safe center; gap: var(--space-12);
  overflow-x: auto;
}
/*
 * The preview scale, set per render. On paper it must not apply — the sheet is
 * its format there, and the page box is drawn to match.
 */
@media screen {
  .mount .sheet { zoom: var(--sheet-zoom, 1); }
}
/*
 * The paper, in the units paper is sold in. Stated in millimetres the sheet is
 * exactly its format — a browser converts mm at 96dpi, so @page and the sheet
 * agree to the pixel and a printed A0 measures 841 × 1189 mm on the wall.
 *
 * Each size is the one before it turned once: the long side becomes the short
 * side of the next.
 */
.sheet--a4 { --paper-short: 210mm; --paper-long:  297mm; }
.sheet--a3 { --paper-short: 297mm; --paper-long:  420mm; }
.sheet--a2 { --paper-short: 420mm; --paper-long:  594mm; }
.sheet--a1 { --paper-short: 594mm; --paper-long:  841mm; }
.sheet--a0 { --paper-short: 841mm; --paper-long: 1189mm; }

.sheet--portrait  { width: var(--paper-short); min-height: var(--paper-long); }
.sheet--landscape { width: var(--paper-long);  min-height: var(--paper-short); }
.sheet { padding: 7.4mm 8.5mm; }

/*
 * The bar plan on paper. Same markup as the screen, at paper density: a row is
 * as tall as a row on the pensum sheet, and the lead columns give the time axis
 * as much width as the page can spare.
 */
.sheet__gantt {
  /* The screen container spaces its group cards apart; on paper the rows are
     one continuous table. */
  gap: 0;
  /* A bar leaves air above and below it, in the same proportion as on screen. */
  --bar-height: 18px;
  --grid-col-id: 46px;
  --grid-col-title: 190px;
  --gantt-lead: calc(var(--grid-col-id) + var(--grid-col-title));
  margin-top: 6px;
}
.sheet--landscape .sheet__gantt { --grid-col-title: 240px; }

.sheet__gantt .gantt__row { min-height: 26px; }
.sheet__gantt .gantt__track { grid-template-rows: 24px; }
.sheet__gantt .gantt__axis { min-height: 24px; }
.sheet__gantt .gantt__quarters { font-size: var(--sheet-text); }
.sheet__gantt .gantt__axislabel { font-size: var(--sheet-text); padding-inline: 6px; }
.sheet__gantt .gantt__rowlabel { padding: 0 6px; }
.sheet__gantt .gantt__col--id { font-size: var(--sheet-text); }
.sheet__gantt .gantt__rowtitle { font-size: var(--sheet-text); }
.sheet__gantt .gantt__barlabel { font-size: var(--sheet-text); }
.sheet__gantt .gantt__track { align-items: center; }

/* The plan closes on the same figure the screen shows beneath it. */
.sheet__gantt .gantt__row--load { border-top: 1px solid var(--color-border-rule); margin-top: 4px; }
.sheet__gantt .gantt__track--load {
  display: grid; grid-template-columns: repeat(var(--gantt-cols, 8), minmax(0, 1fr));
}
.sheet__gantt .capband__cell {
  display: flex; align-items: center; justify-content: center;
  font-size: var(--sheet-text); font-weight: var(--font-semibold);
}
/*
 * Not grid rows here: on the bar plan the heading and the continuation note
 * span the whole sheet rather than sitting in the first column.
 */
.sheet__gantt .sheet__row--more { display: block; padding: 4px 6px 0; }

/* Not a grid row here: the bar plan's heading spans the whole sheet. */
.sheet__gantt .sheet__grouphead {
  display: flex; align-items: baseline; gap: 8px;
  padding: 5px 6px; margin-top: var(--space-8);
  background: var(--color-surface-frame);
  font-size: var(--sheet-lead); font-weight: var(--font-semibold);
}
.sheet__gantt .sheet__grouphead:first-child { margin-top: 0; }

.sheet__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
  border-bottom: 2px solid var(--color-text-strong); padding-bottom: 12px;
}
.sheet__sender { display: flex; align-items: flex-start; gap: 12px; flex: 0 0 auto; }
.sheet__sender img { display: block; margin-top: 2px; }
.sheet__sender div { font-size: var(--sheet-lead); line-height: var(--leading-normal); }
.sheet__sender span { display: block; white-space: nowrap; }
.sheet__sender .is-muted { color: var(--color-text-muted); }
.sheet__titles { flex: 1; min-width: 0; text-align: center; }
.sheet__title { font-size: var(--sheet-title); font-weight: var(--font-bold); }
.sheet__sub { margin-top: 3px; font-size: var(--sheet-lead); color: var(--color-text-secondary); }
.sheet__meta {
  display: flex; flex-direction: column; flex: 0 0 auto; text-align: right;
  font-size: var(--sheet-text); line-height: var(--leading-relaxed); color: var(--color-text-secondary);
  white-space: nowrap;
}

.sheet__table { margin-top: 14px; }
.sheet__row {
  display: grid; grid-template-columns: var(--sheet-cols);
  align-items: center; min-height: 22px;
  border-bottom: var(--border-hairline) solid var(--color-border-subtle);
  font-size: var(--text-xs);
}
.sheet__row--head {
  min-height: var(--target-min); border-bottom: 1px solid var(--color-border-rule);
  font-size: var(--sheet-text); font-weight: var(--font-semibold); color: var(--color-text-muted);
}
/*
 * The row centres its children, so a heading cell was only as tall as its own
 * line — 15px of a 24px row — and the year rule beside it stopped nine pixels
 * short of the cells below. The bar plan's axis already stretched; this makes
 * the table's head agree with it.
 */
.sheet__row--head > * { align-self: stretch; display: flex; align-items: center; }
.sheet__row--head > .sheet__num { justify-content: flex-end; }
.sheet__row--head > .sheet__period { justify-content: center; }
.sheet__id { padding-left: 8px; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
/*
 * 162 of 222 titles were clipped with no tooltip and no way back — on paper an
 * ellipsis is permanent. Two lines cost some rows per sheet and lose nothing.
 */
/* Only the title wraps; the rest clip, so no column can spill into the next. */
.sheet__lead { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.sheet__project {
  padding-right: var(--space-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet__muted {
  color: var(--color-text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 6px;
}
.sheet__num { text-align: right; padding-right: 6px; font-variant-numeric: tabular-nums; }
/* Same rule as on screen: the time axis is centred, the master data is not. */
.sheet__period { text-align: center; padding-right: 0; }
/*
 * A year boundary is the strongest line on the sheet, and the same line in both
 * reports — the table drew it in grey 75,85,99 and the bar plan in 206,211,221,
 * so the two disagreed about how much a year matters.
 */
.sheet__cell.is-yearstart,
.sheet__num.is-yearstart,
.sheet__gantt .gantt__quarters .is-yearstart,
.sheet__gantt .gantt__gridline.is-yearstart,
.sheet__gantt .capband__cell.is-yearstart {
  border-left: var(--border-hairline) solid var(--color-text-strong);
}
.sheet__cell {
  height: 26px; display: flex; align-items: center; justify-content: center;
  padding-inline: 4px; border-left: var(--border-hairline) solid var(--color-border-subtle);
  font-variant-numeric: tabular-nums;
}
.sheet__row.is-unassigned { box-shadow: var(--rail-warn); }

/*
 * The bar plan inside a printed pensum row — «Pensum + Termine» on paper.
 * The band is a second grid row under the figures rather than an overlay:
 * on screen it is positioned against the frozen block's width, which a sheet
 * has no equivalent of, and a second row costs the auto-placed cells nothing.
 * 10px is what a two-digit sub-phase number needs at 8px; the row grows from
 * 26 to 36px and the page budget shrinks in that ratio (see FORMATS).
 */
.sheet__row--band { grid-template-rows: 26px 10px; }
.sheet .pband {
  position: relative; inset: auto;
  grid-row: 2; grid-column: var(--band-col) / -1;
  height: 10px;
  --band-h: 10px; --band-lab-size: 8px; --band-lab-pad: 3px;
}
.sheet .pband .gantt__gate { width: 12px; height: 12px; }
.sheet .pband .diamond { width: 8px; height: 8px; }
.sheet__row--more { border-bottom: 0; font-size: var(--sheet-text); color: var(--color-text-muted); font-style: italic; }
.sheet__row--more span { padding-left: 8px; }
.sheet__row--sum, .sheet__row--load {
  border-top: 1px solid var(--color-border-rule); border-bottom: 0;
  font-weight: var(--font-semibold);
}
.sheet__row--foot { border-bottom: 0; color: var(--color-text-secondary); }
.sheet__row--sum > span:first-child,
.sheet__row--foot > span:first-child,
.sheet__row--load > span:first-child { padding-left: 8px; }
.sheet__row--load .is-neutral { color: var(--color-text-secondary); }
.sheet__row--load .is-danger { color: var(--color-danger-fg); }
.sheet__row--load .is-warn { color: var(--color-warn-fg); }
.sheet__row--load .is-ok { color: var(--color-ok-fg); }

/* Grouping from the toolbar, printed: a heading before, a sum after. */
.sheet__row--group {
  margin-top: var(--space-8);
  background: var(--color-surface-frame);
  font-weight: var(--font-bold); color: var(--color-text-strong);
  border-top: var(--border-hairline) solid var(--color-border-rule);
}
.sheet__row--head + .sheet__row--group { margin-top: 0; }
.sheet__groupcount {
  margin-left: var(--space-5);
  font-weight: var(--font-regular); color: var(--color-text-muted);
}
.sheet__row--groupsum {
  font-weight: var(--font-semibold); color: var(--color-text-strong);
  border-bottom: var(--border-hairline) solid var(--color-border);
}

/* The closing sheet is prose, so it gets a measure rather than a grid. */
.sheet__prose { flex: 1; display: grid; gap: 20px; align-content: start; padding-top: 14px; }
.sheet--landscape .sheet__prose { grid-template-columns: 1fr 1fr; column-gap: 32px; }
.sheet__prose h3 {
  margin: 0 0 6px; font-size: var(--text-xs); font-weight: var(--font-bold);
  color: var(--color-text-strong);
}
.sheet__prose dl {
  display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 4px 12px;
  font-size: var(--text-xs); line-height: 1.45;
}
.sheet__prose dt { font-weight: var(--font-semibold); color: var(--color-text-strong); }
.sheet__prose dd { margin: 0; color: var(--color-text-secondary); }
/* On every sheet now, so it needs its own air above the footer rule. */
.sheet__disclaimer {
  margin: 0 0 10px; padding-top: 10px;
  font-size: var(--sheet-text); font-style: italic; color: var(--color-text-muted);
}

/* The sheet's own scale for the same structure. */
.legend--sheet {
  margin-top: 14px; padding: var(--space-4) var(--space-6);
  gap: 3px var(--space-7); font-size: var(--sheet-text);
}
.legend--sheet dd { gap: var(--space-7); }
.legend--sheet .legend__swatch { width: 9px; height: 9px; }

.sheet__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin-top: auto; padding-top: 8px;
  border-top: 1px solid var(--color-border-rule);
  font-size: var(--sheet-text); color: var(--color-text-muted);
}

/* ==========================================================================
   15 · Empty state, narrow notice, toast
   ========================================================================== */

.empty {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-5);
  padding: var(--space-13); text-align: center;
  border: var(--border-hairline) dashed var(--color-border-rule); border-radius: var(--radius-lg);
  background: var(--color-surface); color: var(--color-text-muted);
}

.toast {
  position: fixed; left: 50%; bottom: var(--space-11); transform: translateX(-50%);
  z-index: var(--z-toast); max-width: min(560px, calc(100vw - 32px));
  padding: var(--space-5) var(--space-8);
  border-radius: var(--radius); background: var(--color-text-strong); color: var(--color-text-inverse);
  font-size: var(--text-base); box-shadow: var(--shadow-modal);
}
/* Class-driven, so it survives a re-render and plays exactly once. */
.toast.is-leaving { animation: toast-out var(--dur) var(--ease-in) forwards; }
@keyframes toast-out { to { opacity: 0; transform: translate(-50%, 8px); } }

/* ==========================================================================
   16 · Responsive
   The mockup is designed at 1440px. Below that the order of degradation is:
   fewer quarters before narrower columns, then optional columns, then scroll.
   ========================================================================== */

@media (max-width: 1100px) {
  .kpi-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Below the planning widths the row may break rather than overflow. */
  .toolbar { flex-wrap: wrap; }
}

@media (max-width: 1024px) {
  .wrap { padding-inline: var(--space-8); }
  .bi-grid { grid-template-columns: minmax(0, 1fr); }
  .proto-pill { display: none; }
  /* The pull cancels Swagger's own 20px padding; the rail is 16px here. */
  #swagger { margin-inline: calc(-1 * var(--space-8)); }
}

/*
 * On a phone the nine toolbar controls wrapped into six rows: 202px of chrome,
 * and the first data row landed at 95 % of the window height — you scrolled a
 * whole screen to see a number. One row that scrolls sideways keeps every
 * control reachable and gives the table back most of the screen.
 */
@media (max-width: 700px) {
  .toolbar {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    scroll-padding-inline: var(--space-8);
    margin-inline: calc(-1 * var(--space-8)); padding-inline: var(--space-8);
  }
  .toolbar::-webkit-scrollbar { width: 0; height: 0; }
  .toolbar > * { flex: 0 0 auto; }
  .toolbar .btn > :not(.icon):not(.badge-count) { overflow: visible; }

}

@media (max-width: 760px) {
  .kpi-strip { grid-template-columns: minmax(0, 1fr); }
  .page-header__row { flex-direction: column; align-items: flex-start; gap: var(--space-6); }
  .page-header__actions { flex-wrap: wrap; }
  .log { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .log--head { display: none; }
  .rebook { grid-template-columns: minmax(0, 1fr); }
  /* The modal's twin of the .chg/.log reflow, missed when that one was written. */
  .modal__log li, .facts__row { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
}

/*
 * Below 760 the page actions wrap; on a phone two stacked rows cost 66px of an
 * 844px screen. Declared after that block so it wins on source order.
 */
@media (max-width: 700px) {
  .page-header__actions {
    /* The row above turns into a column with align-items: flex-start, so this
       box sizes to its content and overflow-x never fires — it grew 4px past
       the window instead of scrolling inside it. */
    align-self: stretch;
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    margin-inline: calc(-1 * var(--space-8)); padding-inline: var(--space-8);
  }
  .page-header__actions::-webkit-scrollbar { width: 0; height: 0; }
  .page-header__actions > * { flex: 0 0 auto; }
}

/*
 * Landscape phone: 279 of 360px were chrome before any content, so the «too
 * narrow» note sat entirely below the fold.
 */
@media (max-height: 460px) and (orientation: landscape) {
  :root { --header-height: 48px; --tabbar-height: 34px; }
  .page-header { padding-block: var(--space-4) 0; }
  .page-header__row { margin-top: var(--space-3); }
}

/* Overridden per format at runtime; this is only the fallback before the
   export tab has rendered a sheet to read the format from. */
@page { size: A4 portrait; margin: 0; }

@media print {
  .shell-header, .tabs, .toolbar, .filterbar, .toast, .shell-footer,
  .page-header__actions, .sheetbar { display: none !important; }
  body { background: var(--color-paper); }
  .grid-card, .table-card, .bi-card { break-inside: avoid; }

  /* The paper mount is screen scaffolding; on paper the sheet is the page. */
  .mount { display: block; padding: 0; border: 0; background: none; overflow: visible; }
  .content, .wrap { padding: 0; gap: 0; }
  .page-header { display: none; }
  /*
   * The sheet keeps its size. It is already exactly its paper format, and the
   * page box is set to match (see syncPageSize), so one sheet is one page at
   * its true dimensions. Letting it reflow to the print container was the whole
   * problem: an A4 sheet arrived on paper 1440px wide and 962px tall, and the
   * row budget measured at 210mm no longer described the page.
   */
  .sheet { box-shadow: none; break-inside: avoid; }
  /*
   * The pensum ramp is a background fill, and browsers drop background fills by
   * default. Without this the sheet prints 687 white rectangles under a legend
   * that still claims «bis 39 %», «40 – 79 %», «80 – 119 %», «ab 120 %».
   */
  .sheet, .sheet * { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  /* One sheet, one page. The last one must not push out an empty trailing page. */
  .sheet + .sheet { break-before: page; }
  .mount { gap: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* =============================================================================
   Account — the menu behind the initials, and the page without a session
   ========================================================================== */

/* The initials are a control now, so they answer to hover and focus like one. */
.avatar[data-act] { border: 0; cursor: pointer; }
.avatar[data-act]:hover, .avatar[data-act].is-open { background: var(--color-accent-hover); }
.avatar--lg { width: 40px; height: 40px; font-size: var(--text-base); }

.account {
  display: flex; align-items: center; gap: var(--space-5);
  padding: var(--space-4) var(--space-5) var(--space-3);
}
.account__name { margin: 0; font-weight: var(--font-semibold); color: var(--color-text-strong); }
.account__meta, .account__note {
  margin: 0; font-size: var(--text-sm); color: var(--color-text-muted);
}
.account__note { padding: 0 var(--space-5) var(--space-4); }
.dd__sep { height: 1px; margin: var(--space-2) 0; background: var(--color-border); }

/* ---- the settings dialog ------------------------------------------------ */

/*
 * The same gutter every other modal body uses — .modal__lead, .modal__section
 * and .rebook all sit on --space-11, and the head and foot bring their own 24px.
 * Without it this block started at the raw edge of the dialog: the section
 * headings stood 24px to the left of the title above them.
 */
.settings { display: flex; flex-direction: column; gap: var(--space-8); padding: var(--space-8) var(--space-11); }
.settings__block { display: flex; flex-direction: column; gap: var(--space-4); }
.settings__title {
  margin: 0; font-size: var(--text-sm); font-weight: var(--font-semibold);
  letter-spacing: var(--tracking-caption); text-transform: uppercase;
  color: var(--color-text-muted);
}
/*
 * The whole row is the target, not the 16px box: a checkbox on its own is half
 * the minimum touch target, and the note beside it is what the reader is
 * actually deciding on.
 */
/*
 * Outdented by its own padding, so the control lines up with the headings while
 * the hover fill still reaches into the gutter. Indented instead, every checkbox
 * sat 8px right of everything above it.
 */
.settings__row {
  display: flex; align-items: flex-start; gap: var(--space-5);
  min-height: var(--target-min); padding: var(--space-3) var(--space-4);
  margin-inline: calc(-1 * var(--space-4));
  border-radius: var(--radius); cursor: pointer;
}
.settings__row:hover { background: var(--color-surface-hover); }
/* The inline margins are the browser's own — 4px on a checkbox, 5px on a radio —
   and they push the control off the gutter the rest of the block sits on. */
.settings__row input { margin: 3px 0 0; width: 16px; height: 16px; flex: 0 0 auto; accent-color: var(--color-accent); }
.settings__row > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.settings__label { color: var(--color-text-strong); }
.settings__note { font-size: var(--text-sm); color: var(--color-text-muted); }
.settings__note--lead { margin: 0 0 var(--space-2); }
.settings__row--inline > span { flex-direction: row; align-items: baseline; gap: var(--space-4); }
.settings__langs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px; }

.settings__eiam {
  margin: 0; padding: var(--space-5);
  border-radius: var(--radius); background: var(--color-surface-sunken);
  font-size: var(--text-sm); color: var(--color-text-secondary);
}

/* ---- signed out --------------------------------------------------------- */

.signout { display: flex; justify-content: center; padding: var(--space-12) var(--space-8); }
.signout__card {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-6);
  max-width: 52ch; padding: var(--space-10);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-raised);
}
.signout__title { margin: 0; font-size: var(--text-2xl); color: var(--color-text-strong); }
.signout__lead { margin: 0; color: var(--color-text-body); }
.signout__note, .signout__demo { margin: 0; font-size: var(--text-sm); color: var(--color-text-muted); }
.signout__demo { font-style: italic; }

@media (max-width: 700px) {
  .settings__langs { grid-template-columns: minmax(0, 1fr); }
  .signout { padding: var(--space-8) var(--space-6); }
  .signout__card { padding: var(--space-8); }
}

/* ==========================================================================
   17 · The tab line
   The tabs and the page actions share one line. The breadcrumb bar and the
   title band that used to sit above the tabs cost 100px of a 630px laptop
   window and said nothing the tab did not.
   ========================================================================== */
.tabline { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-8); }
/* The rule under the tabs runs the full width of the line, with the actions
   standing on it, rather than stopping where the last tab does. The active
   tab's own 2px underline sits on the same edge. */
.tabline { box-shadow: inset 0 -1px 0 var(--color-border); }
.tabline .tabs { margin-top: 0; box-shadow: none; }
.tabline .page-header__actions { padding-bottom: var(--space-4); }

/* ==========================================================================
   18 · The phase band — the bar plan inside the pensum row

   Two numbers, and everything else is arithmetic on them.

   --band-h    how tall the bar is: a 14px strip under the figures, the bar
               plan's own height once the figures are gone, nothing at all
               once neither bars nor gates are drawn.
   --values-h  how much of the row the figures claim: all of it bar the band,
               or none of it when they are hidden.

   Only the rules on .grid-card test a state — the classes the view sets for
   what it switched off. The band, the bar, the figures and the ring around an
   edited cell all derive their geometry from the pair, so a change to either
   moves everything together and there is no combination of switches left to
   forget. An earlier draft wrote each arrangement out as its own rule, and
   every new switch multiplied the ones already there.
   ========================================================================== */
.grid-card {
  --band-h: var(--band-height);
  --band-lab-size: var(--text-2xs);
  --band-lab-pad: var(--space-2);
  --values-h: calc(100% - var(--band-h));
}
/* With the figures gone the bar has the row to itself, and it is then the
   application's own bar rather than a strip squeezed under a number: height,
   type size and indent all come from the tokens the bar plan uses. */
.grid-card.is-values-off {
  --values-h: 0%;
  --band-h: var(--bar-height);
  --band-lab-size: var(--text-xs);
  --band-lab-pad: var(--space-4);
}
.grid-card.is-bars-off.is-gates-off { --band-h: 0px; }

/* The band is positioned, not placed. As a grid item spanning the quarter
   columns it was placed before the auto-placed cells and pushed every one of
   them into an implicit column; out of flow there is nothing to collide with.
   Its offset follows --sticky-w, the frozen block's own width, so it stays on
   the axis when the Ansicht menu adds or removes a column. */
.pgrid .prow { position: relative; }
.pband {
  position: absolute;
  left: var(--sticky-w); right: 0;
  /* It takes whatever the figures do not, and centres the bar inside that. */
  top: var(--values-h); bottom: 0;
  display: flex; align-items: center;
  /* Under the frozen block, which must stay opaque over it. */
  z-index: var(--z-base);
  /* Pointer-transparent, so the cell under it stays the control; the gates
     take their own hits back, at 11px in a 72px cell. */
  pointer-events: none;
}
.pband .gantt__gate { pointer-events: auto; }
/* The bar too: under the figures it is the same control as in the bar plan. */
.pband .gantt__bar { pointer-events: auto; }
/* In the bar plan the bars are the controls, exactly as they were on the
   Termine tab. */
.grid-card.is-values-off .pband { pointer-events: auto; }

.pband .gantt__bar {
  height: var(--band-h);
  padding-inline: var(--band-lab-pad);
  font-size: var(--band-lab-size); line-height: 1;
  /* Four channels share the row — fill, number, band, gate — so the band is
     drawn at the weight the grid uses for its own boundaries, and the number
     keeps the eye. Not below the 3:1 a meaningful boundary needs. */
  border-color: var(--color-border-emphasis);
}
.pband .gantt__bar.is-first { border-left-color: var(--color-border-emphasis); }
.pband .gantt__bar.is-delay { border-color: var(--color-danger-solid); }
.pband .gantt__bar.is-unassigned { border-color: var(--color-warn-solid); }

/* The figure is centred in what the band leaves: pad the cell by the band's
   height and let align-items do the arithmetic. */
.pgrid .prow > .pcell--val { align-items: center; padding-bottom: var(--band-h); }

/* Three layers, and only three. The switch hides the pensum VALUES: the
   figures, the totals that are sums of them, and the editor they open. The
   fill is not part of that layer — it belongs to «Pensum einfärben». */
.grid-card.is-values-off .pgrid .prow > .pcell--val { pointer-events: none; cursor: default; }
.grid-card.is-values-off .cellv { visibility: hidden; }
.grid-card.is-uncoloured .pgrid .pcell--val { background: var(--color-surface); color: var(--color-text-body); }

/* ---- «Heute», the bar plan's marker ---------------------------------------
   It spans the project rows and nothing else; clipped sideways to the axis so
   it cannot scroll out under the frozen block, open at the top so the badge
   can reach above the first row. The colour is the application's own: «now»
   is a position, not a problem, and red belongs to over-capacity alone. */
.pgrid__rows { position: relative; }
.ptoday {
  position: absolute; top: 0; bottom: 0;
  left: var(--sticky-w); right: 0;
  clip-path: inset(-40px 0 0 0);
  z-index: var(--z-overlay);
  pointer-events: none;
}
.ptoday__line {
  position: absolute; top: 0; bottom: 0; width: 2px;
  background: var(--color-brand); z-index: var(--z-sticky);
}
/* Centred on the header/body boundary rather than sitting below it, so the
   marker costs no vertical space of its own. */
.ptoday__badge {
  position: absolute; top: 0; transform: translate(-50%, -50%); z-index: var(--z-label);
  padding: 1px var(--space-3); border-radius: var(--radius-pill);
  background: var(--color-brand); color: var(--color-text-inverse);
  font-size: var(--text-2xs); font-weight: var(--font-bold);
  letter-spacing: var(--tracking-caption); white-space: nowrap;
}

/* ---- the cell being edited ------------------------------------------------
   The ring used to close around the whole cell, which was right when the cell
   held nothing but a number. It now also carries the band, so the ring takes
   the number's own lane and stops above the bar; and the cell drops out of the
   heat ramp while it is in flux — a value being changed is not a value you
   should still be reading a magnitude off. The white ground yields the same
   lane, or it painted over the bar underneath for as long as the popover was
   open. */
.pgrid .pcell--val.is-editing {
  box-shadow: none;
  background-color: transparent;
  background-image: linear-gradient(to bottom,
    var(--color-surface) var(--values-h), transparent var(--values-h));
}
.pgrid .pcell--val.is-editing::after {
  content: ''; position: absolute; pointer-events: none;
  inset: 0; bottom: var(--band-h);
  border-radius: var(--radius-sm);
  box-shadow: var(--ring-cell-edit);
}
/* The picker marks its cell the same way. That one is frozen and has no band
   under it, so the ring takes the whole cell — and it stays sticky: made
   relative, its frozen offset turned into a shift over the quarter columns. */
.pgrid .pcell--lead.is-editing {
  position: sticky; z-index: calc(var(--z-frozen) + 1);
  box-shadow: var(--ring-cell-edit); border-radius: var(--radius-sm);
}

/* ---- the Bearbeitender picker ---------------------------------------------
   A popover on the cell, like the pensum editor; the list reuses the rebooking
   dialog's rows. */
.pop--assign { width: var(--pop-assign-width); }
.pop--assign .dd__searchfield { margin: var(--space-6) 0 var(--space-5); }
.assign__list { max-height: 232px; }
.assign__list li { gap: var(--space-4); }
.person__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* «Niemand zuweisen» has no unit and takes the row. */
.person__name:only-child { grid-column: 1 / -1; }
.assign__list .ampel { flex: 0 0 auto; }
.assign__list li.is-on { background: var(--color-surface-selected); font-weight: var(--font-semibold); }
.assign__list li:focus-visible { outline: var(--focus-ring); outline-offset: -2px; }
.assign__none { color: var(--color-text-muted); font-style: italic; }

/* ==========================================================================
   19 · One row for the projects and the time; the small laptop
   ========================================================================== */
/* Search and filters on the left say WHICH projects; the steps and «Ansicht»
   on the right say WHICH quarters and HOW the rows are drawn. The time bar
   that used to hold the right half is gone, and with it 40px above the first
   row. Measured: 1060px of controls at 1280 against 1233 available; below
   1250px the row may still break, the time half taking a second line,
   right-aligned. */
@media (max-width: 1250px) {
  .toolbar { flex-wrap: wrap; }
  .toolbar__spacer { flex-basis: 100%; }
  .toolbar__spacer + .btn { margin-left: auto; }
}

/* With the search open the row is longer by what the field keeps once it has
   shrunk to its floor — some 175px over the closed icon — so it may break
   that much sooner. 1300 is a first estimate from the figures above, not a
   measurement of the rendered row, and the French labels run wider than the
   German ones the row was sized against. Bounded below at 701px: the phone
   rule in section 16 wants one scrolling row, and a later wrap rule of equal
   specificity would override it. */
@media (min-width: 701px) and (max-width: 1300px) {
  .toolbar:has(.xsearch.is-open) { flex-wrap: wrap; }
  .toolbar:has(.xsearch.is-open) .toolbar__spacer { flex-basis: 100%; }
  .toolbar:has(.xsearch.is-open) .toolbar__spacer + .btn { margin-left: auto; }
}

/* «Gruppieren nach:» is read, not seen, once the row cannot hold it. The full
   toolbar measures 1310px and a 1366 window offers 1293, so the prefix yields
   from 1400px down. Below that the button says only «Teilportfolio» beside a
   filter that says the same; the grid icon and the title tell them apart. */
@media (max-width: 1400px) {
  .btn__prefix {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    padding: 0; border: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
}

/* And at any width once the search is open: the open field costs the row more
   than the prefix does, and the prefix is the first thing the row gives up. */
.toolbar:has(.xsearch.is-open) .btn__prefix {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* The mockup is designed at 1440px and the first breakpoint above was 1100px.
   A 1366×768 laptop sits in the gap, and at that width the first project row
   began 355px down a window whose usable height, under a browser bar and a
   taskbar, is about 630px. Nothing is removed; every band gives back what it
   did not need. The tab bar takes the 34px the landscape-phone rule already
   gives it, the control bars close up, the filter summary sits at the height
   of a small control, and the group headings and the gaps between cards
   shrink by a step. */
@media (max-width: 1440px) {
  :root { --shell-pad-x: 16px; --tabbar-height: 34px; }
  .toolbar { gap: var(--space-3); }
  .toolbar .btn { padding-inline: var(--space-5); }
  .toolbar__check { padding-inline: var(--space-3); }
  .page-header { padding-top: var(--space-4); }
  .filterbar { min-height: var(--control-height-sm); }
  .pgrouphead { padding-bottom: var(--space-3); }
  .pgrouphead__toggle { min-height: var(--control-height-sm); }
  .pblock + .pgroup, .pgroup + .pblock, .pgroup + .pgroup { margin-top: var(--space-10); }
}

/* ---- rhythm at the top ------------------------------------------------------
   Four bands of different rank sat 10 to 16px apart, which read as one dense
   block: the tabs, the toolbar, the filter readout and the first group
   heading. The gaps now say what each band is. The tabs are page navigation,
   so the toolbar stands 20px clear of their rule; the filter readout belongs
   to the toolbar and stays close at 8px; the content starts at the group
   heading, which gets the widest gap on the page, 24px. On a small laptop the
   same shape at 16 / 6 / 16. */
.content { padding-top: var(--space-10); }
.toolbar + .filterbar { margin-top: calc(var(--space-4) - var(--space-8)); }
.filterbar + .grid-card { margin-top: var(--space-4); }
@media (max-width: 1440px) {
  .content { padding-top: var(--space-8); gap: var(--space-6); }
  .toolbar + .filterbar { margin-top: calc(var(--space-3) - var(--space-6)); }
  .filterbar + .grid-card { margin-top: var(--space-2); }
}
