/*
  ADMIN shell — sidebar + main-pane layout structure.
  ADMIN-specific because LIVE has a different shell (header-only, no sidebar).
  Atomic components (buttons, cards, forms, modal, lang-pill, footer, auth-*)
  live in components.css and are shared across both areas.
*/

.admin-shell {
    height: 100%;
    width: 100%;
    display: flex;
    background: var(--sidan-layout-bg);
    color: var(--sidan-layout-text);
    position: relative;
}

/* ---- Sidebar -----------------------------------------------------------
   Fixed-width vertical column. Brand + customer switch are pinned at the
   top, foot at the bottom; only the nav list in between (.admin-sidebar-scroll)
   scrolls when items overflow vertically. The sidebar itself does NOT scroll
   so the user-row stays reachable on short viewports. */
.admin-sidebar {
    width: 240px;
    flex: 0 0 240px;
    background: var(--sidan-surface);
    border-right: 1px solid var(--sidan-border);
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.admin-sidebar-brand {
    /* Positioning context for the save-confirmation overlay, which covers the
       whole brand rectangle. */
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    /* Top padding matches .admin-main-inner so the brand name's baseline
       lines up horizontally with the page title on the right of the shell. */
    padding: 16px 28px 14px 20px;
    box-sizing: border-box;
    border-bottom: 1px solid var(--sidan-border);
    text-decoration: none;
    color: inherit;
    flex: 0 0 auto;
}

/* Logo stacked above the tagline. Stretches to fill the brand container between
   its padding so the full-width save-pill underneath never clips. */
.admin-sidebar-brand-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

/* Small graphical logo in the brand slot. Height-capped so it stays compact;
   width follows the aspect ratio and is bounded to the sidebar. */
.admin-sidebar-brand-logo {
    height: 32px;
    width: auto;
    max-width: 100%;
    display: block;
}

.admin-sidebar-brand-sub {
    /* Static tagline directly under the logo. */
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
    line-height: 1.2;
}

/* Save-confirmation overlay. SaveNotificationService raises a notification for
   2 seconds after any successful save anywhere in the app; AdminLayout then
   renders this overlay covering the whole brand rectangle — success-filled
   background with a large, centered white check. An unmistakable cue that
   leaves the brand text untouched underneath. */
.admin-sidebar-brand-saved {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--sidan-color-primary);
    color: var(--sidan-color-primary-ink);
}

/* Failure counterpart: same overlay, danger fill + cross (see NotifyError). */
.admin-sidebar-brand-saved.is-error {
    background: var(--sidan-danger, #b32424);
}

.admin-sidebar-brand-saved .material-symbols-outlined {
    font-size: 56px;
    line-height: 1;
}

/* Scrollable middle column — the nav lists live here. Flex sizing pushes the
   brand to the top, foot to the bottom, and lets this region absorb the
   remaining vertical space (or scroll when nav items overflow). The top
   padding lifts the first nav item ("Übersicht") off the brand divider. */
.admin-sidebar-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-top: 10px;
}

.admin-sidebar-section {
    padding: 14px 20px 4px;
    font-size: var(--sidan-text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--sidan-text-subtle);
    font-weight: 600;
}

.admin-sidebar-nav {
    padding: 4px 8px;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.admin-nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
    border-radius: var(--sidan-radius-sm);
    color: var(--sidan-text);
    cursor: pointer;
    font-size: var(--sidan-text-md);
    text-decoration: none;
}

.admin-nav-item:hover {
    background: var(--sidan-surface-hover);
    color: var(--sidan-text);
}

.admin-nav-item.is-active {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
    font-weight: 500;
}

.admin-nav-item .material-symbols-outlined {
    font-size: var(--sidan-text-xl);
    flex-shrink: 0;
    opacity: 0.85;
}

.admin-nav-count {
    margin-left: auto;
    font-size: var(--sidan-text-sm);
    color: var(--sidan-text-subtle);
    font-variant-numeric: tabular-nums;
}

.admin-nav-item.is-active .admin-nav-count {
    color: var(--sidan-color-primary);
}

.admin-sidebar-foot {
    flex: 0 0 auto;
    /* Padding + min-height intentionally mirror .app-footer on the right so
       both ends of the shell's bottom edge sit at the same y — the sidebar
       footer and the app footer butt against each other and any mismatch
       reads as a kink in the boundary line. */
    padding: 8px 16px;
    min-height: 48px;
    border-top: 1px solid var(--sidan-border);
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: var(--sidan-text-base);
    color: var(--sidan-text-muted);
}

.admin-user-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--sidan-text-base);
    font-weight: 600;
    flex-shrink: 0;
}

.admin-user-meta {
    flex: 1;
    min-width: 0;
}

.admin-user-name {
    color: var(--sidan-text);
    font-weight: 500;
    font-size: var(--sidan-text-base);
}

/* Avatar + name wrapper as a single clickable target leading to the
   self-service account page. The negative margin keeps the visual
   footprint identical to the previous static layout while letting the
   hover background spill a few pixels outward. */
.admin-user-link {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    color: inherit;
    text-decoration: none;
    border-radius: 6px;
    padding: 4px 6px;
    margin: -4px -6px;
    transition: background-color 0.15s ease;
}

.admin-user-link:hover {
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text);
}

.admin-sidebar.is-collapsed .admin-sidebar-foot {
    padding: 12px 0;
    justify-content: center;
}

/* Collapsed: hide the logout chevron + the name half of the user link,
   keep only the avatar visible so the link still leads to the account
   page. The avatar lives inside .admin-user-link now, so the rule has
   to traverse into it rather than picking direct children only. */
.admin-sidebar.is-collapsed .admin-user-logout {
    display: none;
}

.admin-sidebar.is-collapsed .admin-user-link {
    padding: 0;
    margin: 0;
    flex: 0 0 auto;
}

.admin-sidebar.is-collapsed .admin-user-meta {
    display: none;
}

/* ---- Main pane --------------------------------------------------------- */
.admin-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: var(--sidan-layout-bg);
}

.admin-main-inner {
    flex: 1;
    overflow-y: auto;
    /* Top padding matches .admin-sidebar-brand so the page title's baseline
       lands at the same y as the brand name on the left. */
    padding: 16px 32px;
}

/* ---- Page header ------------------------------------------------------- */
.admin-page-header {
    display: flex;
    /* Top-align so the page-controls (Neu / Löschen / …) sit at the same
       baseline as the page title rather than centered against the longer
       title + subtitle block. The back-link belongs ABOVE this header on
       detail pages — see .admin-back-link. */
    align-items: flex-start;
    gap: 16px;
    /* Consistent gap to whatever follows (toolbar, tabs, table card). Used
       across every admin page so the page header → content rhythm is the
       same everywhere — change here, applies everywhere. */
    margin-bottom: 16px;
}

.admin-breadcrumb {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
    margin-bottom: 4px;
}

.admin-breadcrumb-sep {
    color: var(--sidan-text-subtle);
}

.admin-page-title {
    /* Typography mirrors .admin-sidebar-brand-name exactly — same size, weight,
       line-height and letter-spacing — so the sidebar title and the page title
       look like two faces of the same heading. Only the colour differs:
       brand uses customer primary, the page title is plain ink. */
    font-size: var(--sidan-text-3xl);
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.01em;
    color: var(--sidan-text);
    margin: 0;
    /* Page heading must never appear as a focused control — some browsers
       paint a focus ring around an <h1> when it sits inside a landmark or
       receives programmatic focus (e.g. after route changes). It is read-only
       text, not interactive, so suppress the visual focus state entirely. */
    outline: none;
}
.admin-page-title:focus,
.admin-page-title:focus-visible {
    outline: none;
    box-shadow: none;
}

.admin-page-subtitle {
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
    /* Kept in sync with .admin-sidebar-brand-sub's top margin so the sidebar
       tagline and a page subtitle land on identical baselines across the
       shell. Used by every admin page — change here, applies everywhere. */
    line-height: 1.2;
    margin-top: 8px;
}

/* Page-level action buttons (Neu / Löschen / Duplizieren / Export …). One
   shared slot for every admin page — sits flush-right in the page header,
   top-aligned to the page title. Pages can drop in 1..n buttons; the gap
   keeps them comfortably spaced regardless of count. */
.admin-page-controls {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Status chip sitting next to a row of -lg buttons in admin-page-controls
   (e.g. the "Gesperrt" badge above the user delete button). Replicates
   .admin-toolbar-badge metrics — same role, different container — so a
   default Bootstrap .badge does not render as a tiny pill next to the
   chunky .btn-lg neighbours. The transparent 1 px border lines the badge
   up vertically with the buttons' 1 px solid border. */
.admin-page-badge {
    display: inline-flex;
    align-items: center;
    font-size: 1.25rem;
    font-weight: normal;
    padding: 0.5rem 1rem;
    line-height: 1.5;
    border: 1px solid transparent;
}

/* The badge sometimes doubles as a link (e.g. "Gesperrt" → Konto tab where
   the lock can be cleared). The global `a` rule paints it in the primary
   colour which kills the contrast on a coloured background; pin the text
   to white (matching every Bootstrap dark-coloured .bg-* utility) and add
   a subtle hover cue instead of a colour shift. */
.admin-page-badge-link,
.admin-page-badge-link:hover {
    color: #fff;
    text-decoration: none;
    cursor: pointer;
}

.admin-page-badge-link:hover {
    filter: brightness(1.05);
}

/* ---- Texts page (translation editor) ----------------------------------- */
.admin-lang-pills {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.admin-lang-pill {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    border-radius: var(--sidan-radius-md);
    border: 1px solid var(--sidan-border);
    background: var(--sidan-surface);
    color: var(--sidan-text-muted);
    cursor: pointer;
    font-size: var(--sidan-text-sm);
    line-height: 1;
    transition: background .12s, color .12s, border-color .12s;
}

.admin-lang-pill:hover {
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text);
}

.admin-lang-pill.is-active {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
    border-color: var(--sidan-color-primary-soft);
    font-weight: 500;
}

.admin-lang-pill-key {
    font-weight: 600;
    letter-spacing: 0.04em;
}

.admin-lang-pill-name {
    color: inherit;
    opacity: 0.7;
}

.admin-texts-layout {
    display: grid;
    grid-template-columns: minmax(260px, 320px) 1fr;
    gap: 24px;
    height: calc(100vh - 220px);
    min-height: 480px;
}

.admin-texts-tree {
    background: var(--sidan-surface);
    border-radius: var(--sidan-radius-md);
    padding: 12px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.admin-texts-filter {
    margin-bottom: 4px;
}

.admin-texts-group-title {
    font-size: var(--sidan-text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--sidan-text-subtle);
    font-weight: 600;
    padding: 4px 8px;
}

.admin-texts-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border-radius: var(--sidan-radius-sm);
    border: none;
    background: transparent;
    text-align: left;
    width: 100%;
    color: var(--sidan-text);
    font-size: var(--sidan-text-base);
    cursor: pointer;
}

.admin-texts-row:hover {
    background: var(--sidan-surface-sunk);
}

.admin-texts-row.is-active {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
    font-weight: 500;
}

.admin-texts-row.is-untranslated .admin-texts-row-label {
    color: var(--sidan-text-subtle);
    font-style: italic;
}

.admin-texts-row-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-texts-row-dot {
    color: var(--sidan-warning);
    font-size: var(--sidan-text-md);
    line-height: 1;
}

.admin-texts-empty-list {
    padding: 12px;
    font-style: italic;
    color: var(--sidan-text-subtle);
}

.admin-texts-editor {
    background: var(--sidan-surface);
    border-radius: var(--sidan-radius-md);
    padding: 24px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.admin-texts-empty {
    color: var(--sidan-text-subtle);
    font-style: italic;
    padding: 24px;
    text-align: center;
}

.admin-texts-key-path {
    font-family: var(--sidan-font-mono);
    font-size: var(--sidan-text-sm);
    color: var(--sidan-text-subtle);
    background: var(--sidan-surface-sunk);
    padding: 6px 10px;
    border-radius: var(--sidan-radius-sm);
    align-self: flex-start;
}

.admin-texts-source {
    background: var(--sidan-surface-sunk);
    border-radius: var(--sidan-radius-sm);
    padding: 12px 14px;
}

.admin-texts-source-label {
    font-size: var(--sidan-text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--sidan-text-subtle);
    font-weight: 600;
    margin-bottom: 4px;
}

.admin-texts-source-text {
    color: var(--sidan-text);
    white-space: pre-wrap;
}

.admin-texts-textarea {
    resize: vertical;
    min-height: 120px;
    font-family: inherit;
}

/* ──────────────────────────────────────────────────────────────────────
   Customers admin — list / detail / new
   ────────────────────────────────────────────────────────────────────── */

/* Subtle text-link "zurück" placed BELOW the page subtitle inside the
   left column of .admin-page-header on detail and new pages. Sits as the
   last line of the header text block so it never competes with the title
   for prominence: title and subtitle stay on top, the back-link is a
   quiet escape hatch underneath. Stays a link (no btn classes) — the
   operator's primary action is the page itself; navigating back is
   secondary. */
.admin-back-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
    text-decoration: none;
    line-height: 1.2;
    /* 8px so the back-link lands on the SAME baseline as the sidebar tagline
       (.admin-sidebar-brand-sub, also 8px under the brand name): title and
       brand name share size/line-height, so an identical top gap puts the
       back-link and "Assessment Suite" on one line. When a subtitle IS present,
       the sibling-adjacent override below restores a larger gap. */
    margin-top: 8px;
}

/* `:not(:empty)` is critical: without it, an empty subtitle div (kept in the
   DOM but hidden via the rule below) would still match the adjacent selector
   and force the back-link into the 4px gap, which is wrong when the subtitle
   isn't actually visible. */
.admin-page-subtitle:not(:empty) + .admin-back-link {
    margin-top: 12px;
}

/* A row of two back-links (e.g. "Zum Projekt" + "Zum Setting" on an exercise
   page) — sit side by side; the container owns the top margin so the individual
   links don't each add their own. */
.admin-back-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 18px;
    /* Same 8px as a single .admin-back-link — aligns the row with the sidebar
       tagline baseline (.admin-sidebar-brand-sub). */
    margin-top: 8px;
}
.admin-page-subtitle:not(:empty) + .admin-back-links {
    margin-top: 12px;
}
.admin-back-links .admin-back-link {
    margin-top: 0;
}

/* Customer name inline after the page title — pipe-separated affix so the
   operator always sees which customer the project/setting/exercise belongs to,
   right in the heading. Smaller and muted so the entity's own name still
   dominates; the leading pipe is drawn via ::before to keep the markup clean. */
.admin-page-customer {
    margin-left: 12px;
    font-size: var(--sidan-text-xl);
    font-weight: 400;
    color: var(--sidan-text-muted);
    white-space: nowrap;
}
.admin-page-customer::before {
    content: "|";
    margin-right: 12px;
    font-weight: 400;
    color: var(--sidan-text-subtle);
}

/* Customer ID(s) inline after the page title (SystemAdmin+ only). Subtle
   monospace, smaller and lighter than the title so the name still dominates. */
.admin-page-id {
    margin-left: 12px;
    font-family: var(--sidan-font-mono, ui-monospace, Menlo, monospace);
    font-size: var(--sidan-text-sm);
    font-weight: 400;
    color: var(--sidan-text-subtle);
    letter-spacing: .06em;
    white-space: nowrap;
}

/* "Deaktiviert" status chip — shown ONLY when an entity is inactive (active is
   the silent default). Solid danger fill + white text so a deactivated object is
   impossible to miss; kunden-ueberschreibbar via the central danger variable.
   The big variant sits next to a detail-page title, the -sm one inside lists. */
.admin-status-inactive,
.admin-status-inactive-sm {
    display: inline-flex;
    align-items: center;
    border-radius: 999px;
    font-weight: 700;
    color: #fff;
    background: var(--sidan-danger, #b32424);
    white-space: nowrap;
    vertical-align: middle;
}
.admin-status-inactive {
    gap: 8px;
    margin-left: 16px;
    padding: 6px 18px;
    font-size: var(--sidan-text-md);
}
.admin-status-inactive-sm {
    gap: 5px;
    margin-left: 10px;
    padding: 1px 11px;
    font-size: var(--sidan-text-sm);
}
.admin-status-inactive .material-symbols-outlined {
    font-size: 1.2em;
}
.admin-status-inactive-sm .material-symbols-outlined {
    font-size: 1.05em;
}

/* Two-state activation pill (ActivationBadge) — for status table cells that must
   show BOTH states explicitly, where .admin-status-inactive only flags the
   inactive case. Success fill when active, danger fill when not; both run
   through the central, customer-overridable colour variables. */
.admin-status-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border-radius: 999px;
    padding: 1px 11px;
    font-size: var(--sidan-text-sm);
    font-weight: 700;
    color: #fff;
    white-space: nowrap;
    vertical-align: middle;
}
.admin-status-pill.is-active {
    background: var(--sidan-success, #15803d);
}
.admin-status-pill.is-inactive {
    background: var(--sidan-danger, #b32424);
}
.admin-status-pill .material-symbols-outlined {
    font-size: 1.05em;
}

/* Technical key shown next to a form label (e.g. the EmailOccasion key
   beside the template name). Subtle monospace so the label still reads first. */
.admin-label-key {
    margin-left: 8px;
    font-family: var(--sidan-font-mono, ui-monospace, Menlo, monospace);
    font-size: var(--sidan-text-sm);
    font-weight: 400;
    color: var(--sidan-text-subtle);
    letter-spacing: .04em;
}

/* Belt + braces: even if a page renders an empty subtitle div (e.g.
   `<div class="admin-page-subtitle">@_subtitle</div>` with an empty string),
   collapse it so the back-link follows the title directly. */
.admin-page-subtitle:empty {
    display: none;
}

.admin-back-link .material-symbols-outlined { font-size: var(--sidan-text-md); }

.admin-back-link:hover {
    color: var(--sidan-color-primary);
}

.admin-user-logout {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--sidan-radius-sm);
    color: var(--sidan-text-muted);
    text-decoration: none;
    margin-left: auto;
}

.admin-user-logout:hover {
    background: var(--sidan-surface-strong, rgba(0, 0, 0, .05));
    color: var(--sidan-color-primary);
}

/* Pills — reusable toggle/quick-filter chips. Generic on purpose so any
   in-page toggle set can use them (first user: the event date quick-filters).
   The active state fills with the customer's primary tint, so the chips
   theme per tenant like everything else. */
.admin-pills {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.admin-pill {
    display: inline-flex;
    align-items: center;
    padding: 8px 14px;
    border-radius: var(--sidan-radius-pill);
    border: 1px solid var(--sidan-border);
    background: var(--sidan-surface);
    color: var(--sidan-text-muted);
    cursor: pointer;
    font-size: var(--sidan-text-sm);
    line-height: 1;
    transition: background .12s, color .12s, border-color .12s;
}

.admin-pill:hover {
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text);
}

.admin-pill.is-active {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
    border-color: var(--sidan-color-primary-soft);
    font-weight: 500;
}

/* Quick-filter pill row sits directly above the toolbar; keep the page
   rhythm by reusing the toolbar's own top spacing. */
.admin-quickfilters {
    margin-top: 24px;
}

/* Toolbar (search + counter) above the table.

   align-items: flex-end so a mix of labeled cells (a .form-label sitting on
   top of a date input adds ~22px) and label-less controls (an unwrapped
   .form-select that is its own toolbar child) all line up along the input
   baseline — the labels float above, the controls stay aligned. */
.admin-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    margin: 24px 0 16px;
}

/* When the toolbar opens a tab panel it sits directly under the panel's own
   28px padding — the 24px top margin would stack on top and push the filters
   far below the tab strip. Collapse it so the panel padding alone sets the gap. */
.admin-tab-panel > .admin-toolbar:first-child {
    margin-top: 0;
}

/* Unwrapped label-less control (form-select / input) sitting directly in
   the toolbar. Caps the width so a list of long customer names doesn't push
   the rest of the row off-screen, but lets the control breathe enough that
   long captions stay legible. */
.admin-toolbar-control {
    flex: 0 1 220px;
}

.admin-toolbar-search {
    flex: 1 1 auto;
    position: relative;
    max-width: 480px;
}

.admin-toolbar-search .material-symbols-outlined {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--sidan-text-muted);
    font-size: 22px;
    pointer-events: none;
}

.admin-toolbar-search input {
    padding-left: 42px;
}

/* Compact filter cell — small label above the control, control inherits
   its -lg sizing. Several of these sit side-by-side in the toolbar (date
   range, project, demo flag, …). */
.admin-toolbar-filter {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.admin-toolbar-filter > .form-label {
    margin-bottom: 0;
    font-size: var(--sidan-text-sm);
    color: var(--sidan-text-muted);
}

/* Active-filter highlight: a toolbar control whose value differs from its
   "Alle …"/empty default (or a search field with text) gets a soft primary
   tint, so it's obvious at a glance which filters are narrowing the list.
   Uses the central, customer-overridable primary-soft variable; the :focus
   selector keeps the tint while the operator is editing the field. */
.admin-toolbar .admin-filter-active,
.admin-toolbar .admin-filter-active:focus,
.admin-panel-toolbar .admin-filter-active,
.admin-panel-toolbar .admin-filter-active:focus {
    background-color: var(--sidan-color-primary-soft);
}

/* Hit-count chip sized to match the -lg toolbar siblings (button group, search
   input). Bootstrap has no .badge-lg variant, so we replicate FeedbackSchool's
   own .badge-lg metrics here. Border-radius stays at the Bootstrap default
   (mild rounded rectangle) — explicitly NOT a pill, since pill counters look
   visually inconsistent with the rest of the admin chrome.

   Transparent 1px border compensates for the missing border on .badge vs.
   the 1px solid border on neighbouring .btn elements — without it the badge
   ends up 2px shorter than the button group sitting right next to it. */
.admin-toolbar-badge {
    display: inline-flex;
    align-items: center;
    font-size: 1.25rem;
    font-weight: normal;
    padding: 0.5rem 1rem;
    line-height: 1.5;
    border: 1px solid transparent;
}

/* Table */
.admin-table-card {
    background: var(--sidan-surface);
    border-radius: var(--sidan-radius);
    overflow: hidden;
    box-shadow: var(--sidan-shadow-soft, 0 1px 2px rgba(0, 0, 0, .04));
}

/* Wrapper for the hierarchy view's <TreeView>. Same visual treatment as the
   table card so list/hierarchy feel like two faces of the same surface. */
.admin-tree-card {
    background: var(--sidan-surface);
    border-radius: var(--sidan-radius);
    box-shadow: var(--sidan-shadow-soft, 0 1px 2px rgba(0, 0, 0, .04));
    padding: 12px 16px;
}

.admin-tree-name {
    font-weight: 500;
    color: var(--sidan-text);
}

/* Selected node: the generic .tree-node-header.selected rule paints the
   primary background + ink color on .tree-node-name, but our inner span
   (.admin-tree-name) carries its own color: rule and would otherwise stay
   dark on the primary background. Pull it to the primary-ink so the label
   reads white. */
.tree-node-header.selected .admin-tree-name {
    color: var(--sidan-color-primary-ink);
}

.admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--sidan-text-md);
}

/* Content-sized variant: for tables whose columns carry short values (numbers,
   short labels), where the full page width would only spread slack across the
   cells instead of adding information. */
.admin-table-auto {
    width: auto;
}

.admin-table thead th {
    text-align: left;
    padding: 14px 18px;
    font-weight: 600;
    color: var(--sidan-text-muted);
    background: var(--sidan-surface-strong, rgba(0, 0, 0, .025));
    border-bottom: 1px solid var(--sidan-border, rgba(0, 0, 0, .08));
}

/* Horizontally centers a column — apply to BOTH the thead th and the matching
   tbody td so header and cell content line up. Used for short, glanceable
   columns (counts, X-marks) where left alignment would scatter the content. */
.admin-table th.admin-table-center,
.admin-table td.admin-table-center {
    text-align: center;
}

.admin-table thead th.admin-table-sortable {
    cursor: pointer;
    user-select: none;
}

.admin-table thead th.admin-table-sortable:hover {
    color: var(--sidan-text);
}

.admin-table-sort-icon {
    font-size: 18px;
    vertical-align: middle;
    margin-left: 4px;
    color: var(--sidan-color-primary);
}

.admin-table tbody td {
    padding: 14px 18px;
    border-bottom: 1px solid var(--sidan-border, rgba(0, 0, 0, .06));
    vertical-align: middle;
}

/* Top-aligned modifier — flip default vertical-align when the table mixes
   tall multi-line cells with single-line ones (e.g. a Settings stack next
   to a single timestamp). Without this, the short cells centre against the
   tall one and read as visually misaligned. */
.admin-table-rows-top tbody td {
    vertical-align: top;
}

.admin-table tbody tr:last-child td {
    border-bottom: none;
}

/* Zebra striping for plain data tables (e.g. the exercises overview). Sits
   before the .admin-table-row:hover rule so a hovered striped row still gets the
   primary hover tint. */
.admin-table-striped tbody tr:nth-child(even) td {
    background: var(--sidan-surface-sunk);
}

/* Compact trailing column for row actions (reorder arrows) — shrinks to content
   and right-aligns so the actions hug the table edge. */
.admin-table-actions-col {
    width: 1%;
    white-space: nowrap;
    text-align: right;
}

/* Blazor strips the whitespace between adjacent markup elements, so neighbouring
   row actions (up/down arrows, a trailing delete) would sit flush against each
   other — give every control after the first a gap of its own. Not scoped to
   .admin-icon-btn: the delete action is a .btn, so the gap has to hold for any
   mix of the two. */
.admin-table-actions-col > * + * {
    margin-left: 8px;
}

/* The row actions mix control heights (40px icon buttons next to a small
   button), and inline-level elements align on their BASELINE — which reads as
   one control sitting lower than the others. Centre them against each other so
   a row stays a single visual line whatever it carries. */
.admin-table-actions-col > .admin-icon-btn,
.admin-table-actions-col > .btn {
    vertical-align: middle;
}

/* Add-control above a panel's list (CLAUDE.md: the add control always sits
   ABOVE the list, at a constant position across all list states). */
.admin-panel-toolbar {
    display: flex;
    gap: 12px;
    margin-bottom: 16px;
}

.admin-table-row {
    cursor: pointer;
}

.admin-table-row:hover td {
    /* Match the active-nav-item background in the sidebar — both surfaces are
       interactive hover states, so they should share one tint that
       follows the customer's primary colour. */
    background: var(--sidan-color-primary-soft);
}

/* Multi-select column: a narrow leading column holding the row checkbox. Shrinks
   to content and centres the box; the cell stops the row click so ticking a row
   never navigates to its detail page (see EventsListPanel). */
.admin-table-select {
    width: 1%;
    white-space: nowrap;
    text-align: center;
    cursor: default;
}
.admin-table-select .form-check-input {
    margin: 0;
    vertical-align: middle;
    cursor: pointer;
}

/* Ticked rows carry the soft primary tint so the current selection stays
   visible while scrolling. Placed before .is-today / .is-selected so those
   stronger states still win when a row is both. */
.admin-table-row.is-checked td {
    background: var(--sidan-color-primary-soft);
}

/* Today's events: a light, on-brand tint flags the whole row without the alarm
   of the warning colour. Sits after :hover and before .is-selected so a hovered
   today-row stays flagged, but a selected row still wins the full primary fill
   (equal specificity → source order decides). */
.admin-table-row.is-today td {
    background: var(--sidan-color-primary-soft);
}

/* Currently-selected row (e.g. the customer the user just navigated back from).
   Full primary fill so it stands out clearly above the lighter hover tint. */
.admin-table-row.is-selected td,
.admin-table-row.is-selected:hover td {
    background: var(--sidan-color-primary);
    color: var(--sidan-color-primary-ink);
}
.admin-table-row.is-selected .admin-table-primary-text,
.admin-table-row.is-selected .admin-table-muted {
    color: var(--sidan-color-primary-ink);
}

.admin-table-primary {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    /* CSS custom prop set inline in the row carries depth — see CustomerRow. */
    padding-left: calc(var(--admin-indent, 0) * 18px);
}

.admin-table-indent {
    width: 8px;
    height: 1px;
    background: var(--sidan-border);
    flex: 0 0 auto;
}

.admin-table-primary-text {
    font-weight: 500;
    color: var(--sidan-text);
}

/* Secondary table cells used to dim themselves for visual hierarchy, but
   that made dates / values harder to read in dense rows. Cells now inherit
   the regular text color from .admin-table tbody td; the .admin-table-muted
   marker stays in the markup so the row-selected rule below can still pick
   the cells out and recolour them on the primary fill. */
.admin-table-muted { /* color intentionally not overridden */ }

/* Generic "this column matters more than the others" modifier — give a cell
   a heavier weight without changing its color. The UI font (Museo Sans) ships
   no face above 500, so request 700: browsers then synthesise a faux-bold from
   the 500 face, which reads as clearly bolder than the 300 body text. */
.admin-table-strong {
    font-weight: 700;
}

/* Generic small danger marker: a Material symbol that flags "something in here
   needs attention" next to a caption — section tab, list entry, table cell. It
   only carries colour and size; placement comes from the surrounding layout. */
.admin-icon-danger {
    color: var(--sidan-danger);
    font-size: var(--sidan-text-md);
    line-height: 1;
    /* Pulls the glyph onto the text baseline when it sits inline next to a
       caption; ignored where the marker is a flex item (e.g. .admin-tab). */
    vertical-align: -0.15em;
    /* Never glued to the caption it flags — the gap survives Razor's
       whitespace trimming between the marker and the following text. */
    margin-right: 6px;
}

/* Generic "this value is the problem" modifier for a single cell — the value
   itself carries the danger colour while the rest of the row stays neutral. */
.admin-table-danger-text {
    color: var(--sidan-danger);
    font-weight: 500;
}

/* Section row spanning a data table, naming the group the rows below belong to
   (e.g. the competence group in a target profile). Same treatment as the
   matrix' group row so both read as the same device. */
.admin-table-group td {
    background: var(--sidan-surface-sunk);
    font-weight: 700;
    color: var(--sidan-text-strong);
}

/* Target profile: two narrow numeric columns (min / max) beside the competence
   name. The table itself is content-sized (.admin-table-auto), so the columns
   only need to stay on one line. */
.admin-target-col {
    white-space: nowrap;
}
.admin-target-input {
    width: 7rem;
    text-align: center;
}
/* Activation switch between the intro and the target-profile table. */
.admin-target-toggle {
    margin: 4px 0 20px;
}

/* Secondary note right after a cell's main content (e.g. "(Standard)" behind a
   variant name): normal weight and muted, so it reads as an annotation rather
   than part of the name. */
.admin-table-affix {
    margin-left: 8px;
    font-weight: 400;
    color: var(--sidan-text-muted);
}

/* Several lists stacked in one panel, each its own group (e.g. the result
   variants grouped by type). The generous gap is what separates the groups —
   each one carries its own header row, so no extra rules are needed. */
.admin-list-groups {
    display: flex;
    flex-direction: column;
    gap: 32px;
}

/* Multi-line cell content — a vertical stack of items inside one table cell
   (e.g. the list of Setting names per project). Compact gap so the rows do
   not feel airy compared to single-line cells next to them. */
.admin-table-stack {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Empty state / loading */
.admin-state-placeholder,
.admin-state-empty {
    margin: 32px 0;
    padding: 32px;
    border-radius: var(--sidan-radius);
    background: var(--sidan-surface);
    color: var(--sidan-text-muted);
    text-align: center;
    font-size: var(--sidan-text-md);
}

/* Access-denied panel shown by AdminLayout when the signed-in user opens an
   area their roles do not grant (deep-link / bookmark). Calm, reduced — the
   sidebar still shows whatever the user IS allowed to open. */
.admin-forbidden {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 6px;
    padding: 72px 24px;
    color: var(--sidan-text-muted);
}

.admin-forbidden .material-symbols-outlined {
    font-size: 48px;
    color: var(--sidan-text-subtle);
}

.admin-forbidden-title {
    margin: 8px 0 0;
    font-size: var(--sidan-text-xl);
    color: var(--sidan-text);
}

.admin-forbidden-text {
    margin: 0;
}

/* Impersonation bar — full-width attention strip pinned at the top of the
   main pane while a SystemAdmin acts as another user. Uses the warning tone so
   it is impossible to overlook. */
.admin-impersonation-bar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 24px;
    background: var(--sidan-warning);
    color: var(--sidan-warning-ink);
    font-weight: 500;
}

.admin-impersonation-bar .material-symbols-outlined {
    font-size: 22px;
}

.admin-impersonation-end {
    margin-left: auto;
}

.admin-impersonation-end .btn {
    padding: 4px 14px;
    border: 1px solid var(--sidan-warning-ink);
    border-radius: var(--sidan-radius-pill);
    background: transparent;
    color: var(--sidan-warning-ink);
    font-size: var(--sidan-text-sm);
    cursor: pointer;
}

.admin-impersonation-end .btn:hover {
    background: var(--sidan-warning-ink);
    color: var(--sidan-warning);
}

/* ── ProjectNavigator ──────────────────────────────────────────────────
   Slide-out tree of everything inside a project. Lives on the project
   detail + setting detail pages. The edge-tab on the left of the editor
   area opens it; Strg+B / Cmd+B toggles via sidan2.js. The offcanvas is
   non-blocking (backdrop="false") so the editor stays clickable, and
   anchored just right of the sidebar so the global nav stays visible. */

.project-navigator-edge {
    /* Sticks to the left edge of the editor pane (the sidebar's right
       border), vertically centred on the viewport. Fixed positioning so
       it stays put as the editor scrolls, matching the offcanvas it
       opens. z-index sits below the offcanvas itself (Bootstrap uses
       1045) but above page content. */
    position: fixed;
    top: 50%;
    left: 240px;
    transform: translateY(-50%);
    z-index: 1040;
    width: 28px;
    height: 88px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--sidan-color-primary);
    color: #fff;
    border: none;
    border-radius: 0 var(--sidan-radius-sm) var(--sidan-radius-sm) 0;
    box-shadow: 2px 0 6px rgba(0, 0, 0, .15);
    cursor: pointer;
    transition: background .12s ease;
}

.project-navigator-edge:hover {
    background: var(--sidan-color-primary-hover);
}

.project-navigator-edge .material-symbols-outlined {
    font-size: 22px;
    line-height: 1;
}

/* Offcanvas placement: starts at the sidebar's right edge instead of the
   viewport's left edge — overriding Bootstrap's left:0 default. Width
   carries TWO ~310px columns side-by-side; on viewports that cannot fit
   that, min() clamps so a sliver of editor stays visible and the media
   query below switches to single-column drill mode.

   Animation: Bootstrap's default slides the entire panel from off-screen
   left, so during the animation the panel visibly streaks across the
   sidebar. Instead we override Bootstrap's translateX with a width
   transition — the left edge stays anchored at 240px and the panel
   "wipes out" rightward from the sidebar boundary. Bootstrap's class
   sequence (.show / .showing / .hiding) drives the state. */
.project-navigator-offcanvas.offcanvas-start {
    left: 240px;
    border-right: 1px solid var(--sidan-border);
    transform: none !important;
    overflow: hidden;
    width: 0;
    transition: width 0.3s ease;
}

.project-navigator-offcanvas.offcanvas-start.show,
.project-navigator-offcanvas.offcanvas-start.showing {
    width: min(900px, calc(100vw - 240px - 48px));
}

/* .hiding is added by Bootstrap during the close — explicit width:0
   here ensures the panel animates back to zero rather than snapping
   shut the moment .show is removed. */
.project-navigator-offcanvas.offcanvas-start.hiding {
    width: 0;
}

/* Mirror the sidebar brand block (.admin-sidebar-brand) — same top/bottom
   padding, top-aligned — so the project title and the header's bottom border
   sit at the same height as "SIDAN" and its divider on the left. */
.project-navigator-offcanvas .offcanvas-header {
    border-bottom: 1px solid var(--sidan-border);
    padding: 16px 18px 14px;
    align-items: flex-start;
}

.project-navigator-offcanvas .offcanvas-title {
    /* Same size AND line-height as the sidebar brand name
       (.admin-sidebar-brand-name) so the glyphs sit at the same top edge. */
    font-size: var(--sidan-text-3xl);
    font-weight: 600;
    line-height: 1.1;
}

/* Title + customer subtitle stack; takes the row's free width so the close
   button stays pinned right. min-width:0 enables the children's truncation. */
.project-navigator-heading {
    flex: 1 1 auto;
    min-width: 0;
}

/* The title doubles as the project context — collapse long project names to
   "…" rather than letting them wrap onto a second line and shove the close
   button down. min-width:0 is the standard flex-truncation trick. */
.project-navigator-title {
    min-width: 0;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Owning customer's name, muted, under the project title — mirrors the
   sidebar tagline (.admin-sidebar-brand-sub): same size, gap and line-height. */
.project-navigator-customer {
    margin-top: 8px;
    font-size: var(--sidan-text-md);
    line-height: 1.2;
    color: var(--sidan-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Columns ──────────────────────────────────────────────────────────
   Two equal columns side-by-side on wide displays. Each scrolls
   independently so a long Aufgaben list doesn't push the Setting list
   out of view (and vice versa). On narrow displays the columns stack
   into a single visible column at a time, with the .is-drilled class
   driving the slide between them. */
.project-navigator-columns {
    padding: 0;
    display: flex;
    align-items: stretch;
    overflow: hidden;
}

.project-navigator-col {
    flex: 1 1 50%;
    min-width: 0;
    overflow-y: auto;
    padding: 12px 10px;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.project-navigator-col + .project-navigator-col {
    border-left: 1px solid var(--sidan-border);
}

/* Header above the section list in column 2 — names the browsed setting
   so the operator never loses track of which content they are looking at,
   even when column 1 is hidden behind them on narrow displays. */
.project-navigator-col-header {
    padding: 2px 10px 8px;
    font-size: var(--sidan-text-md);
    font-weight: 600;
    color: var(--sidan-text);
    border-bottom: 1px solid var(--sidan-border);
    margin-bottom: 6px;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

/* Single navigable row — used for Stamm, every Setting in column 1, and
   for each sub-item (Aufgabe / Kompetenz) in column 2. Buttons and links
   share the row chrome so the visual rhythm is identical regardless of
   what HTML element backs the entry. */
.project-navigator-entry {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: none;
    background: transparent;
    border-radius: var(--sidan-radius-sm);
    color: var(--sidan-text);
    font-size: var(--sidan-text-md);
    text-decoration: none;
    text-align: left;
    line-height: 1.3;
    overflow-wrap: anywhere;
    cursor: pointer;
}

.project-navigator-entry:hover {
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text);
}

/* Active = the URL is on this entry's destination. Strong primary tone. */
.project-navigator-entry.is-active {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
    font-weight: 500;
}

/* Browsed = the operator selected this setting in column 1 to preview its
   content in column 2, but has not navigated to it yet. Muted background
   so it reads as "in focus" without competing with the active highlight. */
.project-navigator-entry.is-browsed {
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text);
}

.project-navigator-entry .material-symbols-outlined {
    font-size: var(--sidan-text-lg);
    opacity: 0.7;
}

.project-navigator-entry.is-active .material-symbols-outlined {
    opacity: 1;
}

/* Indented row used for the individual exercises / competences listed
   under a section header in column 2 — visually subordinate to their
   parent section. */
.project-navigator-entry-sub {
    padding-left: 22px;
}

/* Section heading — wraps a clickable label that navigates to a tab
   (e.g. "Settings", "Aufgaben"). Uppercase + muted reads as a heading;
   it still hovers + carries an active state when the operator is on
   that tab. */
.project-navigator-section {
    padding-top: 8px;
}

.project-navigator-section-link {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border-radius: var(--sidan-radius-sm);
    font-size: var(--sidan-text-md);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--sidan-text-subtle);
    font-weight: 600;
    text-decoration: none;
}

.project-navigator-section-link:hover {
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text-muted);
}

.project-navigator-section-link.is-active {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
}

.project-navigator-section-link .material-symbols-outlined {
    font-size: var(--sidan-text-md);
    opacity: 0.7;
}

.project-navigator-section-link.is-active .material-symbols-outlined {
    opacity: 1;
}

/* Counter chip after a section name (e.g. "Aufgaben 7"). Uses tabular
   numbers so a list with mixed counts lines up cleanly. */
.project-navigator-count {
    margin-left: auto;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-subtle);
    font-variant-numeric: tabular-nums;
}

.project-navigator-section-link.is-active .project-navigator-count {
    color: var(--sidan-color-primary);
}

/* Hint shown in column 2 before the operator has picked a setting yet
   in column 1 — keeps the empty column from looking broken. */
.project-navigator-hint {
    padding: 12px 10px;
    color: var(--sidan-text-subtle);
    font-size: var(--sidan-text-sm);
    font-style: italic;
}

.project-navigator-empty {
    padding: 8px 10px;
    color: var(--sidan-text-subtle);
    font-size: var(--sidan-text-sm);
    font-style: italic;
}

/* "Zurück"-button at the top of column 2 — slides back to column 1 on
   narrow displays. Hidden on wide displays where both columns are visible
   side-by-side; the media query below flips it on. */
.project-navigator-back {
    display: none;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    margin: -4px -4px 8px;
    background: transparent;
    border: none;
    border-radius: var(--sidan-radius-sm);
    color: var(--sidan-color-primary);
    font-size: var(--sidan-text-sm);
    font-weight: 500;
    cursor: pointer;
    align-self: flex-start;
}

.project-navigator-back:hover {
    background: var(--sidan-color-primary-soft);
}

.project-navigator-back .material-symbols-outlined {
    font-size: var(--sidan-text-md);
}

/* ── Narrow-display drill mode ────────────────────────────────────────
   Below the breakpoint the offcanvas covers almost the full viewport and
   only one column shows at a time. The .is-drilled class on the column
   container swaps which one (default = col 1, drilled = col 2) by
   sliding the inner flex strip with translateX. CSS only — no JS-side
   layout switching. */
@media (max-width: 900px) {
    .project-navigator-offcanvas.offcanvas-start {
        left: 0;
    }

    .project-navigator-offcanvas.offcanvas-start.show,
    .project-navigator-offcanvas.offcanvas-start.showing {
        width: min(620px, 100vw);
    }

    .project-navigator-columns {
        position: relative;
        overflow: hidden;
    }

    /* Each column takes 100% of the visible area; the strip is 200% wide
       so both fit side-by-side and we translate to show one or the other. */
    .project-navigator-col {
        flex: 0 0 100%;
        width: 100%;
        transition: transform .18s ease;
    }

    .project-navigator-col + .project-navigator-col {
        border-left: none;
    }

    .project-navigator-columns:not(.is-drilled) > .project-navigator-col {
        transform: translateX(0);
    }

    .project-navigator-columns.is-drilled > .project-navigator-col {
        transform: translateX(-100%);
    }

    .project-navigator-back {
        display: inline-flex;
    }
}

/* ── ProjectOverviewPanel ─────────────────────────────────────────────
   Read-only landing panel of a project: one card per Setting in a
   responsive auto-fill grid, each surfacing its Aufgaben + Kompetenzen
   as clickable links. Every label is a link — the panel is "read-only"
   in the sense that no edit controls live here, but every entry drops
   the operator straight into the relevant editor. */
/* Toolbar above the cards — right-aligned "Neues Setting" CTA. */
.project-overview-toolbar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 16px;
}

/* Settings overview accordion. The Setting name is the one link in the header
   (primary colour); clicking it opens the Setting instead of toggling the item. */
.settings-name {
    color: var(--sidan-color-primary);
    text-decoration: none;
}

.settings-name:hover {
    color: var(--sidan-color-primary-hover);
    text-decoration: underline;
}

/* Supported languages as a row of round flags. */
.settings-flags {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
}

/* Expanded body: the Aufgaben as a striped table (.admin-table admin-table-striped),
   each name a link that opens the exercise directly. */
.settings-exercises-empty {
    margin: 0;
    color: var(--sidan-text-subtle);
    font-style: italic;
}

/* Generic admin accordion: a single bordered container whose items are separated
   only by a divider line (no gaps / per-item frames). Each item has a header
   (chevron + title + count) plus optional actions on the right; the body holds
   the item's content. Shared by the competence-groups overview and the project
   settings overview. */
.admin-accordion {
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-md);
    overflow: hidden;
}

.admin-accordion-item:not(:last-child) {
    border-bottom: 1px solid var(--sidan-border);
}

.admin-accordion-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 4px 12px 4px 4px;
    background: var(--sidan-surface);
}

/* The chevron + title + count form one big toggle target; the action controls
   sit outside it so they never collapse the section. */
.admin-accordion-toggle {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 8px;
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    color: var(--sidan-text);
}

.admin-accordion-chevron {
    flex: 0 0 auto;
    color: var(--sidan-text-muted);
}

.admin-accordion-title {
    font-weight: 700;
    font-size: var(--sidan-text-md);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Count of items in the section, rendered as a small neutral pill badge. */
.admin-accordion-count {
    flex: 0 0 auto;
    min-width: 24px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-sm);
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.admin-accordion-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    flex: 0 0 auto;
}

.admin-accordion-body {
    /* Left pad so the body content lines up with the header TITLE, not the item
       edge: title offset is head-pad(4) + toggle-pad(8) + chevron(24) + gap(10)
       = 46px; the body's tables add 18px cell padding of their own, so 46 - 18
       = 28px here puts the first column flush under the title. */
    padding: 8px 12px 8px 28px;
}

/* Keep every accordion-body table row a uniform height, whether or not the row
   carries reorder buttons. The 40px icon buttons would otherwise make button
   rows taller than plain ones (and taller than the sibling settings/exercises
   table). Trimming the action column's vertical padding keeps the button cell
   shorter than the text cell, so the row height stays governed by the text
   column — identical for every row and across both accordions. */
.admin-accordion-body .admin-table td.admin-table-actions-col {
    padding-top: 4px;
    padding-bottom: 4px;
}

.competence-group-empty {
    color: var(--sidan-text-muted);
    margin: 12px 4px;
}

/* Tabs */
/* Content that sits between the page header and the tab bar and belongs to the
   whole page rather than to one tab (e.g. a caption field). Capped to form
   width so it does not read as a full-width section of its own. */
.admin-page-lead {
    max-width: 560px;
    margin-bottom: 20px;
}

/* An input with a short note beside it ("(Standard)"), both on one line and
   centred against each other. The control takes the remaining width. */
.admin-field-with-affix {
    display: flex;
    align-items: center;
    gap: 12px;
}
.admin-field-with-affix > :first-child {
    flex: 1 1 auto;
    min-width: 0;
}
.admin-field-affix {
    flex: 0 0 auto;
    color: var(--sidan-text-muted);
    white-space: nowrap;
}

.admin-tabs {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid var(--sidan-border);
    /* No top margin — the spacing to whatever is above (.admin-page-header)
       comes from that element's margin-bottom alone, so the page-header →
       content rhythm stays consistent across every admin page. */
    margin: 0 0 16px;
}

.admin-tab {
    /* inline-flex so a trailing marker (e.g. the danger icon of a section that
       needs attention) sits vertically centred beside the caption. */
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 12px 18px;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
    cursor: pointer;
    transition: color .15s, border-color .15s;
}

.admin-tab:hover {
    color: var(--sidan-text);
}

/* Inside a tab the marker trails the caption and the flex gap already spaces
   it, so the inline margin of .admin-icon-danger is not needed here. */
.admin-tab .admin-icon-danger {
    margin-right: 0;
}

.admin-tab.is-active {
    color: var(--sidan-color-primary);
    border-bottom-color: var(--sidan-color-primary);
    font-weight: 500;
}

/* Tab that carries an icon (the timetable editor's "Eckdaten") — lay out as
   inline-flex so icon and label sit on the vertical centre instead of the
   text baseline. */
.tt-day-frame {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.admin-tab-panel {
    background: var(--sidan-surface);
    border-radius: var(--sidan-radius);
    padding: 28px;
    box-shadow: var(--sidan-shadow-soft, 0 1px 2px rgba(0, 0, 0, .04));
}

/* Forms */
.admin-form-card {
    background: var(--sidan-surface);
    border-radius: var(--sidan-radius);
    padding: 28px;
    box-shadow: var(--sidan-shadow-soft, 0 1px 2px rgba(0, 0, 0, .04));
    margin-top: 24px;
}

/* 12-column form grid. Default field width is half the row (span 6); the
   wider / narrower variants below let a single panel mix half-, third- and
   quarter-width fields under one grid (e.g. PLZ + Ort + Land as 3/6/3). */
.admin-form-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 18px 24px;
}

.admin-form-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    grid-column: span 6;
}

.admin-form-field-wide {
    grid-column: 1 / -1;
}

/* Stretches a half-width field over two rows of the grid — used on the
   project Stamm tab so the Customer dropdown sits beside Name + Description
   instead of dropping to its own row beneath them. */
.admin-form-field-tall {
    grid-row: span 2;
}

.admin-form-field-third {
    grid-column: span 4;
}

.admin-form-field-quarter {
    grid-column: span 3;
}

.admin-form-field-sixth {
    grid-column: span 2;
}

.admin-form-field-narrow {
    max-width: 220px;
}

.admin-form-field-color .form-control-color {
    width: 56px;
    padding: 4px;
}

/* Per-day event schedule (EventScheduleEditor): each row is one day with its own
   date + start time. A single-day event drops the day-label column and stacks
   just date + time; a multi-day (timetabled) event prefixes each row with the
   day's name. Column headers ("Datum" / "Beginn") sit once above the rows. */
.event-schedule {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.event-schedule-head,
.event-schedule-row {
    display: grid;
    grid-template-columns: 220px 160px;
    gap: 12px;
    align-items: center;
}

.event-schedule-head.is-multi-day,
.event-schedule-row.is-multi-day {
    grid-template-columns: minmax(140px, 1fr) 220px 160px;
}

.event-schedule-head {
    color: var(--sidan-text-muted);
}

.event-schedule-col-day {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Native date/time inputs keep the grid track width instead of stretching. */
.event-schedule-row .event-schedule-col-date,
.event-schedule-row .event-schedule-col-time {
    width: 100%;
}

/* Horizontal row of compact form fields, each only as wide as its content
   (e.g. the rating scale's "Wertebereich" input-group beside the BOPs).
   Wraps onto further lines when the viewport is too narrow. */
.admin-form-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 18px 24px;
    margin-bottom: 24px;
}

/* Equal-width columns for fields that belong side by side (e.g. the per-audience
   exercise instructions, or an anchor's positive/negative phrasings). Reflows on
   the CONTAINER's width, not the viewport: as many columns as fit while each
   stays >= 280px wide, so the editors sit next to each other on desktop and drop
   to fewer columns only when the pane is genuinely too narrow (tablet, split
   view). Column count follows the child count — works for two, three or more. */
.admin-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 24px;
}

/* Each child takes exactly one track here — without this reset a nested
   .admin-form-field would keep its 12-track "grid-column: span 6" and so claim
   half the row, forcing a single item per line (i.e. stacked instead of side by
   side). */
.admin-columns > * {
    grid-column: auto;
}

/* Vertical stack of form fields with the same row rhythm as .admin-form-grid
   (18px). Use it when fields are grouped in a single column — e.g. one side of
   an .admin-columns split — where the grid's own row gap doesn't apply because
   the fields aren't direct grid children. */
.admin-form-stack {
    display: flex;
    flex-direction: column;
    gap: 18px;
}

/* Min/max/polarity bundled into one input-group: hug the content instead of
   stretching the row, keep the number inputs compact (1–2 digits + spinner),
   let the polarity dropdown size to its options. */
.admin-form-row .admin-scale-controls {
    flex-wrap: nowrap;
    width: auto;
}

/* List of target profiles the current scale invalidates — sits between the
   scale controls and the per-value caption table, and only exists while there
   is something to fix. A single-item .admin-accordion given a danger skin: a
   status-coloured frame plus a soft-danger header keep the collapsible header
   and its body as one clearly-bound unit (a status border is the sanctioned
   exception to borderless surfaces). */
.admin-scale-conflicts {
    margin-bottom: 24px;
    border-color: var(--sidan-danger);
}

.admin-scale-conflicts .admin-accordion-head {
    background: var(--sidan-danger-soft);
}

.admin-scale-conflicts-title {
    color: var(--sidan-danger);
}

.admin-scale-controls .form-control {
    flex: 0 0 auto;
    width: 7rem;
}

.admin-scale-controls .form-select {
    flex: 0 0 auto;
    width: auto;
}

/* BOP toggle buttons: centre the value within each (equal-width) cell —
   .btn defaults to flex-start, which left-aligns the digit when the
   button-group stretches the buttons. */
.admin-bop-group .btn {
    justify-content: center;
    min-width: 3rem;
}

.admin-form-actions {
    margin-top: 24px;
    display: flex;
    justify-content: flex-end;
}

.admin-form-actions-start {
    justify-content: flex-start;
}

/* "Add" control that sits ABOVE a list (CLAUDE.md convention). Unlike
   .admin-form-actions (a form's submit footer, spaced at the top), this one
   needs no top gap and instead separates itself from the list below. */
.admin-list-add {
    display: flex;
    justify-content: flex-start;
    margin-bottom: 16px;
}

/* "In Vorbereitung" stub button — visible, disabled, hint via title attribute.
   Used wherever a planned admin action exists but its backing infrastructure
   (uploads, email send, exports, login-code regeneration, …) is not yet
   implemented. Visual treatment is muted so it does not compete with active
   actions but stays discoverable. */
.admin-btn-stub {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    opacity: 0.75;
    cursor: not-allowed;
    position: relative;
}

.admin-btn-stub:hover {
    background: var(--sidan-surface-sunk);
}

.admin-btn-stub-badge {
    margin-left: 4px;
    color: var(--sidan-warning);
    font-size: 0.7em;
    line-height: 1;
}

/* Inline action group — used in event participant rows and similar. */
.admin-row-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    margin-left: auto;
}

/* Compact icon-only button used for inline trash / login-code actions */
.admin-icon-btn {
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-sm);
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-text-muted);
    cursor: pointer;
    transition: color .12s, background .12s, border-color .12s;
    padding: 0;
    text-decoration: none;
}

.admin-icon-btn:hover {
    background: rgba(var(--sidan-color-primary-rgb), 0.16);
    color: var(--sidan-color-primary);
}

.admin-icon-btn.is-danger:hover {
    color: var(--sidan-danger, #b32424);
    border-color: var(--sidan-danger-border, #f3c0c0);
    background: var(--sidan-danger-bg, #fdecec);
}

.admin-icon-btn:disabled,
.admin-icon-btn.is-stub {
    opacity: 0.6;
    cursor: not-allowed;
}

/* Progress bar for long-running background work (e.g. the sample-solution
   import). Deliberately not Bootstrap's .progress: the fill has to follow the
   customer's primary colour, and the track the app's sunk surface. */
.admin-progress {
    height: 10px;
    border-radius: 999px;
    background: var(--sidan-surface-sunk);
    overflow: hidden;
}

.admin-progress-bar {
    height: 100%;
    width: 0;
    border-radius: inherit;
    background: var(--sidan-color-primary);
    transition: width .3s ease;
}

/* Queued, no unit count yet — a slice sliding along the track so the operator
   sees the job is alive without implying a percentage. */
.admin-progress-bar.is-indeterminate {
    width: 35%;
    animation: admin-progress-slide 1.4s ease-in-out infinite;
}

@keyframes admin-progress-slide {
    0%   { margin-left: -35%; }
    100% { margin-left: 100%; }
}

.admin-progress-label {
    margin: 8px 0 0;
    color: var(--sidan-text-muted);
}

/* Centered status block for result/confirmation modals (e.g. welcome-mail
   send): a large state icon over a short message. Success / failure tint the
   icon via the central status variables; the pending state stays muted. */
.admin-status-result {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 12px;
    padding: 12px 0;
}
.admin-status-result .material-symbols-outlined { font-size: 56px; line-height: 1; }
.admin-status-result.is-success .material-symbols-outlined { color: var(--sidan-success); }
.admin-status-result.is-error .material-symbols-outlined { color: var(--sidan-danger, #b32424); }
.admin-status-result p { margin: 0; }

.admin-delete-dependents {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.admin-delete-dependents li {
    background: var(--sidan-surface-sunk);
    padding: 8px 12px;
    border-radius: var(--sidan-radius-sm);
    font-size: var(--sidan-text-md);
}

/* Step-progress checklist used in long-running modal dialogs (e.g. the
   project-duplication wizard while files are being copied). Three states
   per row: pending (subtle empty circle), active (spinner in the customer's
   primary tone) and done (filled checkmark). Tone follows the brand palette
   so every customer sees their colours during the busy state. */
.admin-checklist {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.admin-checklist-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px;
    background: var(--sidan-surface-sunk);
    border-radius: var(--sidan-radius-sm);
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
}

.admin-checklist-item.is-active { color: var(--sidan-text); }
.admin-checklist-item.is-done   { color: var(--sidan-text); }

.admin-checklist-icon {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    line-height: 1;
}

.admin-checklist-icon.is-pending { color: var(--sidan-text-subtle); }
.admin-checklist-icon.is-active  { color: var(--sidan-color-primary); }
.admin-checklist-icon.is-done    { color: var(--sidan-color-primary); }

.admin-checklist-label {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    flex: 1 1 auto;
}

/* Secondary counter shown next to the active step (e.g. "127 von 2683
   Dateien"). Quieter so the step name stays the primary cue. */
.admin-checklist-progress {
    color: var(--sidan-text-muted);
    font-variant-numeric: tabular-nums;
    font-size: var(--sidan-text-sm);
}

/* Simple vertical stack of editable rows (security IP-blocks panel). */
.admin-participants-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* Compact inline-edit table — one editable record per row, inputs filling their
   cell and tightened row padding so each entry sits on one slim line. Shared by
   the event Candidates/Observers/Roleplayers tables and the Rooms table. */
.admin-edit-table tbody td {
    padding-top: 8px;
    padding-bottom: 8px;
}

.admin-edit-table .form-control,
.admin-edit-table .form-select {
    width: 100%;
}

/* Running number column — narrow, muted, centered. */
.admin-edit-col-no {
    width: 1%;
    white-space: nowrap;
    text-align: center;
    color: var(--sidan-text-subtle);
}

/* Moderator / passive toggle columns — narrow, switch centered in the cell. */
.admin-edit-col-flag {
    width: 1%;
    white-space: nowrap;
    text-align: center;
}

.admin-edit-col-flag .admin-switch {
    justify-content: center;
}

/* Short-tag (Kürzel) column — only needs room for ~3 characters. */
.admin-edit-col-short {
    width: 90px;
}

/* Move-up and avatar columns — narrow, centered. */
.admin-edit-col-move,
.admin-edit-col-avatar {
    width: 1%;
    white-space: nowrap;
    text-align: center;
}

/* "Move up one place" button — reorders the row above/below (replaces drag). */
.admin-move-up .material-symbols-outlined {
    font-size: 1.35rem;
}

/* Position-swap feedback: the two rows that just exchanged places slide into
   their new spots (the lower one up, the upper one down) with a brief tint, so
   the swap is visible even though the DOM reorders instantly. translateY(100%)
   ≈ one row height for adjacent rows. */
@keyframes participant-swap-up {
    from { transform: translateY(100%); background: var(--sidan-color-primary-soft); }
    to   { transform: translateY(0); }
}
@keyframes participant-swap-down {
    from { transform: translateY(-100%); background: var(--sidan-color-primary-soft); }
    to   { transform: translateY(0); }
}
.admin-edit-table tbody tr.is-swap-up {
    animation: participant-swap-up .3s ease;
}
.admin-edit-table tbody tr.is-swap-down {
    animation: participant-swap-down .3s ease;
}

/* Compact action icon whose backend is not yet wired: muted, non-interactive. */
.admin-icon-btn--stub {
    color: var(--sidan-text-subtle);
    cursor: not-allowed;
    opacity: 0.6;
}

/* Action icon in an "active" state (e.g. a note has been entered). */
.admin-icon-btn.is-active {
    color: var(--sidan-color-primary);
}

/* ── Participant avatar ────────────────────────────────────────────────
   A round avatar chip (photo / chosen icon / initials on a brand-coloured
   circle), used inline in the people table and as the preview in the picker
   dialog; plus the dialog's icon grid and upload row. */

/* The chip itself — a filled brand-coloured circle by default, overlaid by the
   photo when one is set. Two sizes: sm inline in the table, lg in the dialog. */
.avatar-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    overflow: hidden;
    background: var(--sidan-color-primary);
    color: var(--sidan-text-on-primary);
    flex: 0 0 auto;
    line-height: 1;
}
.avatar-chip--sm {
    width: 40px;
    height: 40px;
}
.avatar-chip--lg {
    width: 128px;
    height: 128px;
}
.avatar-chip-photo {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.avatar-chip .material-symbols-outlined {
    color: var(--sidan-text-on-primary);
}
.avatar-chip--sm .material-symbols-outlined {
    font-size: 1.6rem;
}
.avatar-chip--lg .material-symbols-outlined {
    font-size: 4.5rem;
}
.avatar-chip-initials {
    font-weight: 600;
    letter-spacing: 0.02em;
}
.avatar-chip--sm .avatar-chip-initials {
    font-size: var(--sidan-text-base);
}
.avatar-chip--lg .avatar-chip-initials {
    font-size: 3rem;
}

/* Table avatar button: a borderless click target wrapping the chip. */
.admin-avatar-btn {
    display: inline-flex;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    border-radius: 50%;
    transition: box-shadow .12s;
}
.admin-avatar-btn:hover {
    box-shadow: 0 0 0 3px var(--sidan-color-primary-soft);
}

/* Dialog: centred preview of the current avatar. */
.admin-avatar-preview {
    display: flex;
    justify-content: center;
    margin-bottom: 24px;
}

/* Dialog: section labels above the icon grid / the upload control. */
.admin-avatar-section-label {
    text-align: center;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text);
    margin-bottom: 16px;
}
.admin-avatar-section-label--secondary {
    margin-top: 28px;
}

/* Dialog: the selectable icon grid. */
.admin-avatar-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 12px;
}
.admin-avatar-option {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1;
    border: 2px solid transparent;
    border-radius: 50%;
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text-muted);
    cursor: pointer;
    transition: color .12s, background .12s, border-color .12s;
}
.admin-avatar-option .material-symbols-outlined {
    font-size: 2rem;
}
.admin-avatar-option:hover {
    border-color: var(--sidan-color-primary);
    color: var(--sidan-text);
}
.admin-avatar-option.is-selected {
    border-color: var(--sidan-color-primary);
    color: var(--sidan-color-primary);
    background: var(--sidan-color-primary-soft);
}

/* Dialog: the photo upload button. */
.admin-avatar-upload-row {
    display: flex;
    align-items: center;
    gap: 18px;
    margin-bottom: 6px;
}
/* A little more breathing room between the upload row and its hint. */
.admin-avatar-upload-row + .admin-form-hint {
    margin-top: 14px;
}

/* Dialog: the remove control sits on its own separated line below both choices,
   making clear it clears whichever avatar is currently set (photo or icon). */
.admin-avatar-remove-row {
    display: flex;
    justify-content: center;
    margin-top: 28px;
    padding-top: 24px;
    border-top: 1px solid var(--sidan-border);
}
.admin-avatar-remove-row .admin-btn-danger {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* Secondary explanatory line under a form control (e.g. in a dialog). */
.admin-form-hint {
    margin-top: 8px;
    color: var(--sidan-text-subtle);
}

/* Login code shown AS a button: the monospace code plus a subtle regenerate
   glyph. Clicking it opens the regenerate-confirmation dialog — the code itself
   is never editable by keyboard, so a button reads more honestly than an input. */
.admin-login-code-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
.admin-login-code-value {
    font-family: var(--sidan-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
    letter-spacing: 0.04em;
}
.admin-login-code-btn .material-symbols-outlined {
    font-size: 1.25rem;
    opacity: 0.75;
}

/* Fixed group label shown in place of the dropdown when there is only one group. */
.admin-group-fixed {
    display: inline-block;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text);
}

/* Table card whose rows contain pop-over menus (e.g. the language flag picker).
   The default .admin-table-card clips overflow for its rounded corners, which
   would cut the menu off — allow overflow here and round the header cells instead
   so the corners still match the card. */
.admin-table-card--menus {
    overflow: visible;
}

.admin-table-card--menus .admin-table thead th:first-child {
    border-top-left-radius: var(--sidan-radius);
}

.admin-table-card--menus .admin-table thead th:last-child {
    border-top-right-radius: var(--sidan-radius);
}

/* Flag-based language picker (LanguageFlagPicker.razor). Compact trigger showing
   the selected flag(s); the drop-down reuses the .content-lang-menu styling. */
.lang-flag-picker {
    position: relative;
    display: inline-block;
}

.lang-flag-trigger {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 48px;
    padding: 4px 10px;
    background: var(--sidan-surface);
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-sm);
    cursor: pointer;
    color: var(--sidan-text);
}

.lang-flag-trigger:hover {
    border-color: var(--sidan-color-primary);
}

.lang-flag-trigger:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.lang-flag-trigger.is-empty .lang-flag-empty {
    color: var(--sidan-text-subtle);
}

.lang-flag-caret {
    font-size: 20px;
    color: var(--sidan-text-subtle);
}

/* Anchor the picker's menu to its left edge (the shared rule anchors right). */
.lang-flag-picker .content-lang-menu {
    right: auto;
    left: 0;
}

.admin-tab-section {
    margin: 20px 0 8px;
    font-size: var(--sidan-text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--sidan-text-subtle);
    font-weight: 600;
}

/* Result matrix grid */
/* Exercise × competence grid (SettingMatrixTable): competences in rows
   (grouped), exercises in columns. The table sizes to its content — a full-width
   table would only spread slack across the cells — and scrolls horizontally
   inside its wrapper when a setting has many exercises. */
.admin-matrix-scroll {
    overflow-x: auto;
}
.admin-matrix {
    width: auto;
    border-collapse: collapse;
    font-size: var(--sidan-text-md);
}
.admin-matrix th,
.admin-matrix td {
    border-bottom: 1px solid var(--sidan-border);
    padding: 10px 12px;
    text-align: left;
}
.admin-matrix th {
    color: var(--sidan-text-muted);
    font-weight: 600;
}
/* No percentage widths anywhere in this table: in an auto-width table a
   percentage column width is only satisfiable by growing the table, which
   pushes it to the full container width — the exact opposite of the intent.
   Content sizing plus tight padding keeps the columns narrow. */
.admin-matrix td.is-checkable {
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    user-select: none;
    padding-left: 6px;
    padding-right: 6px;
}
.admin-matrix td.is-checkable:hover { background: var(--sidan-surface-sunk); }

/* The unit in the corner cell (e.g. "%") is enlarged and set in the primary
   colour so it reads as the grid's heading rather than a stray glyph; the cell
   centres it both ways. */
.admin-matrix thead th:first-child {
    text-align: center;
    vertical-align: middle;
}
.admin-matrix-corner-unit {
    font-size: calc(var(--sidan-text-md) * 2);
    line-height: 1;
    color: var(--sidan-color-primary);
}

/* Exercise headers read bottom-to-top so long names fit narrow columns. */
.admin-matrix-col-head {
    vertical-align: bottom;
    padding: 12px 6px;
}
/* Block + auto margins centre the rotated label reliably over the disc
   below — text-align does not centre a vertical-writing inline-block. */
.admin-matrix-col-head span {
    display: block;
    width: fit-content;
    margin-left: auto;
    margin-right: auto;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    white-space: nowrap;
    color: var(--sidan-text-strong);
    font-weight: normal;
}
/* Lead column header (e.g. "Abschlusskonferenz") is set apart in the primary
   colour so it reads as a distinct concept next to the exercise heads. */
.admin-matrix-col-head.is-lead span {
    color: var(--sidan-color-primary);
    font-weight: 600;
}

.admin-matrix-row-head {
    white-space: nowrap;
}

/* Competence-group section row spanning the whole width. */
.admin-matrix-group td {
    background: var(--sidan-surface-sunk);
    font-weight: 700;
    color: var(--sidan-text-strong);
}

/* Weighting variant of the matrix: each rated cell holds a small number input
   (percent, unit shown once in the corner cell), unrated cells stay empty and
   are greyed out so the editable area is obvious at a glance. */
.admin-matrix-weight-cell {
    text-align: center;
    vertical-align: middle;
    padding-left: 6px;
    padding-right: 6px;
}
.admin-matrix-weight-cell.is-unrated {
    background: var(--sidan-surface-sunk);
}
.admin-matrix-weight {
    width: 5.5rem;
    margin: 0 auto;
    text-align: center;
}
/* Deviates from the default weight — outlined in the warning colour so the
   exceptions are visible at a glance across the whole grid. */
.admin-matrix-weight.is-adjusted {
    border-color: var(--sidan-warning);
    color: var(--sidan-warning);
    font-weight: 600;
}

/* Final-conference visibility toggle: an eye icon per competence — open (visible)
   in the primary colour, struck-through (hidden) in a light grey. */
.admin-matrix-eye {
    font-size: 1.75rem;
    vertical-align: middle;
    cursor: pointer;
}
.admin-matrix-eye.is-on {
    color: var(--sidan-color-primary);
}
.admin-matrix-eye.is-off {
    color: var(--sidan-border);
}

/* Matrix bubble: one fixed-diameter CSS circle drives every cell state, so the
   rated check and the empty ring are always identical in size. The glyph
   inside never sets the dimensions — only the circle does.
   - .is-empty : muted ring, not rated.
   - .is-filled: primary disc holding a white check glyph. */
.admin-matrix-bubble {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Centre on the cell's line box regardless of inner content — without this
       the empty (text-less) bubble aligns to the baseline and rides too high. */
    vertical-align: middle;
    width: 3rem;
    height: 3rem;
    box-sizing: border-box;
    border-radius: 50%;
    line-height: 1;
    font-weight: 600;
    font-size: var(--sidan-text-sm);
}
.admin-matrix-bubble.is-empty {
    border: 2px solid var(--sidan-border);
}
.admin-matrix-bubble.is-filled {
    background: var(--sidan-color-primary);
    color: var(--sidan-color-primary-ink);
}
.admin-matrix-bubble .material-symbols-outlined {
    font-size: 1.75rem;
    color: inherit;
    font-variation-settings: 'FILL' 1;
}

/* Activity log */
.admin-activity-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--sidan-text-md);
}
.admin-activity-table th,
.admin-activity-table td {
    border-bottom: 1px solid var(--sidan-border);
    padding: 10px 12px;
    text-align: left;
}
.admin-activity-table th {
    color: var(--sidan-text-muted);
    font-weight: 600;
}

/* Security → IP-Sperren: the "add" bar and the existing-entry rows. This panel
   has its own layout rather than the event participant grid — two fields on top
   with the action button on its own full-width line below, left-aligned (add +
   delete are text buttons, not right-pinned icons). */
.admin-ip-add-row,
.admin-ip-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 12px 24px;
    align-items: end;
}

.admin-ip-add-row {
    margin-bottom: 24px;
    border-bottom: 1px solid var(--sidan-border);
    padding-bottom: 24px;
}

.admin-ip-row {
    padding: 12px 14px;
    background: var(--sidan-surface-sunk);
    border-radius: var(--sidan-radius-md);
}

.admin-ip-add-row .form-control,
.admin-ip-row .form-control {
    width: 100%;
}

/* .admin-form-field carries grid-column: span 6 from the 12-track .admin-form-grid;
   reset it so each field takes a single track of this 2-column IP grid (otherwise
   both fields span 6 and wrap underneath each other). */
.admin-ip-add-row .admin-form-field,
.admin-ip-row .admin-form-field {
    grid-column: auto;
}

/* Action button drops below the fields on its own line, flush left. */
.admin-ip-actions {
    grid-column: 1 / -1;
    display: flex;
    justify-content: flex-start;
}

@media (max-width: 880px) {
    .admin-ip-add-row,
    .admin-ip-row { grid-template-columns: minmax(0, 1fr); }
}

.admin-ip-range {
    font-family: var(--sidan-font-mono, ui-monospace, Menlo, monospace);
    letter-spacing: .02em;
}

/* Read-only descriptor shown in place of the editable note for automatic
   blocks: the "Automatisch" pill next to the human-readable reason. Sits at the
   same vertical rhythm as the note input it replaces. */
.admin-ip-auto-note {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: var(--sidan-input-height-lg, 3rem);
    color: var(--sidan-text-muted, #6c757d);
}

/* Settings table: label / value rows grouped under spanning subheading rows.
   Generic — for any panel that lists inherit-or-override settings. */
.admin-settings-table {
    width: 100%;
    border-collapse: collapse;
}

.admin-settings-table th,
.admin-settings-table td {
    padding: 12px 16px;
    text-align: left;
    vertical-align: middle;
}

.admin-settings-table tr.admin-settings-row {
    border-bottom: 1px solid var(--sidan-border);
}

/* Zebra striping. Only data rows carry .admin-settings-row, so subheading and
   hint rows are never tinted even though they share the :nth-child sequence. */
.admin-settings-table tr.admin-settings-row:nth-child(even) {
    background: var(--sidan-surface-sunk);
}

/* Time/length inputs are single small integers — a full-width control reads as
   if it expects far more, so cap it to a compact field. */
.admin-settings-table .admin-input-narrow {
    width: 96px;
    flex: 0 0 auto;
}

/* Backoffice tab: login-code button + direct-link icon on one row. */
.admin-backoffice-code-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Backoffice tab: e-mail field + send button on one row. */
.admin-backoffice-invite {
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: 560px;
}
.admin-backoffice-invite input {
    flex: 1 1 auto;
}
.admin-backoffice-invite .btn {
    flex: 0 0 auto;
}

/* Login-code pools table (System → Login-Codes): compact numeric/select cells so
   the config controls read as small settings, not full-width form fields. */
.admin-codepools-table td {
    vertical-align: middle;
}
.admin-codepools-num {
    width: 130px;
}
.admin-codepools-charset {
    width: 220px;
}
.admin-codepools-actions {
    text-align: right;
}

.admin-settings-label {
    font-weight: 500;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text);
}

/* Inline "ⓘ" trigger sitting after a feature caption; opens its plain-language
   explanation dialog (FeatureInfo component). Borderless, muted by default and
   picking up the customer's primary colour on hover/focus. */
.feature-info-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    margin-left: 6px;
    padding: 0;
    border: none;
    background: none;
    color: var(--sidan-text-muted);
    cursor: pointer;
    line-height: 1;
}
.feature-info-btn:hover,
.feature-info-btn:focus-visible {
    color: var(--sidan-color-primary);
}
.feature-info-btn .material-symbols-outlined {
    font-size: 20px;
}

/* Body text of an explanation dialog: readable measure, comfortable spacing. */
.feature-info-text {
    margin: 0;
    font-size: var(--sidan-text-md);
    font-weight: 400;
    line-height: 1.55;
    max-width: 60ch;
}

.admin-settings-value { /* width is set on <colgroup> so the value column
   gets the same footprint whether it carries one (colspan=2) or two
   separate per-area cells. */ }

/* Three-column layout: label + ADMIN + LIVE. Globale Felder spannen den
   Wert über beide Bereichs-Spalten (colspan=2); bereichs-spezifische
   Werte stehen in ihrer eigenen Spalte. */
.admin-settings-col-label { width: 50%; }
.admin-settings-col-area { width: 25%; }

.admin-settings-table thead th {
    padding-top: 0;
    padding-bottom: 8px;
    font-size: var(--sidan-text-sm);
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--sidan-text-muted);
    border-bottom: 1px solid var(--sidan-border);
}

.admin-settings-table thead th.admin-settings-col-area {
    text-align: left;
    color: var(--sidan-text);
    font-weight: 700;
}

.admin-settings-row td.admin-settings-area-value {
    text-align: left;
}

/* Vertical separators between the three columns (label | ADMIN | LIVE). A left
   border sits before every ADMIN and LIVE cell, giving the label|ADMIN and
   ADMIN|LIVE lines on per-area rows and in the header. Full-width
   subheading/hint rows (colspan=3) have no internal cell boundary and stay
   unbroken, banding the sections. */
.admin-settings-table thead th.admin-settings-col-area,
.admin-settings-table td.admin-settings-area-value {
    border-left: 2px solid var(--sidan-border);
}

/* Global rows span their value across both area columns (colspan=2): the value
   applies to ADMIN and LIVE alike. Center it so it reads as belonging to the
   whole row rather than sitting in the ADMIN column, and deliberately DROP the
   ADMIN|LIVE separator inside the span — a mid-line here would re-imply a
   per-area split and make a shared value look ADMIN-only. */
.admin-settings-table td.admin-settings-area-value[colspan="2"] {
    text-align: center;
}

.admin-settings-table tr.admin-settings-row.is-disabled .admin-settings-label {
    opacity: .5;
}

.admin-settings-subhead > th {
    padding-top: 32px;
    padding-bottom: 6px;
    font-size: var(--sidan-text-md);
    font-weight: 600;
    letter-spacing: .02em;
    color: var(--sidan-text);
    border-bottom: 1px solid var(--sidan-border);
}

.admin-settings-table tr.admin-settings-subhead:first-child > th {
    padding-top: 8px;
}

.admin-settings-subhead-hint > td {
    padding-top: 8px;
    padding-bottom: 4px;
    max-width: 60ch;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
}

/* Projects → Settings tab: stacked rows, one Setting per row, with a
   compact trash control on the right. */
.admin-settings-list {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin-bottom: 20px;
}

.admin-setting-row {
    display: flex;
    align-items: flex-end;
    gap: 12px;
    padding: 16px;
    background: var(--sidan-surface-sunk);
    border-radius: var(--sidan-radius-md);
}

.admin-setting-row .admin-form-field {
    flex: 1 1 auto;
}

.admin-setting-row .btn {
    flex: 0 0 auto;
    align-self: flex-end;
    height: var(--sidan-control-lg-height, 48px);
    aspect-ratio: 1 / 1;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.admin-color-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.admin-color-hex {
    flex: 0 0 auto;
    width: 7.5rem;
    font-family: var(--sidan-font-mono, ui-monospace, Menlo, monospace);
    letter-spacing: .04em;
}

/* Branding asset upload (font / logo): a labelled file picker plus the current
   asset and a remove action. Reused for both font and logo sections. */
.admin-upload-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 16px;
}
/* Themed, localized file picker (FileUploadButton.razor): a real .btn next to
   the selected filename, proxying a visually hidden native <input type=file>.
   The browser file control can be neither themed nor translated, so we replace
   it; the wrapping <label> keeps the hidden input the click target. */
.admin-file-upload {
    display: flex;
    align-items: center;
    gap: 14px;
    margin: 0;
    cursor: pointer;
}
/* Materials: the "choose files" + "add link" controls sit side by side above the list. */
.admin-explorer-add-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
}
.admin-file-upload-name {
    min-width: 0;
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-base);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Visually hidden but kept in the layout + focusable; the label proxies clicks. */
.admin-file-upload-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.admin-upload-current {
    display: flex;
    align-items: center;
    gap: 10px;
    color: var(--sidan-text-muted);
}
.admin-logo-thumb {
    height: 56px;
    width: auto;
    max-width: 160px;
    border-radius: var(--sidan-radius-sm);
    border: 1px solid var(--sidan-border);
    background: var(--sidan-surface);
    object-fit: contain;
}

/* Larger preview used in the branding panel when a logo is already present.
   No frame — the operator should see the file as it will appear, not as a
   thumbnail with a border. Logo left, remove button pinned to the right
   edge on the same baseline as the top of the logo — lines up with the
   per-row "Entfernen" buttons of the font table below. */
.admin-upload-current--logo {
    flex-direction: row;
    align-items: flex-start;
    gap: 16px;
    width: 100%;
}

.admin-upload-current--logo .btn {
    margin-left: auto;
}

.admin-logo-preview {
    width: 100%;
    max-width: 180px;
    height: auto;
    max-height: 110px;
    object-fit: contain;
    object-position: left center;
}

/* Per-customer font list — three columns: a live sample rendered in the
   uploaded face, the source file name, and a remove control. */
.admin-font-table {
    margin: 4px 0 18px;
}

.admin-font-table th {
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-sm);
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--sidan-border);
}

.admin-font-table td {
    padding: 10px 12px;
    vertical-align: middle;
    border-bottom: 1px solid var(--sidan-border);
}

.admin-font-table tr:last-child td {
    border-bottom: 0;
}

.admin-font-sample {
    font-size: var(--sidan-text-xl);
    line-height: 1.2;
    color: var(--sidan-text);
}

.admin-font-name {
    color: var(--sidan-text-muted);
    font-family: var(--sidan-font-mono, ui-monospace, Menlo, monospace);
    font-size: var(--sidan-text-md);
}

/* Placeholder token in the invalid-placeholders dialog: monospace so the exact
   bracketing (the whole point of the check) is unambiguous. */
.admin-placeholder-token {
    font-family: var(--sidan-font-mono, ui-monospace, Menlo, monospace);
    font-size: var(--sidan-text-md);
    /* Override Bootstrap's pink <code> colour: the token is the subject of the
       dialog, so render it as plain bold body text instead. */
    color: var(--sidan-text);
    font-weight: bold;
}

.admin-font-actions {
    text-align: right;
    width: 1%;
    white-space: nowrap;
}

/* Inherited rows: faintly muted file-name cell so the lack of a remove
   button reads as "from above" rather than "no action available". No
   "inherited from X" label — a child tenant must not learn its parent's
   identity (data-privacy boundary). */
.admin-font-table tr.is-inherited .admin-font-name {
    color: var(--sidan-text-muted);
    font-style: italic;
}

/* Branding tab: two columns — colours (+ preview) on the left, the font/logo
   uploads on the right. Collapses to one column on narrow viewports. */
.admin-branding-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    /* stretch (not start) so both columns take the full row height — otherwise
       the right column's divider border only spans its own shorter content. */
    align-items: stretch;
}
/* Inner padding instead of a gap, so the divider line on the right column sits
   symmetrically (32px each side) between the two columns. */
.admin-branding-col { padding-inline: 32px; }
.admin-branding-col:first-child { padding-inline-start: 0; }
.admin-branding-col:last-child { padding-inline-end: 0; }
.admin-branding-layout > .admin-branding-col + .admin-branding-col {
    border-left: 1px solid var(--sidan-border);
}
@media (max-width: 1100px) {
    .admin-branding-layout { grid-template-columns: minmax(0, 1fr); }
    .admin-branding-col { padding-inline: 0; }
    /* Stacked: swap the vertical divider for a horizontal one above column two. */
    .admin-branding-layout > .admin-branding-col + .admin-branding-col {
        border-left: none;
        border-top: 1px solid var(--sidan-border);
        padding-top: 24px;
    }
}

/* Colour pickers as a 2-up grid inside the left column. .admin-form-field
   carries grid-column: span 6 from the 12-track form grid; reset it so each
   picker takes one track here. */
.admin-branding-colors {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 18px 24px;
}
.admin-branding-colors .admin-form-field {
    grid-column: auto;
}
@media (max-width: 640px) {
    .admin-branding-colors { grid-template-columns: minmax(0, 1fr); }
}

/* Toggle / switch */
.admin-switch {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    user-select: none;
    font-size: var(--sidan-text-md);
}

.admin-switch input[type="checkbox"] {
    width: 22px;
    height: 22px;
    accent-color: var(--sidan-color-primary);
    cursor: pointer;
}

/* Multiple .admin-switch rows stack vertically. Without an explicit block
   container the inline-flex elements pile up on the same line. */
.admin-switch + .admin-switch,
.admin-inline-action + .admin-switch {
    display: flex;
    margin-top: 12px;
}

/* Vertical stack of Bootstrap .form-check checkboxes, used for the roles
   list. Gap matches the .admin-switch row spacing so a mix of checkboxes
   and toggles reads as one column. The per-row centering (flex, body size,
   box alignment) is the app-wide default in css/bootstrap.css — this wrapper
   only owns the vertical stacking. */
.admin-checkbox-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* Assignable list (AssignableList component): a stack of rows, each a full-width
   <label> click target that toggles the item's assignment. Unlike a .form-check
   the row is not centred by the global rule, so the layout lives here: checkbox
   at the top, content beside it (content may wrap to several lines, e.g. an
   anchor teaser plus its badges). */
.admin-assignable-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.admin-assignable-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 0;
    padding: 8px 10px;
    border-radius: var(--sidan-radius-sm);
    cursor: pointer;
}
.admin-assignable-item:hover {
    background: var(--sidan-surface-sunk);
}
.admin-assignable-item .form-check-input {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    margin: 0;
}
.admin-assignable-content {
    flex: 1 1 auto;
    min-width: 0;
    line-height: 22px;
}

/* Caption + action button on the same line — used for the auto-lock
   reason next to an "Entsperren" button. */
.admin-inline-action {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-top: 12px;
}

/* Inline warning-coloured caption (without a full alert chrome). */
.admin-text-warning {
    color: var(--sidan-warning, #b25b00);
    font-weight: 500;
}

/* Languages grid */
.admin-languages-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 12px;
    margin-top: 12px;
}

.admin-language-pill {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    background: var(--sidan-surface);
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-sm);
    cursor: pointer;
    transition: border-color .15s, background .15s;
    text-align: left;
}

.admin-language-pill:hover {
    border-color: var(--sidan-color-primary);
}

.admin-language-pill.is-active {
    background: var(--sidan-color-primary);
    border-color: var(--sidan-color-primary);
    color: #fff;
}

.admin-language-pill-key {
    font-weight: 600;
    font-size: var(--sidan-text-md);
    min-width: 32px;
}

.admin-language-pill-name {
    flex: 1 1 auto;
    font-size: var(--sidan-text-md);
}

.admin-language-pill-default {
    font-size: var(--sidan-text-sm);
    padding: 2px 8px;
    border-radius: var(--sidan-radius-pill);
    background: rgba(255, 255, 255, .25);
}

.admin-text-body {
    color: var(--sidan-text-muted);
    margin-bottom: 12px;
    font-size: var(--sidan-text-md);
    max-width: 60ch;
}

/* Standalone muted note (e.g. "only one method available") without the
   .admin-text-body paragraph spacing — sits closer to whatever it
   annotates. */
.admin-text-muted {
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-md);
}

/* MyAccount sign-in method picker. Stack form-check radios with a small
   gap; the trailing muted hint (one-method-only) sits below them. */
.account-method-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.account-method-list .form-check {
    margin: 0;
}

.admin-error {
    background: var(--sidan-danger-bg, #fdecec);
    color: var(--sidan-danger, #b32424);
    border: 1px solid var(--sidan-danger-border, #f3c0c0);
    padding: 12px 14px;
    border-radius: var(--sidan-radius-sm);
    font-size: var(--sidan-text-md);
    margin-top: 12px;
}

@media (max-width: 880px) {
    .admin-form-grid {
        grid-template-columns: minmax(0, 1fr);
    }
    /* Collapse all span-modifier widths to a single column so quarter/third
       fields don't end up squashed past readability on narrow viewports. */
    .admin-form-field,
    .admin-form-field-third,
    .admin-form-field-quarter,
    .admin-form-field-sixth {
        grid-column: 1 / -1;
    }
}

/* ──────────────────────────────────────────────────────────────────────
   E-mail templates: editor + live preview side by side
   ────────────────────────────────────────────────────────────────────── */
/* Two equal columns so the rich-text editor stops sprawling full width and the
   rendered mail sits right beside it. Both cards share the .admin-form-card
   surface; only the split governs their width. */
.email-editor-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 24px;
    align-items: start;
}

.email-preview-pane {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.email-preview-title {
    font-size: var(--sidan-text-md);
    font-weight: 600;
    color: var(--sidan-text);
}

/* The mail renders in its own document; give it a neutral surround and a tall
   viewport so most messages show without scrolling. */
.email-preview-frame {
    width: 100%;
    min-height: 480px;
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-sm);
    background: #fff;
}

/* Stack editor over preview once two columns get too cramped to edit in. */
@media (max-width: 1200px) {
    .email-editor-split {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ──────────────────────────────────────────────────────────────────────
   UI-Languages admin page
   ────────────────────────────────────────────────────────────────────── */
.admin-languages-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.admin-language-row {
    display: grid;
    /* Three columns: flag · name+endonym · actions. Coverage column removed
       (was always 100% post-activation and added more visual noise than info). */
    grid-template-columns: 32px 1fr auto;
    align-items: center;
    gap: 16px;
    padding: 14px 18px;
    background: var(--sidan-surface);
    border-radius: var(--sidan-radius);
    box-shadow: var(--sidan-shadow-soft, 0 1px 2px rgba(0, 0, 0, .04));
}

.admin-language-row-meta { min-width: 0; }

.admin-language-row-name {
    font-weight: 500;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text);
}

.admin-language-row-native {
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-sm);
}

.admin-language-row-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* "Sprache hinzufügen" CTA on the project Stamm tab. Sits ABOVE the content-
   language list (CLAUDE.md add-control placement); left-aligned and natural
   width so it reads as the entry point to the searchable catalog, with a gap
   to the list below. */
.project-languages-add {
    align-self: flex-start;
    margin-bottom: 6px;
}

/* Content-language switcher in the project page header (pill + dropdown). The
   pill reuses .lang-pill from the footer picker; only the dropdown panel is
   defined here. The picker is positioned relative so the menu anchors to it. */
.content-lang-picker {
    position: relative;
}

/* Transparent full-screen click-away layer below the menu — a click anywhere
   outside the menu closes it. */
.content-lang-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1040;
}

.content-lang-menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 1045;
    min-width: 210px;
    display: flex;
    flex-direction: column;
    padding: 6px;
    background: var(--sidan-surface);
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-sm);
    box-shadow: var(--sidan-shadow-lg);
}

.content-lang-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    background: transparent;
    border-radius: var(--sidan-radius-sm);
    color: var(--sidan-text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.content-lang-item:hover {
    background: var(--sidan-surface-sunk);
}

.content-lang-item.is-active {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
    font-weight: 500;
}

.content-lang-item-name {
    flex: 1;
    min-width: 0;
}

.content-lang-item .check {
    flex-shrink: 0;
    color: var(--sidan-color-primary);
}

.admin-btn-danger {
    color: var(--sidan-danger, #b32424);
    border-color: var(--sidan-danger-border, #f3c0c0);
}
.admin-btn-danger:hover {
    background: var(--sidan-danger-bg, #fdecec);
    color: var(--sidan-danger, #b32424);
}

/* Icon-only variant of the danger button — the project default for delete /
   remove actions (CLAUDE.md). Compact and square, the Material `delete` glyph
   centred; pair with title/aria-label for an accessible name. */
.admin-btn-danger-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding-inline: 0.6rem;
}

/* Per-language spinner used in the UiLanguages admin while a background
   translation is in progress. Replaces the <Flag> in-place so the row/pill
   keeps its layout dimensions and nothing visibly jumps. */
.admin-language-spinner {
    width: 32px;
    height: 32px;
    border-width: 3px;
    color: var(--sidan-color-primary);
    flex-shrink: 0;
}

.admin-language-pill .admin-language-spinner {
    width: 18px;
    height: 18px;
    border-width: 2px;
}

@media (max-width: 720px) {
    .admin-language-row {
        grid-template-columns: 32px 1fr;
    }
    .admin-language-row-actions {
        grid-column: 1 / -1;
    }
}

/* Scale captions table — one row per integer value in the scale's
   [min..max] range: the value, its LIVE display caption (Anzeige) and the
   LIVE mouse-over legend. */
.admin-scale-table {
    table-layout: fixed;
    width: 100%;
    max-width: 860px;
    border-collapse: collapse;
}

.admin-scale-col-value { width: 56px; }
.admin-scale-col-caption { width: 38%; }

.admin-scale-table th {
    text-align: left;
    font-weight: 500;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
    padding: 0 12px 8px;
}

.admin-scale-table td {
    padding: 4px 12px;
    vertical-align: middle;
}

.admin-scale-table-value {
    font-weight: 600;
    text-align: center;
    color: var(--sidan-text-muted);
}

/* ── Project documents explorer (Vorlagen tab) ──────────────────────────
   Two columns: a file tree on the left, the contextual detail/upload pane
   on the right. Generic, theme-driven (colours only via --sidan-* vars). */
.admin-explorer {
    display: grid;
    /* minmax(0, …) so a long, unbreakable file name can't blow the column
       past its share — keeps the split a true 50/50. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 24px;
    align-items: start;
}

.admin-explorer-tree {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.admin-explorer-folder {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

/* A tree row — folder or file. Full-width clickable button, no chrome. */
.admin-explorer-node {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 10px;
    border: none;
    background: transparent;
    border-radius: var(--sidan-radius-sm);
    text-align: left;
    color: inherit;
    cursor: pointer;
    font-size: var(--sidan-text-md);
}

.admin-explorer-node:hover {
    background: var(--sidan-surface);
}

.admin-explorer-node.is-active {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
}

/* Two indent levels: audience (category) → file. */
.admin-explorer-node-file {
    padding-left: 28px;
}

/* Category (folder) names in the customer's primary colour. */
.admin-explorer-node-audience .admin-explorer-node-label {
    color: var(--sidan-color-primary);
}

.admin-explorer-node-label {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Inline action (e.g. "move up") at the right end of a file row. Appears on
   row hover / when the row is active; click does not select the row. */
.admin-explorer-node-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: var(--sidan-text-muted);
    border-radius: var(--sidan-radius-sm);
    padding: 2px;
    cursor: pointer;
    opacity: 0;
}

.admin-explorer-node-action .material-symbols-outlined {
    font-size: 20px;
}

.admin-explorer-node-file:hover .admin-explorer-node-action,
.admin-explorer-node-file.is-active .admin-explorer-node-action {
    opacity: 1;
}

.admin-explorer-node-action:hover {
    background: var(--sidan-surface);
    color: var(--sidan-color-primary);
}

.admin-explorer-folder-icon,
.admin-explorer-file-icon {
    font-size: 20px;
    color: var(--sidan-text-muted);
}

.admin-explorer-count {
    min-width: 22px;
    padding: 0 6px;
    text-align: center;
    border-radius: 999px;
    background: var(--sidan-surface);
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-sm);
}

.admin-explorer-node.is-active .admin-explorer-caret,
.admin-explorer-node.is-active .admin-explorer-file-icon {
    color: var(--sidan-color-primary);
}

.admin-explorer-empty {
    padding-left: 28px;
    color: var(--sidan-text-muted);
    cursor: default;
}

/* No outer frame — the detail content sits flush in its column (like the tree
   on the left), not inside a surface card. */
.admin-explorer-detail {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.admin-explorer-detail-title {
    margin: 0;
}

.admin-explorer-detail-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

/* Left cluster (Download + optional "translate"); the delete button stays
   pinned to the right via the parent's space-between. */
.admin-explorer-detail-actions-left {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

/* Secondary row beneath the primary actions: the warning buttons (missing fonts,
   invalid placeholders) that only appear when there is something to flag. */
.admin-explorer-detail-warnings {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 12px;
}

/* Outline button that flags a missing-font problem in the warning colour. At
   rest the warning colour is the text/border; on hover it fills, with the
   on-warning ink (white) as the text. */
.admin-font-warning-btn {
    color: var(--sidan-warning);
    border-color: var(--sidan-warning);
}

.admin-font-warning-btn:hover {
    background: var(--sidan-warning);
    color: var(--sidan-warning-ink);
}

/* Missing-fonts table: rows colour-coded by severity — a font used in text is
   required (danger red), a declared-but-unused one is only nice-to-have
   (warning orange) and can be removed. */
.admin-font-table {
    width: 100%;
}

.admin-font-row-required td {
    color: var(--sidan-danger, #b32424);
}

.admin-font-row-optional td {
    color: var(--sidan-warning);
}

/* Action column + its placeholder dash for required (non-removable) fonts. */
.admin-font-action-col {
    text-align: right;
    white-space: nowrap;
}

.admin-font-action-none {
    color: var(--sidan-text-subtle);
}

.admin-explorer-file-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-sm);
}

/* Keep the icon, truncate the (potentially very long) file name with an ellipsis. */
.admin-explorer-file-meta .material-symbols-outlined {
    flex-shrink: 0;
}

.admin-explorer-file-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-explorer-uploading {
    display: flex;
    align-items: center;
    color: var(--sidan-text-muted);
}

/* Inline file preview (image / PDF) in the detail pane. */
.admin-explorer-preview {
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-sm);
    background: var(--sidan-surface-sunk);
    overflow: hidden;
}

.admin-explorer-preview-image {
    display: block;
    width: 100%;
    max-height: 360px;
    object-fit: contain;
}

/* Scrollable PDF preview (PDF + converted Office docs). */
.admin-explorer-preview-frame {
    display: block;
    width: 100%;
    height: 480px;
    border: none;
}

.admin-explorer-preview-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 40px 16px;
    color: var(--sidan-text-muted);
    text-align: center;
}

.admin-explorer-preview-placeholder .material-symbols-outlined {
    font-size: 40px;
}

/* Exercise phases: a two-column grid — phase checkbox (label sized by
   .admin-checkbox-list) in column 1, the phase's duration field in column 2.
   max-content sizes column 1 to the widest label so all duration fields line up
   in one left-aligned column right after it. align-items centres each row. */
.exercise-phases {
    display: grid;
    grid-template-columns: max-content max-content;
    column-gap: 24px;
    row-gap: 12px;
    align-items: center;
}

/* The mandatory Durchführung phase: slightly emphasised label + a "Pflicht" chip. */
.exercise-phase-mandatory .form-check-label {
    font-weight: 600;
}

.exercise-phase-badge {
    margin-left: 8px;
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-color-primary);
    font-size: var(--sidan-text-sm);
    font-weight: 400;
}

/* Optionality hint, pinned full-width below the mandatory phase — a thin rule
   separates the mandatory Durchführung above from the optional phases below. */
.exercise-phases-hint {
    grid-column: 1 / -1;
    margin: 4px 0 8px;
    padding-top: 12px;
    border-top: 1px solid var(--sidan-border);
}

/* Assigned-competences list (right column of the Ablauf tab) — plain links. */
.exercise-competence-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.exercise-competence-list a {
    font-size: var(--sidan-text-md);
}

/* Exercise phase duration: a short numeric field (≤ 3 digits + unit). Hug the
   content instead of stretching the row. */
.exercise-duration {
    width: auto;
    flex: 0 0 auto;
}

.exercise-duration .form-control {
    flex: 0 0 auto;
    /* Room for 3 digits plus the number spinner at -lg sizing. */
    width: 8.5rem;
}

/* ---- Exercise anchors ("Anker") explorer ------------------------------ */
/* Anchor navigator (accordion body): the competence name in each header is a
   selectable button (click → its observer hint), styled as a primary link; it
   inherits weight/size/ellipsis from .admin-accordion-title. Active = the
   competence whose hint is currently being edited. */
.anchors-competence-name {
    min-width: 0;
    border: none;
    background: none;
    padding: 0;
    text-align: left;
    cursor: pointer;
    font-family: inherit;
    color: var(--sidan-color-primary);
}

.anchors-competence-name.is-active {
    text-decoration: underline;
}

/* Compact striped anchor table inside the (narrow) navigator column. */
.anchors-table tbody td {
    padding: 6px 10px;
}

/* Selected anchor row: a soft tint with normal (dark) text. The shared
   .is-selected fills the row with solid primary + white text, but the striped
   even-row background outranks it (higher specificity) — leaving white text on
   grey, unreadable. Scope a readable selection here that outranks the striping. */
.anchors-table .admin-table-row.is-selected td,
.anchors-table .admin-table-row.is-selected:hover td {
    background: var(--sidan-color-primary-soft);
    color: var(--sidan-text);
}

/* Sample-solution answer, rendered read-only in its category's table. The rich
   text may run over several lines, so the cell wraps instead of truncating —
   the whole row opens the editor. */
.sample-solution-answer-cell {
    overflow-wrap: anywhere;
}

/* Row index inside a list table — a small muted ordinal in the first column
   (anchors within a competence, sample-solution answers within a category). */
.admin-table-no-col {
    width: 1%;
    white-space: nowrap;
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-sm);
    text-align: right;
}

/* Teaser (wraps to as many lines as it needs) plus the completeness dots; the
   dots align to the teaser's first line. */
.anchors-teaser-wrap {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    min-width: 0;
}

.anchors-teaser {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

.anchors-teaser-wrap .anchors-dots {
    /* Nudge down so the dots sit on the teaser's first text line. */
    margin-top: 6px;
}

/* Completeness dots: one per phrasing (positive/negative/intro/self). Filled =
   the current language has text for that phrasing. */
.anchors-dots {
    display: inline-flex;
    gap: 3px;
    flex-shrink: 0;
}

.anchors-dot {
    width: 7px;
    height: 7px;
    border-radius: 999px;
    border: 1px solid var(--sidan-border);
}

.anchors-dot.is-filled {
    background: var(--sidan-color-primary);
    border-color: var(--sidan-color-primary);
}

/* Anchor detail: the two categories (phrasings + BOPs) side by side — the tabs
   were dissolved into a fixed two-column layout. Each column stacks its fields
   (and, for BOPs, the standard/all toggle) vertically. */
.anchors-detail-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    align-items: start;
}

.anchors-detail-col {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

.anchors-detail-col-title {
    display: flex;
    align-items: baseline;
    gap: 14px;
    font-weight: 600;
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-md);
}

/* BOPs view switch: two inline text links (not a button group) beside the "BOPs"
   heading. The active view is the primary-coloured, bold one; the other is a
   muted link. */
.anchors-view-toggle {
    display: inline-flex;
    gap: 12px;
    font-weight: 400;
}

.anchors-view-link {
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;
    font: inherit;
    color: var(--sidan-text-muted);
    text-decoration: none;
}

.anchors-view-link:hover {
    color: var(--sidan-color-primary);
    text-decoration: underline;
}

.anchors-view-link.is-active {
    color: var(--sidan-color-primary);
    font-weight: 600;
    cursor: default;
    text-decoration: none;
}

/* Default scale steps get a subtle primary accent so the rater sees which steps
   are the standard ones. */
.anchors-bop-default .form-label {
    color: var(--sidan-color-primary);
}

/* Tablet: stack the two columns so the tree sits above the detail pane. */
@media (max-width: 768px) {
    .admin-explorer {
        grid-template-columns: 1fr;
    }
}

/* ---- Scroll-to-top helper (ScrollToTop.razor) -------------------------- */
/* Floating round button, bottom-right of the viewport. Hidden by default;
   sidan2.initScrollToTop adds .is-visible once the main area is scrolled past
   its threshold. Primary-filled so it picks up customer branding via the
   central variables. */
.admin-scroll-top {
    position: fixed;
    right: 28px;
    bottom: 28px;
    z-index: 1030;
    width: 48px;
    height: 48px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: var(--sidan-color-primary);
    color: var(--sidan-color-primary-ink, #fff);
    box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
    cursor: pointer;
    /* Hidden resting state — faded out, nudged down, and click-through so it
       never intercepts pointer events while invisible. */
    opacity: 0;
    transform: translateY(8px);
    pointer-events: none;
    transition: opacity .18s ease, transform .18s ease, background .15s ease;
}

.admin-scroll-top.is-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

.admin-scroll-top:hover {
    background: var(--sidan-color-primary-hover, var(--sidan-color-primary));
}

.admin-scroll-top .material-symbols-outlined {
    font-size: 26px;
}

/* ── KI function-test chat ──────────────────────────────────────────────── */

/* Editable connection (endpoint + model) the test is hitting. */
.ki-settings {
    margin-bottom: 24px;
}

.ki-settings-title {
    font-size: var(--sidan-text-md);
    font-weight: 600;
    color: var(--sidan-text);
}

.ki-settings-hint {
    font-size: var(--sidan-text-base);
    color: var(--sidan-text-muted);
    margin: 4px 0 12px;
    max-width: 70ch;
}

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

.ki-settings-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.ki-settings-label {
    font-size: var(--sidan-text-base);
    font-weight: 500;
    color: var(--sidan-text-subtle);
}

/* Model dropdown + reload button on one row. */
.ki-model-row {
    display: flex;
    gap: 8px;
    align-items: stretch;
}

.ki-model-row .form-select,
.ki-model-row .form-control {
    flex: 1;
}

.ki-model-reload {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.ki-settings-error {
    font-size: var(--sidan-text-base);
    color: var(--sidan-danger);
}

/* Data-protection origin badge: where the selected LLM runs (internal / EU /
   third country). Colour communicates the privacy posture at a glance. */
.ki-loc-badge {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border-radius: var(--sidan-radius-pill, 999px);
    font-size: var(--sidan-text-sm);
    font-weight: 600;
    white-space: nowrap;
}

.ki-loc-badge--internal {
    background: var(--sidan-success-soft);
    color: var(--sidan-success);
}

.ki-loc-badge--eu {
    background: var(--sidan-warning-soft);
    color: var(--sidan-warning);
}

.ki-loc-badge--third {
    background: var(--sidan-danger-soft);
    color: var(--sidan-danger);
}

/* Per-endpoint origin override in the manage dialog — kept compact next to the
   delete button. */
.ki-endpoint-loc-select {
    flex-shrink: 0;
    width: auto;
}

/* Privacy advisory shown when a personal-data purpose runs on an external LLM. */
.ki-privacy-warning {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 12px;
    padding: 12px 14px;
    border-radius: var(--sidan-radius-md);
    background: var(--sidan-danger-soft);
    color: var(--sidan-danger);
    font-size: var(--sidan-text-base);
    max-width: 70ch;
}

.ki-privacy-warning .material-symbols-outlined {
    flex-shrink: 0;
    font-size: 1.3em;
}

/* Endpoint selector: dropdown + "manage" button on one row (mirrors .ki-model-row). */
.ki-endpoint-row {
    display: flex;
    gap: 8px;
    align-items: stretch;
}

.ki-endpoint-row .form-select {
    flex: 1;
}

.ki-endpoint-manage {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

/* Endpoint management dialog: add row above the list (CLAUDE.md "add control
   above the list"), then the list of saved endpoints. Name and address are
   stacked (each needs the full dialog width — URLs and names can get long). */
.ki-endpoint-add-row {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-bottom: 24px;
    border-bottom: 1px solid var(--sidan-border);
    padding-bottom: 24px;
}

.ki-endpoint-add-actions {
    display: flex;
    justify-content: flex-start;
}

.ki-endpoint-add-actions .btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.ki-endpoint-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.ki-endpoint-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    background: var(--sidan-surface-sunk);
    border-radius: var(--sidan-radius-md);
}

.ki-endpoint-info {
    flex: 1;
    min-width: 0;
}

.ki-endpoint-name {
    font-size: var(--sidan-text-md);
    font-weight: 600;
    color: var(--sidan-text);
}

.ki-endpoint-url {
    font-size: var(--sidan-text-base);
    color: var(--sidan-text-muted);
    font-family: var(--sidan-font-mono, ui-monospace, Menlo, monospace);
    word-break: break-all;
}

.ki-endpoint-key {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: 4px;
    font-size: var(--sidan-text-base);
    color: var(--sidan-text-muted);
}

.ki-endpoint-key .material-symbols-outlined {
    font-size: 1.1em;
}

/* Function-test section heading (moved here from the page header — the page is
   about configuration; the reachability probe is a distinct block below it). */
.ki-test-header {
    margin-top: 32px;
}

.ki-test-title {
    font-size: var(--sidan-text-lg);
    font-weight: 600;
    color: var(--sidan-text);
    margin: 0;
}

.ki-test-subtitle {
    font-size: var(--sidan-text-base);
    color: var(--sidan-text-muted);
    margin: 4px 0 12px;
    max-width: 70ch;
}

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

.ki-transcript {
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-height: 58vh;
    overflow-y: auto;
    padding: 4px;
}

.ki-empty {
    color: var(--sidan-text-muted);
    text-align: center;
    padding: 40px 16px;
}

.ki-msg {
    max-width: 80%;
    padding: 10px 14px;
    border-radius: var(--sidan-radius-lg);
}

.ki-msg--user {
    align-self: flex-end;
    background: var(--sidan-color-primary-soft);
}

.ki-msg--assistant {
    align-self: flex-start;
    background: var(--sidan-surface-sunk);
}

.ki-msg--error {
    align-self: flex-start;
    background: var(--sidan-danger-bg);
    border: 1px solid var(--sidan-danger-border);
}

.ki-msg-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 4px;
}

.ki-msg-role {
    font-size: var(--sidan-text-sm);
    font-weight: 600;
    color: var(--sidan-text-subtle);
}

.ki-msg--error .ki-msg-role {
    color: var(--sidan-danger);
}

.ki-msg-meta {
    font-size: var(--sidan-text-sm);
    color: var(--sidan-text-muted);
    font-variant-numeric: tabular-nums;
}

.ki-msg-text {
    font-size: var(--sidan-text-md);
    color: var(--sidan-text);
    white-space: pre-wrap;
    word-break: break-word;
}

.ki-thinking {
    display: flex;
    align-items: center;
    gap: 10px;
    color: var(--sidan-text-muted);
}

/* Composer pinned to the bottom of the chat, always reachable. */
.ki-composer {
    position: sticky;
    bottom: 0;
    display: flex;
    gap: 12px;
    padding-top: 8px;
    background: var(--sidan-surface);
}

.ki-composer .form-control {
    flex: 1;
}

/* Purpose selector left of the chat input — sized to content, not stretched. */
.ki-composer .ki-test-purpose {
    flex: 0 0 auto;
    width: auto;
    max-width: 220px;
}

/* ════════════════════════════════════════════════════════════════════
   Timetable day grid (read-only view; the editor builds on it)
   Time axis vertical, one column per actor lane. Session cells span
   their duration via rowspan; colors come from the central step-type
   tokens in variables.css.
   ════════════════════════════════════════════════════════════════════ */

.tt-grid-scroll {
    overflow-x: auto;
}

.tt-grid {
    border-collapse: separate;
    border-spacing: 0;
    table-layout: fixed;
    width: max-content;
}

/* Sticky lane headers keep orientation on long days. */
.tt-grid thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--sidan-surface);
    border-bottom: 1px solid var(--sidan-border-strong);
    padding: 8px 10px;
    text-align: left;
    font-size: var(--sidan-text-md);
    font-weight: var(--sidan-font-weight-bold);
    color: var(--sidan-text);
}

.tt-time-col {
    width: 76px;
    min-width: 76px;
}

.tt-lane-head {
    width: 210px;
    min-width: 210px;
    border-left: 1px solid var(--sidan-border);
}

/* Slot rows are thin; sessions get their height from the rowspan. */
.tt-grid tbody tr {
    height: 18px;
}

/* Breathing room between the sticky header row and the day's first slot —
   a transparent border (not padding) so the colored session fills stay
   aligned with their true start time. */
.tt-grid tbody tr:first-child > th,
.tt-grid tbody tr:first-child > td {
    border-top: 10px solid transparent;
    background-clip: padding-box;
}

/* Time axis: sticky to the left, labels only on raster marks. */
.tt-time-cell {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--sidan-surface);
    padding: 0 10px 0 0;
    text-align: right;
    vertical-align: top;
    /* Match the raster row height so a time label never stretches its row —
       only then is a session's rendered height truly proportional to its
       duration (rowspan × row height). */
    line-height: 18px;
    font-size: var(--sidan-text-sm);
    font-weight: var(--sidan-font-weight-regular);
    color: var(--sidan-text-subtle);
    font-variant-numeric: tabular-nums;
}

.tt-empty {
    border-left: 1px solid var(--sidan-border);
}

/* Quarter-hour guide lines across the grid. */
.tt-row-mark .tt-empty,
.tt-row-mark .tt-time-cell {
    border-top: 1px solid var(--sidan-border);
}

/* A session cell is exactly as tall as its duration (rowspan × row height). Its
   content lives in an absolutely-positioned body so it can never stretch the
   cell: text that does not fit is clipped (overflow:hidden), and a hover popup
   surfaces the full data for clipped sessions (sidan2.initTimetableTooltips). */
.tt-session {
    position: relative;
    vertical-align: top;
    overflow: hidden;
    padding: 0;
    border-radius: var(--sidan-radius-sm);
    background: var(--sidan-surface-sunk);
}

.tt-session-body {
    position: absolute;
    inset: 0;
    overflow: hidden;
    padding: 4px 10px;
    display: flex;
    flex-direction: column;
}

.tt-session-title {
    font-size: var(--sidan-text-md);
    font-weight: var(--sidan-font-weight-bold);
    color: var(--sidan-text);
    line-height: 1.2;
}

.tt-session-meta {
    font-size: var(--sidan-text-sm);
    color: var(--sidan-text-muted);
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
}

/* The session's length, appended after its time range. When it deviates from
   the setting's planned duration it flips to the warning colour (bold, so it
   stays visible on the coloured execution fill too) — the editable mismatch the
   validator flags as W1, surfaced inline. */
.tt-session-duration.is-deviation {
    color: var(--sidan-warning);
    font-weight: var(--sidan-font-weight-bold);
}

/* Step-type coloring — soft fill per type (variables.css). Only the phase
   that matters most — Durchführung — carries the full primary fill; its text
   switches to the on-primary ink to stay readable. */
.tt-type-preparation     { background: var(--sidan-step-preparation-bg); }
.tt-type-execution       { background: var(--sidan-step-execution-bg); }
.tt-type-execution .tt-session-title,
.tt-type-execution .tt-session-meta { color: var(--sidan-step-execution-ink); }
.tt-type-selfrating      { background: var(--sidan-step-selfrating-bg); }
.tt-type-rating          { background: var(--sidan-step-rating-bg); }
.tt-type-reflection      { background: var(--sidan-step-reflection-bg); }
.tt-type-welcome         { background: var(--sidan-step-welcome-bg); }
.tt-type-information     { background: var(--sidan-step-information-bg); }
.tt-type-reading         { background: var(--sidan-step-reading-bg); }
.tt-type-pause           { background: var(--sidan-step-pause-bg); }
.tt-type-feedbacktalk    { background: var(--sidan-step-feedbacktalk-bg); }
.tt-type-finalconference { background: var(--sidan-step-finalconference-bg); }
.tt-type-goodbye         { background: var(--sidan-step-goodbye-bg); }

/* Per-setting health (placeholder model): one entry per setting of the project,
   the reference setting emphasized. Stacks vertically inside the findings panel. */
/* Findings panel: pick which setting's verdict the list below reflects, with
   that setting's count badges next to the dropdown. */
.tt-findings-setting-picker {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* Keeps the conflict / hint badges on one row beneath the setting select.
   Without a wrapper the badges would each become a full-width flex child of
   the column-direction picker above. Spacing between badges is the shared
   .tt-status-badge + .tt-status-badge margin. */
.tt-findings-setting-badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

/* AI instruction row in the generate dialog: text input + apply button. */
.tt-ai-row {
    display: flex;
    gap: 8px;
}

.tt-ai-row .form-control {
    flex: 1;
}

/* Editor toolbar above the grid: lane filters + simulated start time (the
   former preview dialog's tools). */
/* A dialog whose page header lives in the AdminModal header (the timetable
   editor, the generation wizard): title + subtitle/back-link stack on the left;
   reset the modal header's bold/large inheritance on the second line so it still
   reads as a subtitle resp. back-link (the shared classes set size/colour, not
   weight). The header suppresses text selection for dragging — re-enable it on
   the back-link so it behaves like the one on every other admin page. */
.tt-modal-header-title {
    line-height: 1.1;
}
.tt-modal-header-title .admin-page-subtitle,
.tt-modal-header-title .admin-back-link {
    font-weight: var(--sidan-font-weight-regular);
}
.tt-modal-header-title .admin-back-link {
    user-select: text;
}

/* ── Editor shell: fixed flex column inside the fullscreen modal body ─────────
   The body itself must not scroll (only the grid region does), so a finding jump
   never moves the toolbar / tabs / findings panel out of view. Scoped via :has
   so other modals keep their normal scrolling body. */
.modal-body:has(> .tt-editor-shell) {
    overflow: hidden;
}

.tt-editor-shell {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    gap: 14px;
}

/* Toolbar: plan-health toggle on the left, controls on the right. The right side
   splits into a configuration group and an action group (see .tt-toolbar-group).
   Wraps onto a second line on narrow widths (tablet). */
.tt-editor-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px 24px;
}

.tt-editor-toolbar-state {
    display: flex;
    align-items: center;
}

.tt-editor-toolbar-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px 20px;
    margin-left: auto;
}

/* One cluster of buttons; a divider line sets the action group apart from the
   configuration group so "doing" and "configuring" controls read as separate. */
.tt-toolbar-group {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.tt-toolbar-group + .tt-toolbar-group {
    padding-left: 20px;
    border-left: 1px solid var(--sidan-border);
}

/* Stage: the scrolling grid region + the optional docked findings panel. */
.tt-editor-stage {
    display: flex;
    gap: 16px;
    flex: 1;
    min-height: 0;
}

/* The grid is the ONLY scroll container — vertical and horizontal. Jumping to a
   finding scrolls this, not the page. The inner .tt-grid-scroll (shared with the
   read-only view dialog) hands its horizontal scrolling over to this region. */
.tt-grid-region {
    flex: 1;
    min-width: 0;
    overflow: auto;
}

.tt-grid-region .tt-grid-scroll {
    overflow: visible;
}

/* Findings panel: docked on the right, scrolls on its own, stays put while the
   grid scrolls. Collapsed by default (the host only mounts it when open). */
.tt-findings-panel {
    flex: 0 0 360px;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 16px;
    background: var(--sidan-surface-sunk);
    border-radius: var(--sidan-radius);
}

.tt-findings-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.tt-findings-panel-title {
    font-weight: var(--sidan-font-weight-bold);
}

.tt-findings-empty {
    color: var(--sidan-text-muted);
}

/* Check-mark filter dropdowns: open/close is Blazor state (no Bootstrap JS),
   so the menu positioning lives here. The invisible backdrop closes the menu
   on any outside click; the menu sits above it. */
.tt-filter-dropdown {
    position: relative;
}

.tt-filter-dropdown .dropdown-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: auto;
    z-index: 1080;
    min-width: 250px;
    max-height: 60vh;
    overflow-y: auto;
    padding: 8px 0;
    box-shadow: var(--sidan-shadow-md);
}

.tt-filter-dropdown .form-check {
    margin: 0;
    padding: 6px 18px;
}

.tt-filter-dropdown .form-check-label {
    font-size: var(--sidan-text-md);
    font-weight: var(--sidan-font-weight-regular);
}

.tt-dropdown-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1075;
}

/* Candidate filter: each row pairs the visibility check-box with that
   candidate's profile selector. Wider menu to hold both; the select is pushed
   to the right of the row, the check-box keeps the shared left alignment. */
.tt-candidate-menu {
    /* Grow to the widest row so a candidate label never wraps; a floor keeps it
       comfortable with short labels, the cap stops it leaving the viewport. */
    min-width: 360px;
    width: max-content;
    max-width: 90vw;
}

/* One row = check-box + label on the left, profile select on the right, all on
   a single vertically centred line. A few px between rows for breathing room. */
.tt-candidate-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 5px 18px;
}
.tt-candidate-row + .tt-candidate-row {
    margin-top: 4px;
}
.tt-candidate-row--header {
    border-bottom: 1px solid var(--sidan-border);
    margin-bottom: 4px;
    padding-bottom: 9px;
}

/* Check-box + label as their own centred, clickable cluster; fills the slack so
   the select aligns at the row's right edge. Reset Bootstrap's float layout. */
.tt-candidate-check {
    flex: 1 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0;
    cursor: pointer;
}
.tt-candidate-check .form-check-input {
    float: none;
    margin: 0;
    flex: 0 0 auto;
}
.tt-candidate-name {
    white-space: nowrap;
    font-size: var(--sidan-text-md);
}
.tt-candidate-profile {
    flex: 0 0 auto;
    width: auto;
    min-width: 180px;
}

/* Reorder controls (move a candidate column up/down) at the row's right edge. */
.tt-candidate-reorder {
    flex: 0 0 auto;
    display: flex;
    gap: 4px;
    margin-left: auto;
}

/* Start-times dialog: a time value is five characters — cap the input so it
   doesn't read like a free-text field. */
.tt-day-start-input {
    max-width: 160px;
}

/* Validation status pills (list column + findings toggle in the dialog). */
/* Groups a timetable's conflict + hint pills on one line — no wrapping in a
   narrow cell (they overflow side by side instead). Spacing between the pills
   stays the shared .tt-status-badge + .tt-status-badge margin. */
.tt-status-badges {
    display: inline-flex;
    align-items: center;
}

.tt-status-badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 12px;
    border-radius: var(--sidan-radius-pill);
    font-size: var(--sidan-text-sm);
    font-weight: var(--sidan-font-weight-bold);
    white-space: nowrap;
}

.tt-status-badge + .tt-status-badge {
    margin-left: 6px;
}

.tt-status-badge.is-valid {
    background: var(--sidan-success-soft);
    color: var(--sidan-success);
}

.tt-status-badge.has-warnings {
    background: var(--sidan-warning-soft);
    color: var(--sidan-warning);
}

.tt-status-badge.has-errors {
    background: var(--sidan-danger-soft);
    color: var(--sidan-danger);
}

/* Plan-health toggle in the editor toolbar: the status pills act as the button
   that opens / closes the docked findings panel. */
.tt-findings-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    color: var(--sidan-text-muted);
}

/* Findings list inside the docked panel: one line per finding (clickable when it
   points at sessions). The panel itself scrolls, so the list is uncapped. */
.tt-finding-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.tt-finding {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text);
}

.tt-finding.is-clickable {
    cursor: pointer;
}

.tt-finding.is-clickable:hover {
    color: var(--sidan-color-primary);
}

.tt-finding .material-symbols-outlined {
    font-size: var(--sidan-text-lg);
    align-self: center;
}

.tt-finding-error {
    color: var(--sidan-danger);
}

.tt-finding-warning {
    color: var(--sidan-warning);
}

.tt-finding-day {
    flex: 0 0 auto;
    color: var(--sidan-text-subtle);
    font-size: var(--sidan-text-sm);
}

/* Editor affordances: clickable sessions / empty slots in the editable grid. */
.tt-session.tt-editable,
.tt-empty.tt-editable {
    cursor: pointer;
}

.tt-empty.tt-editable:hover {
    background: var(--sidan-color-primary-soft);
}

/* Read-only viewer: sessions are not clickable, but hovering surfaces the detail
   popup — the help cursor signals "hover for more" without implying an edit. */
#tt-viewer-grid .tt-session {
    cursor: help;
}

/* Drag & drop (editor only): sessions move via pointer drag, the bottom strip
   resizes. touch-action none lets pointer events win over touch scrolling on
   the draggable surfaces; position relative hosts the ghost translate and the
   absolutely positioned handle. */
.tt-session.tt-editable {
    position: relative;
    touch-action: none;
}

.tt-resize-handle {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 8px;
    cursor: ns-resize;
}

.tt-session.tt-dragging {
    opacity: .75;
    z-index: 5;
    box-shadow: var(--sidan-shadow-soft, 0 2px 8px rgba(0, 0, 0, .2));
}

.tt-session.tt-resizing {
    outline: 2px dashed var(--sidan-color-primary);
    outline-offset: -2px;
}

/* Hover popup for sessions whose box is too short to show all their text. A
   single reusable element (sidan2.initTimetableTooltips) positioned beside the
   session — never overlapping it, flipped away from the viewport edges. It
   mirrors the session's content (title + times + room + candidates), unclipped. */
.tt-session-tooltip {
    position: fixed;
    z-index: 1090;
    max-width: 320px;
    padding: 8px 12px;
    border-radius: var(--sidan-radius-sm);
    background: var(--sidan-surface);
    color: var(--sidan-text);
    box-shadow: var(--sidan-shadow-md);
    pointer-events: auto;
    display: none;
}

.tt-session-tooltip.is-visible {
    display: block;
}

.tt-session-tooltip .tt-session-title {
    font-size: var(--sidan-text-md);
    font-weight: var(--sidan-font-weight-bold);
}

.tt-session-tooltip .tt-session-meta {
    font-size: var(--sidan-text-sm);
    color: var(--sidan-text-muted);
    font-variant-numeric: tabular-nums;
}

/* The conflicts / hints section of the session hover popup. */
.tt-session-tooltip .tt-tooltip-findings {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.tt-session-tooltip .tt-finding {
    font-size: var(--sidan-text-sm);
}

.tt-session-tooltip .tt-finding .material-symbols-outlined {
    font-size: var(--sidan-text-md);
}

/* Divider when the popup carries the clipped body AND its findings below it. */
.tt-session-tooltip .tt-tooltip-body + .tt-tooltip-findings {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--sidan-border);
}

body.tt-drag-active {
    user-select: none;
}

/* Floating delta preview ("+15 min") that follows the cursor during a drag. */
.tt-drag-badge {
    position: fixed;
    z-index: 2000;
    display: none;
    padding: 4px 10px;
    border-radius: var(--sidan-radius-sm);
    background: var(--sidan-text);
    color: var(--sidan-surface);
    font-size: var(--sidan-text-sm);
    pointer-events: none;
}

.tt-drag-badge.is-visible {
    display: block;
}

/* Session dialog: participant number checkboxes flow in a compact wrap. */
.tt-check-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 20px;
}

/* Two narrow fields (time/count pairs) side by side in dialogs. */
.tt-window-row {
    display: flex;
    gap: 16px;
}

/* Variant whose fields share the row width equally (e.g. name + setting). */
.tt-window-row-grow .admin-form-field {
    flex: 1;
}

/* A single field that takes the remaining row width next to a fixed-width
   sibling (e.g. day label beside the narrow start-time field). */
.tt-field-grow {
    flex: 1;
}

/* Save dialog: two side-by-side options — overwrite the plan (re-applies to
   not-started events, impactful) vs. save a fresh duplicate (safe). Each is a
   borderless surface card carrying its own action button. */
.tt-save-options {
    display: flex;
    gap: 16px;
}

.tt-save-option {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 20px;
    background: var(--sidan-surface-sunk);
    border-radius: 10px;
}

.tt-save-option-title {
    margin: 0;
    font-size: var(--sidan-text-md);
    font-weight: 600;
}

.tt-save-option-hint {
    flex: 1;
    margin: 0;
    font-size: var(--sidan-text-md);
    color: var(--sidan-text-muted);
}

.tt-save-option-hint.is-warning {
    color: var(--sidan-danger, #b32424);
}

.tt-save-option .btn {
    width: 100%;
}

@media (max-width: 768px) {
    .tt-save-options { flex-direction: column; }
}

/* Read-only field value shown in place of a control (e.g. the single setting
   in the create dialog), aligned with the label like an input would be. */
.tt-static-value {
    padding: 6px 0;
    font-size: var(--sidan-text-md);
}

/* Generation wizard: per-day step heading ("Tag 1") above the phase table. */
.tt-wizard-step-title {
    margin-bottom: 12px;
}

/* The day's start time on the per-day phase step — the anchor the framing
   phases below are scheduled from; set off above the phase table. */
.tt-wizard-day-start {
    margin-bottom: 16px;
}

/* Timetable name field at the top of the Eckdaten dialog, above the day rows. */
.tt-frame-name {
    margin-bottom: 20px;
}

/* Observer rotation on the basics step, set off below the day table; its
   dropdown spans the full dialog width. */
.tt-wizard-rotation {
    margin-top: 20px;
}

/* Day duration: hours + minutes inputs with small unit affixes, side by side. */
.tt-duration-fields {
    display: flex;
    align-items: center;
    gap: 6px;
}

.tt-duration-input {
    width: 80px;
}

.tt-duration-unit {
    color: var(--sidan-text-muted);
}

/* Phase table (per-day wizard step): the "scheduled" checkbox column is centred
   and the duration/start inputs stay compact. */
.tt-phase-check-col {
    text-align: center;
    width: 1%;
    white-space: nowrap;
}

.tt-phase-table .form-check-input {
    margin: 2px 0 0;
}

/* Lunch row config (mode + corridor) shown inline in the phase cell, only while
   lunch is scheduled. Stacked under the phase name; wraps on narrow widths. */
.tt-lunch-phase {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* Expanded config rows (lunch, feedback) span two lines in the phase cell; bottom-
   aligning every cell keeps all input fields on one line with the inline controls.
   Scoped under the table class to beat ".admin-table tbody td { vertical-align:
   middle }". */
.tt-phase-table .tt-config-row > td {
    vertical-align: bottom;
}

/* …but the checkbox aligns with the phase name on the first line, not the
   inputs on the bottom line — nudged down 4px to sit centred on the name. */
.tt-phase-table .tt-config-row > td.tt-phase-check-col {
    vertical-align: top;
}

.tt-phase-table .tt-config-row > td.tt-phase-check-col .form-check-input {
    margin-top: 4px;
}

.tt-lunch-inline {
    display: flex;
    /* The corridor block carries a label on top, so bottom-align the row to
       keep the mode select and the time fields on one baseline. */
    align-items: flex-end;
    gap: 8px;
    flex-wrap: wrap;
}

.tt-lunch-inline .tt-lunch-mode {
    width: auto;
    min-width: 200px;
}

/* Lunch corridor: "Im Zeitraum" label stacked over the start–end fields. */
.tt-lunch-corridor {
    flex: 0 0 auto;
}

.tt-lunch-corridor-fields {
    display: flex;
    align-items: center;
    gap: 8px;
}

.tt-lunch-sep {
    color: var(--sidan-text-muted);
}

/* Create-timetable dialog: the counts and the per-day table stacked in one column. */
.tt-create-col {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.tt-count-input {
    max-width: 160px;
}

/* Grid marks: a session in an error finding carries a danger edge, a session
   with hints only the softer warning edge; the session the operator focused via
   the findings list is emphasized. */
.tt-session.tt-conflict {
    outline: 2px solid var(--sidan-danger);
    outline-offset: -2px;
}

.tt-session.tt-warning {
    outline: 2px solid var(--sidan-warning);
    outline-offset: -2px;
}

.tt-session.tt-focus {
    outline: 3px solid var(--sidan-danger);
    outline-offset: -3px;
    animation: tt-focus-flash 0.8s steps(1) 3;
}

.tt-session.tt-focus .tt-session-title,
.tt-session.tt-focus .tt-session-meta {
    animation: tt-focus-flash-text 0.8s steps(1) 3;
}

/* Flash the focused session's background in danger — the text flips to the
   on-danger ink in sync to stay readable — so the eye finds the session the
   operator just jumped to from the findings list. A danger outline remains
   afterwards until the next jump. */
@keyframes tt-focus-flash {
    50% { background: var(--sidan-danger); }
}

@keyframes tt-focus-flash-text {
    50% { color: var(--sidan-danger-ink); }
}

/* Idle-gap finding: it points at EMPTY space (the hole between sessions), so
   the warning lives on the gap's empty cells, not a session block. Blink them
   in the warning colour, then keep a soft warning tint so the gap stays found
   until the next jump. */
.tt-empty.tt-gap-focus {
    background: var(--sidan-warning-soft);
    animation: tt-gap-flash 0.8s steps(1) 3;
}

@keyframes tt-gap-flash {
    50% { background: var(--sidan-warning); }
}

/* ── Event overview: bulk-action bar + archive badge ──────────────────────
   Shown above the events table once at least one row is ticked; carries the
   selection count, a clear link and the archive / delete actions. The add
   control sits in the page header, so this bar only ever holds bulk actions. */
.admin-bulk-bar {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 16px;
    padding: 10px 16px;
    background: var(--sidan-color-primary-soft);
    border-radius: var(--sidan-radius-md);
}

.admin-bulk-count {
    font-size: var(--sidan-text-md);
    font-weight: var(--sidan-font-weight-bold);
    color: var(--sidan-text);
}

/* Actions hug the right edge; the count + clear link stay left. */
.admin-bulk-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-left: auto;
}

/* Small neutral badge appended to an archived event's date cell so the state
   is legible in the (archived) list without a dedicated status column. */
.admin-badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border-radius: var(--sidan-radius-pill);
    font-size: var(--sidan-text-sm);
    line-height: 1.4;
    white-space: nowrap;
}
.admin-badge-archived {
    background: var(--sidan-surface-sunk);
    color: var(--sidan-text-muted);
}

/* ── Bulk confirm dialog: generated confirmation code ─────────────────────
   The operator must retype the shown three-letter code before the confirm
   button unlocks. The code block is displayed large and spaced so it is easy
   to read and copy by hand. */
.admin-bulk-code-prompt {
    margin-bottom: 8px;
}
.admin-bulk-code {
    display: block;
    margin-bottom: 16px;
    padding: 12px 16px;
    background: var(--sidan-surface-sunk);
    border-radius: var(--sidan-radius-md);
    font-size: 1.75rem;
    font-weight: var(--sidan-font-weight-bold);
    letter-spacing: 0.35em;
    text-align: center;
    /* Trailing letter-spacing pushes the glyph off-centre; nudge it back. */
    text-indent: 0.35em;
    user-select: none;
}
.admin-bulk-code-input {
    text-transform: uppercase;
    letter-spacing: 0.2em;
}

/* ── Toolbar filter as a checkbox multi-select dropdown ───────────────────
   Used by the Demo filter on admin/projects. The trigger reuses .form-select
   so it carries the same box + caret as the sibling <select> filters; the
   panel + transparent click-away backdrop mirror the content-language picker
   (see .content-lang-menu / -backdrop). */
.admin-filter-dropdown {
    position: relative;
}
.admin-filter-toggle {
    text-align: left;
    cursor: pointer;
}
.admin-filter-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1040;
}
.admin-filter-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 1045;
    min-width: 100%;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    background: var(--sidan-surface);
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-sm);
    box-shadow: var(--sidan-shadow-lg);
}
/* Each row is a .form-check (global centred checkbox layout); only reset the
   Bootstrap margin and give the whole row a comfortable click target. */
.admin-filter-menu-item {
    margin: 0;
    padding: 4px 2px;
    cursor: pointer;
    white-space: nowrap;
}
/* Small muted heading separating dimensions when the menu groups several. The
   first heading loses its top spacing so the panel does not open with a gap. */
.admin-filter-menu-heading {
    margin-top: 8px;
    color: var(--sidan-text-muted);
    font-size: var(--sidan-text-sm);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.admin-filter-menu-heading:first-child {
    margin-top: 0;
}

/* ── Action dropdown (AdminDropdownButton) ────────────────────────────────
   A toolbar button that opens a short list of actions. Same open/close model
   as .admin-filter-dropdown (Blazor state + transparent click-away backdrop);
   only the panel content differs — plain action rows instead of check-boxes. */
.admin-dropdown {
    position: relative;
}
.admin-dropdown-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1040;
}
.admin-dropdown-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 1045;
    min-width: 100%;
    width: max-content;
    display: flex;
    flex-direction: column;
    padding: 6px 0;
    background: var(--sidan-surface);
    border: 1px solid var(--sidan-border);
    border-radius: var(--sidan-radius-sm);
    box-shadow: var(--sidan-shadow-lg);
}
/* Trigger at the right end of a toolbar: hang the menu off that edge instead. */
.admin-dropdown-menu-end {
    left: auto;
    right: 0;
}
.admin-dropdown-item {
    padding: 8px 18px;
    border: 0;
    background: none;
    color: inherit;
    font-size: var(--sidan-text-md);
    text-align: left;
    white-space: nowrap;
    cursor: pointer;
}
.admin-dropdown-item:hover,
.admin-dropdown-item:focus-visible {
    background: var(--sidan-surface-hover);
}
