/* ==========================================================================
   TESM Design-System -- Teil 2: Komponenten
   ========================================================================== */

@layer components {
  /* -- Karten ------------------------------------------------------------- */
  .card {
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
  }
  .card__header {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--border-subtle);
  }
  .card__header > .row { flex: 1; min-width: 0; }
  .card__title {
    font-size: var(--fs-md);
    font-weight: 640;
    letter-spacing: -0.008em;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
  }
  .card__sub {
    font-size: var(--fs-xs);
    color: var(--text-faint);
    margin-top: 2px;
  }
  .card__body { padding: var(--sp-5); }
  .card__body--flush { padding: 0; }
  .card__footer {
    display: flex;
    gap: var(--sp-2);
    justify-content: flex-end;
    padding: var(--sp-3) var(--sp-5);
    border-top: 1px solid var(--border-subtle);
    background: var(--surface-2);
    border-radius: 0 0 var(--r-lg) var(--r-lg);
  }
  .card--accent { border-color: color-mix(in srgb, var(--accent) 42%, var(--border)); }
  .card--danger { border-color: color-mix(in srgb, var(--danger) 42%, var(--border)); }

  /* -- Schaltflaechen ----------------------------------------------------- */
  .btn {
    --btn-bg: var(--surface-1);
    --btn-fg: var(--text);
    --btn-border: var(--border-strong);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    padding: 7px 14px;
    min-height: 34px;
    border: 1px solid var(--btn-border);
    border-radius: var(--r-sm);
    background: var(--btn-bg);
    color: var(--btn-fg);
    font-size: var(--fs-base);
    font-weight: 570;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
    transition: background var(--dur-fast), border-color var(--dur-fast),
      color var(--dur-fast), box-shadow var(--dur-fast), transform var(--dur-fast);
  }
  .btn:hover { background: var(--surface-3); text-decoration: none; }
  .btn:active { transform: translateY(1px); }
  .btn[disabled], .btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
  }
  .btn svg { width: 16px; height: 16px; flex-shrink: 0; stroke-width: 1.9; }

  .btn--primary {
    --btn-bg: var(--accent);
    --btn-fg: var(--accent-text);
    --btn-border: transparent;
    box-shadow: var(--shadow-accent);
  }
  .btn--primary:hover { --btn-bg: var(--accent-hover); }

  .btn--danger {
    --btn-bg: var(--danger);
    --btn-fg: #fff;
    --btn-border: transparent;
  }
  .btn--danger:hover { filter: brightness(1.08); background: var(--danger); }

  .btn--ghost {
    --btn-bg: transparent;
    --btn-border: transparent;
    --btn-fg: var(--text-muted);
  }
  .btn--ghost:hover { --btn-fg: var(--text); background: var(--surface-3); }

  .btn--subtle {
    --btn-bg: var(--surface-3);
    --btn-border: transparent;
  }

  .btn--sm { padding: 4px 10px; min-height: 28px; font-size: var(--fs-sm); }
  .btn--sm svg { width: 14px; height: 14px; }
  .btn--lg { padding: 10px 20px; min-height: 42px; font-size: var(--fs-md); }
  .btn--icon { padding: 7px; min-width: 34px; }
  .btn--icon.btn--sm { padding: 5px; min-width: 28px; }
  .btn--block { width: 100%; }

  .btn-group {
    display: inline-flex;
    border: 1px solid var(--border-strong);
    border-radius: var(--r-sm);
    overflow: hidden;
  }
  .btn-group .btn {
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  .btn-group .btn + .btn { border-left: 1px solid var(--border); }
  .btn-group .btn[aria-pressed="true"] {
    background: var(--accent-soft);
    color: var(--accent-soft-text);
  }

  /* -- Formulare ---------------------------------------------------------- */
  .field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
  .field__label {
    font-size: var(--fs-sm);
    font-weight: 570;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    gap: var(--sp-2);
  }
  .field__label .req { color: var(--danger); }
  .field__hint { font-size: var(--fs-xs); color: var(--text-faint); }
  .field__error { font-size: var(--fs-xs); color: var(--danger); font-weight: 550; }
  .field--inline { flex-direction: row; align-items: center; gap: var(--sp-3); }

  .input, .select, .textarea {
    width: 100%;
    padding: 8px 11px;
    min-height: 36px;
    background: var(--surface-1);
    border: 1px solid var(--border-strong);
    border-radius: var(--r-sm);
    color: var(--text);
    font-size: var(--fs-base);
    transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
  }
  .input:hover, .select:hover, .textarea:hover { border-color: var(--text-faint); }
  .input:focus, .select:focus, .textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-ring);
  }
  /* Der Knopf im Dateifeld kommt vom Browser und ignoriert jede Aussenfarbe:
     im dunklen Thema ein hellgraues Kaestchen mit dunkler Schrift, das weder
     zum Feld noch zu den uebrigen Knoepfen passt und kaum zu lesen war.
     Deshalb explizit wie ein Sekundaerknopf gestaltet -- und weil vier Felder
     betroffen sind (Sicherung, Lizenz, Profilbild, Freigaben), einmal hier. */
  .input[type="file"] {
    padding: 5px 6px;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    cursor: pointer;
    color: var(--text-muted);
  }
  .input[type="file"]::file-selector-button {
    padding: 5px 12px;
    margin-right: var(--sp-1);
    border: 1px solid var(--border-strong);
    border-radius: var(--r-sm);
    background: var(--surface-2);
    color: var(--text);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: 570;
    cursor: pointer;
    transition: background var(--dur-fast), border-color var(--dur-fast);
  }
  .input[type="file"]:hover::file-selector-button {
    background: var(--surface-3);
    border-color: var(--text-faint);
  }
  .input[type="file"]:focus-visible::file-selector-button {
    border-color: var(--accent);
  }

  .input[readonly], .input[disabled], .select[disabled], .textarea[disabled] {
    background: var(--surface-inset);
    color: var(--text-muted);
    cursor: not-allowed;
  }
  .input[aria-invalid="true"] { border-color: var(--danger); }
  .textarea { min-height: 96px; resize: vertical; line-height: var(--lh-normal); }
  .input--mono { font-family: var(--font-mono); font-size: var(--fs-sm); }
  .select {
    appearance: none;
    padding-right: 32px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238f96a6' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    background-size: 15px;
  }

  .input-group { display: flex; align-items: stretch; }
  .input-group .input { border-radius: var(--r-sm) 0 0 var(--r-sm); }
  .input-group .btn { border-radius: 0 var(--r-sm) var(--r-sm) 0; border-left: 0; }

  .search-input {
    position: relative;
    display: flex;
    align-items: center;
    min-width: 180px;
  }
  .search-input svg {
    position: absolute;
    left: 10px;
    width: 15px;
    height: 15px;
    color: var(--text-faint);
    pointer-events: none;
  }
  .search-input .input { padding-left: 32px; }

  .check {
    display: inline-flex;
    align-items: flex-start;
    gap: var(--sp-2);
    cursor: pointer;
    font-size: var(--fs-base);
    line-height: 1.35;
  }
  .check input[type="checkbox"], .check input[type="radio"] {
    appearance: none;
    flex-shrink: 0;
    width: 17px;
    height: 17px;
    margin: 1px 0 0;
    border: 1.5px solid var(--border-strong);
    border-radius: 5px;
    background: var(--surface-1);
    cursor: pointer;
    transition: background var(--dur-fast), border-color var(--dur-fast);
  }
  .check input[type="radio"] { border-radius: 50%; }
  .check input:checked {
    background: var(--accent);
    border-color: var(--accent);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
    background-size: 13px;
    background-position: center;
    background-repeat: no-repeat;
  }
  .check input[type="radio"]:checked {
    background-image: none;
    box-shadow: inset 0 0 0 4px var(--surface-1);
  }
  .check input:disabled { opacity: 0.42; cursor: not-allowed; }
  .check input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .check--disabled { color: var(--text-faint); cursor: not-allowed; }

  .switch { display: inline-flex; align-items: center; gap: var(--sp-3); cursor: pointer; }
  .switch input {
    appearance: none;
    width: 38px;
    height: 22px;
    border-radius: var(--r-full);
    background: var(--border-strong);
    position: relative;
    cursor: pointer;
    transition: background var(--dur) var(--ease);
    flex-shrink: 0;
  }
  .switch input::after {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #fff;
    box-shadow: var(--shadow-xs);
    transition: transform var(--dur) var(--ease);
  }
  .switch input:checked { background: var(--accent); }
  .switch input:checked::after { transform: translateX(16px); }
  .switch input:disabled { opacity: 0.45; cursor: not-allowed; }

  fieldset.fieldset {
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    padding: var(--sp-4);
    margin: 0;
  }
  fieldset.fieldset > legend {
    padding: 0 var(--sp-2);
    font-size: var(--fs-sm);
    font-weight: 620;
    color: var(--text-muted);
  }

  .form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: var(--sp-4);
  }
  .form-grid--wide { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
  .form-grid > .field--full { grid-column: 1 / -1; }

  /* -- Tabellen ----------------------------------------------------------- */
  .table-wrap {
    overflow-x: auto;
    border-radius: var(--r-lg);
  }
  .table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--fs-base);
  }
  .table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: 9px var(--sp-4);
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
    color: var(--text-faint);
    font-size: var(--fs-xs);
    font-weight: 650;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    text-align: left;
    white-space: nowrap;
  }
  .table tbody td {
    padding: 10px var(--sp-4);
    border-bottom: 1px solid var(--border-subtle);
    vertical-align: middle;
  }
  .table tbody tr:last-child td { border-bottom: 0; }
  .table tbody tr:hover { background: var(--surface-2); }
  .table th.sortable { cursor: pointer; user-select: none; }
  .table th.sortable:hover { color: var(--text); }
  .table th.sortable::after {
    content: "\2195";
    margin-left: 5px;
    opacity: 0.4;
    font-size: 1.05em;
  }
  .table th[aria-sort="ascending"]::after { content: "\2191"; opacity: 1; color: var(--accent); }
  .table th[aria-sort="descending"]::after { content: "\2193"; opacity: 1; color: var(--accent); }
  .table td.numeric, .table th.numeric { text-align: right; font-variant-numeric: tabular-nums; }
  .table td.actions { text-align: right; white-space: nowrap; }
  .table td.mono { font-family: var(--font-mono); font-size: var(--fs-sm); }
  .table--compact tbody td { padding: 6px var(--sp-3); }
  .table--fixed { table-layout: fixed; }

  .table-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--border-subtle);
  }
  .table-toolbar__spacer { flex: 1; }

  /* -- Statusanzeigen ----------------------------------------------------- */
  .pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 9px;
    border-radius: var(--r-full);
    background: var(--neutral-pill);
    color: var(--neutral-pill-text);
    font-size: var(--fs-xs);
    font-weight: 650;
    white-space: nowrap;
    line-height: 1.5;
  }
  .pill::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
  }
  .pill--plain::before { display: none; }
  .pill--online, .pill--ok, .pill--success, .pill--green {
    background: var(--success-soft);
    color: var(--success);
  }
  .pill--offline, .pill--error, .pill--danger, .pill--red {
    background: var(--danger-soft);
    color: var(--danger);
  }
  /* Gefuellt statt getoent: "kritisch" muss sich von "Warnung" unterscheiden,
     und beide sind rot. */
  .pill--critical {
    background: var(--danger);
    /* Weiss wie bei .btn--danger. Vorher stand hier ein Token, das es nicht
       gibt -- die Deklaration war damit unwirksam, und die Schrift blieb in
       der Textfarbe: dunkelgrau auf Rot. */
    color: #fff;
    font-weight: 700;
  }
  .pill--critical::before { background: currentColor; }

  .pill--warning, .pill--unknown, .pill--orange {
    background: var(--warning-soft);
    color: var(--warning);
  }
  .pill--info, .pill--blue { background: var(--info-soft); color: var(--info); }
  .pill--accent, .pill--admin { background: var(--accent-soft); color: var(--accent-soft-text); }
  .pill--lg { padding: 5px 12px; font-size: var(--fs-sm); }

  .status-pill {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 12px;
    border-radius: var(--r-full);
    background: var(--surface-1);
    border: 1px solid var(--border);
    font-size: var(--fs-sm);
    font-weight: 570;
    color: var(--text-muted);
    white-space: nowrap;
  }
  .status-pill__dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--success);
    flex-shrink: 0;
  }
  /* Der Knopf sitzt *in* der Pille: kein Rahmen, keine Flaeche, nur das
     Symbol in der Farbe der Pille. Ein Standardknopf wuerde sie sprengen. */
  .status-pill__action { display: inline-flex; }
  .status-pill__action button {
    display: inline-flex;
    align-items: center;
    padding: 0;
    margin-left: 2px;
    border: 0;
    background: none;
    color: inherit;
    opacity: 0.65;
    cursor: pointer;
  }
  .status-pill__action button:hover { opacity: 1; }

  .status-pill__dot--pulse { animation: pulse-ring 2.4s var(--ease) infinite; }
  .status-pill--danger { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); color: var(--danger); }
  .status-pill--danger .status-pill__dot { background: var(--danger); }
  .status-pill--warning { border-color: color-mix(in srgb, var(--warning) 45%, var(--border)); color: var(--warning); }
  .status-pill--warning .status-pill__dot { background: var(--warning); }

  @keyframes pulse-ring {
    0%   { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
    70%  { box-shadow: 0 0 0 6px transparent; opacity: 0.85; }
    100% { box-shadow: 0 0 0 0 transparent; opacity: 1; }
  }

  .badge {
    display: inline-flex;
    align-items: center;
    padding: 1px 7px;
    border-radius: var(--r-xs);
    background: var(--surface-3);
    color: var(--text-muted);
    font-size: var(--fs-xs);
    font-weight: 620;
    font-family: var(--font-mono);
  }

  .avatar {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--accent-soft);
    color: var(--accent-soft-text);
    font-size: var(--fs-sm);
    font-weight: 700;
    object-fit: cover;
    overflow: hidden;
  }
  .avatar--lg { width: 56px; height: 56px; font-size: var(--fs-lg); }
  .avatar--sm { width: 24px; height: 24px; font-size: var(--fs-xs); }

  /* -- Kennzahlen --------------------------------------------------------- */
  .stat {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: var(--sp-4);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    transition: border-color var(--dur-fast), transform var(--dur-fast);
  }
  a.stat:hover, button.stat:hover {
    border-color: var(--accent);
    text-decoration: none;
    transform: translateY(-1px);
  }
  .stat[aria-pressed="true"] {
    border-color: var(--accent);
    background: var(--accent-soft);
  }
  .stat__label {
    font-size: var(--fs-xs);
    font-weight: 620;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-faint);
  }
  .stat__value {
    font-size: var(--fs-2xl);
    font-weight: 680;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
  }
  .stat__hint { font-size: var(--fs-xs); color: var(--text-faint); }
  .stat--ok .stat__value { color: var(--success); }
  .stat--danger .stat__value { color: var(--danger); }
  .stat--warning .stat__value { color: var(--warning); }
  .stat--muted .stat__value { color: var(--text-faint); }
  button.stat { cursor: pointer; text-align: left; font: inherit; }

  .progress {
    height: 6px;
    border-radius: var(--r-full);
    background: var(--surface-3);
    overflow: hidden;
  }
  .progress__bar {
    height: 100%;
    border-radius: inherit;
    background: var(--accent);
    transition: width var(--dur-slow) var(--ease);
  }
  .progress--success .progress__bar { background: var(--success); }
  .progress--danger .progress__bar { background: var(--danger); }

  /* -- Hinweise ----------------------------------------------------------- */
  .alert {
    display: flex;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-md);
    border: 1px solid var(--border);
    background: var(--surface-2);
    font-size: var(--fs-base);
    line-height: var(--lh-normal);
  }
  .alert svg { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; }
  .alert__body { min-width: 0; flex: 1; }
  .alert__title { font-weight: 640; margin-bottom: 2px; }
  .alert--info { background: var(--info-soft); border-color: transparent; color: var(--text); }
  .alert--info svg { color: var(--info); }
  .alert--success { background: var(--success-soft); border-color: transparent; }
  .alert--success svg { color: var(--success); }
  .alert--warning { background: var(--warning-soft); border-color: transparent; }
  .alert--warning svg { color: var(--warning); }
  .alert--danger { background: var(--danger-soft); border-color: transparent; }
  .alert--danger svg { color: var(--danger); }

  .empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-12) var(--sp-5);
    text-align: center;
    color: var(--text-muted);
  }
  .empty svg { width: 34px; height: 34px; color: var(--text-faint); stroke-width: 1.4; }
  .empty__title { font-size: var(--fs-md); font-weight: 620; color: var(--text); }
  .empty p { max-width: 46ch; font-size: var(--fs-base); }

  /* -- Toasts ------------------------------------------------------------- */
  .toast-stack {
    position: fixed;
    top: var(--sp-4);
    right: var(--sp-4);
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    width: min(380px, calc(100vw - var(--sp-8)));
    pointer-events: none;
  }
  .toast {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-lg);
    font-size: var(--fs-base);
    pointer-events: auto;
    animation: toast-in var(--dur) var(--ease);
  }
  .toast--success { border-left-color: var(--success); }
  .toast--danger { border-left-color: var(--danger); }
  .toast--warning { border-left-color: var(--warning); }
  .toast--info { border-left-color: var(--info); }
  .toast__body { flex: 1; min-width: 0; line-height: var(--lh-normal); }
  .toast__close {
    flex-shrink: 0;
    padding: 0;
    width: 20px;
    height: 20px;
    border: 0;
    background: none;
    color: var(--text-faint);
    cursor: pointer;
    line-height: 1;
    font-size: 17px;
  }
  .toast__close:hover { color: var(--text); }
  .toast.is-leaving { animation: toast-out 160ms var(--ease) forwards; }

  @keyframes toast-in {
    from { opacity: 0; transform: translateX(14px) scale(0.98); }
    to { opacity: 1; transform: none; }
  }
  @keyframes toast-out {
    to { opacity: 0; transform: translateX(14px) scale(0.98); }
  }

  /* -- Dialoge ------------------------------------------------------------ */
  dialog.modal {
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    background: var(--surface-1);
    color: var(--text);
    box-shadow: var(--shadow-lg);
    width: min(560px, calc(100vw - var(--sp-8)));
    max-height: calc(100dvh - var(--sp-12));
    overflow: visible;
  }
  dialog.modal--wide { width: min(880px, calc(100vw - var(--sp-8))); }
  dialog.modal--narrow { width: min(420px, calc(100vw - var(--sp-8))); }
  dialog.modal::backdrop {
    background: var(--surface-overlay);
    backdrop-filter: blur(3px);
  }
  dialog.modal[open] { animation: modal-in var(--dur) var(--ease); }
  @keyframes modal-in {
    from { opacity: 0; transform: translateY(8px) scale(0.985); }
    to { opacity: 1; transform: none; }
  }
  .modal__header {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--border-subtle);
  }
  .modal__title { font-size: var(--fs-md); font-weight: 640; flex: 1; }
  .modal__sub { font-size: var(--fs-xs); color: var(--text-faint); margin-top: 2px; }
  /* Eine Datei-Vorschau soll die ganze Flaeche des Dialogs nutzen; der
     uebliche Innenabstand des Dialogkoerpers wuerde sie einrahmen. */
  /* Zwei Klassen am selben Element: diese hier muss gewinnen, obwohl
     .modal__body weiter unten steht. Mit der doppelten Klasse haengt das
     nicht mehr an der Reihenfolge im Stylesheet. */
  .modal__body.modal__body--flush { padding: 0; max-height: none; overflow: visible; }
  .modal__caption {
    padding: 0 var(--sp-5) var(--sp-2);
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    word-break: break-all;
  }
  .file-preview {
    display: block;
    width: 100%;
    height: min(70dvh, 760px);
    border: 0;
    /* Die Vorschau zeigt fremde Dokumente; ein neutraler Grund gehoert
       darunter. --surface-0 gibt es nicht, der Rahmen blieb durchsichtig. */
    background: var(--surface-1);
  }

  /* -- Betrachter: Bild, PDF, Rahmen ------------------------------------- */
  /* Eine feste Hoehe fuer alle drei Anzeigen: sonst springt der Dialog beim
     Wechsel zwischen Foto und PDF. */
  .viewer {
    position: relative;
    display: flex;
    flex-direction: column;
    /* Was der Dialog uebrig laesst: Kopf, Dateiname und Fusszeile brauchen
       zusammen rund 190 Pixel. Ein fester Anteil der Fensterhoehe schob die
       Zoomleiste auf kleinen Fenstern unter den Rand. */
    height: min(calc(100dvh - 240px), 760px);
    min-height: 220px;
    background: var(--surface-1);
  }
  /* Hochkant -- Tablet oder Monitor im Hochformat -- gibt es unter dem Dialog
     viel Platz, den die Obergrenze verschenkt. Beim Ausfuellen eines
     Formulars zaehlt jede sichtbare Zeile, also nehmen wir dort die Hoehe,
     die da ist. Der Rest (Kopf, Dateiname, Fusszeile) braucht rund 200 Pixel;
     die 240 lassen etwas Luft. */
  @media (orientation: portrait) {
    .viewer {
      height: calc(100dvh - 240px);
    }
  }
  .viewer > .file-preview {
    flex: 1;
    height: auto;
  }
  .viewer__stage {
    position: relative;
    flex: 1;
    /* Ohne min-height waechst die Buehne mit dem Bild, und dessen
       max-height:100% haengt dann an sich selbst: ein 4032x3024-Foto stand
       36 Pixel ueber den Rand hinaus. */
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    /* Die Seite darf nicht mitwandern, wenn im Bild geschoben wird. */
    touch-action: none;
    cursor: zoom-in;
  }
  .viewer__stage--gezogen {
    cursor: grab;
  }
  .viewer__img {
    /* Der Ausgangszustand ist "passt in die Flaeche" -- genau das fehlte:
       ein Foto vom iPad ist 4032 Pixel breit und wurde im Rahmen 1:1
       gezeigt, also als Ausschnitt aus der Mitte. */
    max-width: 100%;
    max-height: 100%;
    /* Zoomen soll nicht unscharf wirken, solange nicht vergroessert wird. */
    transform-origin: center center;
    will-change: transform;
    user-select: none;
    -webkit-user-drag: none;
  }
  /* pdf.js verlangt einen absolut positionierten, scrollenden Behaelter --
     der liegt deshalb *innen*. Aussen ein gewoehnlicher Flex-Kasten, damit
     Zoomleiste und Speicherzeile ihren Platz behalten. Vorher rechnete der
     Bereich mit "bottom: var(--sp-10)", also mit genau einer Leiste; die
     zweite Zeile lag dann unter dem Dialogrand. */
  .viewer__pdf {
    position: relative;
    flex: 1;
    min-height: 0;
    /* Ein PDF ist weisses Papier. pdf.js malt Formularfelder mit Systemfarben
       und ueberlaesst Ankreuzfelder dem Browser -- im dunklen Design heisst
       CanvasText weiss, und die Haken waren auf dem Papier unsichtbar. Fuer
       diesen Ausschnitt gilt deshalb immer das helle Schema. */
    color-scheme: light;
  }
  /* pdf.js rechnet fuer die Seite mit dem Standard "content-box": ihr
     9-Pixel-Rahmen liegt *ausserhalb* der angegebenen Breite. Unser globales
     "border-box" rechnete ihn hinein -- die Leinwand wurde 18 Pixel schmaler
     als die Anmerkungsebene, und die Formularfelder wanderten nach rechts
     unten, immer weiter je weiter unten sie lagen. */
  .viewer__pdf .pdfViewer .page {
    box-sizing: content-box;
  }
  .viewer__pdfscroll {
    position: absolute;
    inset: 0;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
  }
  .viewer__bar {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-4);
    border-top: 1px solid var(--border);
    background: var(--surface-2, var(--surface-1));
  }
  .viewer__hint {
    margin-inline-start: auto;
  }
  /* Bildfeld eines Formulars: der Kasten zeigt das gewaehlte Foto, die
     Schaltflaeche liegt darin. Acrobats eigener Knopf tut im Browser nichts --
     das hier ist der Ersatz. */
  .viewer__bildfeld [data-viewer-bildvorschau] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: #fff;
  }
  .viewer__bildwahl {
    position: absolute;
    inset: auto 0 4px 0;
    display: flex;
    justify-content: center;
    cursor: pointer;
  }
  .viewer__bildwahl > span {
    padding: 4px 10px;
    border-radius: var(--r-sm, 6px);
    background: var(--accent);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: 600;
    box-shadow: var(--shadow-sm, 0 1px 3px rgb(0 0 0 / 0.3));
  }
  .viewer__bildfeld--belegt .viewer__bildwahl > span {
    opacity: 0.85;
  }
  .viewer__save {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--border);
    background: var(--surface-2, var(--surface-1));
  }
  .viewer__save .viewer__feld {
    flex: 1 1 12rem;
    margin: 0;
  }
  /* Die Entscheidung steht rechts und der Haken darueber: er gehoert zum
     Speichern, nicht zu den Feldern davor. */
  .viewer__saveaktionen {
    margin-left: auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--sp-2);
  }
  .viewer__saveknoepfe {
    display: flex;
    gap: var(--sp-2);
  }
  /* Nach "gibt es schon": der Haken ist die Antwort -- also faellt er auf. */
  .viewer__saveaktionen .check.is-gefragt {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: var(--r-xs);
  }
  /* Eigener Name: ".field--inline" gibt es schon und steht dort fuer
     "waagerecht" -- diese Regel haette sie app-weit umgestellt. */
  .viewer__feld {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  /* Der Dateiname ist ein Knopf (er oeffnet die Vorschau), soll aber wie ein
     Link aussehen -- ein Knopf mit Rahmen mitten in einer Tabellenzeile waere
     unruhig. */
  .file-name {
    background: none;
    border: 0;
    padding: 0;
    color: var(--accent);
    font: inherit;
    text-align: left;
    cursor: pointer;
  }
  .file-name:hover { text-decoration: underline; }

  .modal__body {
    padding: var(--sp-5);
    overflow-y: auto;
    max-height: min(64dvh, 640px);
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
  }
  .modal__footer {
    display: flex;
    gap: var(--sp-2);
    justify-content: flex-end;
    padding: var(--sp-3) var(--sp-5);
    border-top: 1px solid var(--border-subtle);
    background: var(--surface-2);
    border-radius: 0 0 var(--r-lg) var(--r-lg);
  }
  .modal__footer--split { justify-content: space-between; }

  /* -- Tabs --------------------------------------------------------------- */
  .tabs {
    display: flex;
    gap: var(--sp-1);
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
  }
  .tab {
    padding: 9px var(--sp-3);
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--text-muted);
    font-size: var(--fs-base);
    font-weight: 570;
    white-space: nowrap;
    cursor: pointer;
  }
  .tab:hover { color: var(--text); text-decoration: none; }
  .tab[aria-selected="true"], .tab.is-active {
    color: var(--accent);
    border-bottom-color: var(--accent);
  }

  /* -- Befehlspalette ----------------------------------------------------- */
  dialog.palette {
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    background: var(--surface-1);
    box-shadow: var(--shadow-lg);
    width: min(560px, calc(100vw - var(--sp-6)));
    margin-top: 10vh;
    overflow: hidden;
  }
  dialog.palette::backdrop { background: var(--surface-overlay); backdrop-filter: blur(3px); }
  .palette__input {
    width: 100%;
    padding: var(--sp-4) var(--sp-5);
    border: 0;
    border-bottom: 1px solid var(--border-subtle);
    background: none;
    color: var(--text);
    font-size: var(--fs-md);
  }
  .palette__input:focus { outline: none; }
  .palette__list { max-height: 46dvh; overflow-y: auto; padding: var(--sp-2); }
  .palette__item {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    width: 100%;
    padding: 9px var(--sp-3);
    border: 0;
    border-radius: var(--r-sm);
    background: none;
    color: var(--text);
    font-size: var(--fs-base);
    text-align: left;
    cursor: pointer;
  }
  .palette__item svg { width: 16px; height: 16px; color: var(--text-faint); flex-shrink: 0; }
  .palette__item:hover, .palette__item.is-active { background: var(--accent-soft); color: var(--accent-soft-text); }
  .palette__item.is-active svg { color: currentColor; }
  .palette__group { margin-left: auto; font-size: var(--fs-xs); color: var(--text-faint); }
  .palette__hint {
    padding: var(--sp-2) var(--sp-5);
    border-top: 1px solid var(--border-subtle);
    background: var(--surface-2);
    font-size: var(--fs-xs);
    color: var(--text-faint);
    display: flex;
    gap: var(--sp-4);
  }

  kbd {
    display: inline-block;
    padding: 1px 5px;
    border: 1px solid var(--border-strong);
    border-bottom-width: 2px;
    border-radius: var(--r-xs);
    background: var(--surface-2);
    color: var(--text-muted);
    font-size: var(--fs-xs);
    font-family: var(--font-mono);
    line-height: 1.5;
  }

  /* -- Rechtematrix ------------------------------------------------------- */
  .matrix { width: 100%; border-collapse: separate; border-spacing: 0; }
  .matrix th, .matrix td { padding: 7px var(--sp-3); border-bottom: 1px solid var(--border-subtle); }
  .matrix thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface-2);
    font-size: var(--fs-xs);
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-faint);
    text-align: center;
  }
  .matrix thead th:first-child { text-align: left; }
  /* Kuerzel, englischer Name, deutsche Beschriftung -- untereinander. Nur das
     Kuerzel allein war eine Spalte, die man nachschlagen musste; die Legende
     steht unter einer Tabelle, die man dafuer erst zu Ende scrollt. */
  .matrix__spalte { display: flex; flex-direction: column; align-items: center; line-height: 1.15; }
  .matrix__spalte b {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    color: var(--text);
    font-weight: 650;
  }
  .matrix__spalte span { font-size: var(--fs-xs); color: var(--text-muted); text-transform: none; }
  .matrix__spalte small {
    font-size: 10px;
    color: var(--text-faint);
    text-transform: none;
    letter-spacing: 0;
    font-weight: 400;
  }
  .matrix td:not(:first-child), .matrix th:not(:first-child) { width: 72px; text-align: center; }
  .matrix tr.matrix__area > td {
    background: var(--surface-2);
    font-weight: 640;
  }
  .matrix tr.matrix__area > td:first-child { border-radius: var(--r-sm) 0 0 var(--r-sm); }
  .matrix tr.is-locked td:not(.matrix__killswitch) { opacity: 0.42; }
  /* Was es fuer eine Zeile nicht gibt: dunkler und eingesenkt statt heller.
     Vorher war es ein natives Kaestchen -- im dunklen Thema ein helles Quadrat,
     das kraeftiger wirkte als ein echtes, nicht gesetztes Recht. */
  .matrix__na {
    display: inline-block;
    width: 17px;
    height: 17px;
    border-radius: 5px;
    /* Dunkler als die Karte -- genau das war die Absicht: "dunkler heisst,
       es gibt das Recht nicht". Mit dem nicht existierenden --surface-0 war
       das Feld durchsichtig und damit so hell wie die Karte. */
    background: var(--canvas);
    border: 1px solid var(--border-subtle);
    box-shadow: inset 0 1px 2px rgb(0 0 0 / 28%);
    vertical-align: -3px;
    opacity: 0.55;
  }
  .matrix__resource-label { display: flex; flex-direction: column; gap: 1px; }
  .matrix__resource-label small { color: var(--text-faint); font-size: var(--fs-xs); }
  .matrix__zeile { display: flex; align-items: baseline; gap: var(--sp-2); }
  .matrix__zeile b { font-weight: 600; }

  /* Unterzeile: gehoert zur Zeile darueber. Ohne Einrueckung liest man sechs
     unabhaengige Dinge, wo sechs Teile eines Ganzen stehen. */
  .matrix tr.matrix__unterzeile > td:first-child { padding-left: var(--sp-6); }
  .matrix tr.matrix__unterzeile > td:first-child .matrix__zeile b { font-weight: 500; }

  /* Was eine Aktion in dieser Zeile bedeutet -- in der Zeile, nicht im
     Titelattribut: mit dem Finger gibt es kein "hover". */
  .matrix__bedeutung { color: var(--text-faint); font-size: var(--fs-xs); }
  .matrix__bedeutung b {
    font-family: var(--font-mono);
    color: var(--text-muted);
    font-weight: 500;
  }

  .matrix__alle {
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-muted);
    border-radius: var(--r-sm);
    font-size: 10px;
    line-height: 1;
    padding: 2px 4px;
    cursor: pointer;
  }
  .matrix__alle:hover { color: var(--accent); border-color: var(--accent); }

  .matrix__filter {
    display: flex;
    align-items: baseline;
    gap: var(--sp-3);
    margin-bottom: var(--sp-3);
    flex-wrap: wrap;
  }
  .matrix__filter-hint { color: var(--text-faint); font-size: var(--fs-xs); }
  /* Eine ausgefilterte Zeile ist versteckt, nicht entfernt: ihre Kaestchen
     bleiben im Formular, sonst loescht ein Filter beim Speichern Rechte. */
  .matrix tr[hidden] { display: none; }
  .matrix__legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    font-size: var(--fs-xs);
    color: var(--text-muted);
  }
  .matrix__legend b { color: var(--text); font-family: var(--font-mono); }

  /* -- Sonstiges ---------------------------------------------------------- */
  .code-block {
    margin: 0;
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface-inset);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-sm);
    font-size: var(--fs-sm);
    line-height: 1.55;
    overflow-x: auto;
    white-space: pre;
    tab-size: 2;
  }
  .code-block--wrap { white-space: pre-wrap; word-break: break-all; }

  .log-view {
    margin: 0;
    padding: var(--sp-3) var(--sp-4);
    max-height: 62dvh;
    overflow: auto;
    background: var(--surface-inset);
    border-radius: 0 0 var(--r-lg) var(--r-lg);
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    line-height: 1.55;
    white-space: pre-wrap;
    word-break: break-word;
  }
  /* ---------------------------------------------------------------------- #
     Aufklappbares
     ---------------------------------------------------------------------- #

     Der native Marker eines <summary> verschwindet, sobald das Element ein
     Flex-Container ist -- und genau das sind unsere Kartenkoepfe. Auf der
     DHCP-Seite stand der Optionskatalog deshalb als Kasten da, dem man nicht
     ansah, dass er sich oeffnen laesst.

     Der Pfeil wird deshalb selbst gezeichnet: als Flex- bzw. Inline-Element,
     das in beiden Faellen erscheint, in Akzentfarbe (das ist die Farbe, mit
     der diese Oberflaeche "hier kannst du etwas tun" sagt) und mit einer
     Drehung beim Oeffnen. Eine Stelle fuer alle Aufklapper -- Karten,
     Vorschauen, Abschnitte. */
  details > summary {
    list-style: none;
    cursor: pointer;
  }
  details > summary::-webkit-details-marker { display: none; }
  details:not(.nav-tree) > summary::before {
    content: "";
    display: inline-block;
    flex: 0 0 auto;
    align-self: center;
    width: 0;
    height: 0;
    margin-right: var(--sp-2);
    border-left: 6px solid var(--accent);
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
    transition: transform var(--dur) var(--ease);
  }
  details:not(.nav-tree)[open] > summary::before { transform: rotate(90deg); }
  /* Der Navigationsbaum bringt seinen eigenen Winkel mit -- der bekommt
     dieselbe Farbe, damit "aufklappbar" ueberall gleich aussieht. */
  .nav-tree__chevron { color: var(--accent); }

  .log-view mark { background: var(--warning-soft); color: var(--warning); border-radius: 3px; }

  /* Stufen im laufenden Protokoll. Die Zuordnung der Farbrollen ist Absicht:
     --warning ist bernsteinfarben und traegt den HINWEIS, --danger ist rot und
     traegt die WARNUNG. Wer das nach den Namen "aufraeumt", dreht die Bedeutung
     um: ein Geraet, das nicht antwortet, waere dann orange und eines, das neu
     startet, rot. */
  .log-view__zeile--info { color: var(--success); }
  .log-view__zeile--hinweis { color: var(--warning); }
  .log-view__zeile--warnung { color: var(--danger); }
  .log-view__zeile--fehler { color: var(--danger); font-weight: 600; }

  .kv { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-2) var(--sp-4); font-size: var(--fs-base); }
  .kv dt { color: var(--text-faint); white-space: nowrap; }
  .kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

  .breadcrumb { display: flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-faint); }
  .breadcrumb a { color: var(--text-muted); }
  .breadcrumb span::after { content: "/"; margin-left: 6px; opacity: 0.5; }
  .breadcrumb > :last-child::after { content: none; }

  .divider { height: 1px; background: var(--border-subtle); margin: var(--sp-2) 0; }

  .hint-icon {
    display: inline-grid;
    place-items: center;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: var(--surface-3);
    color: var(--text-faint);
    font-size: 10px;
    font-weight: 700;
    cursor: help;
    flex-shrink: 0;
  }
  .hint-icon:hover { background: var(--accent-soft); color: var(--accent-soft-text); }

  .skeleton {
    border-radius: var(--r-sm);
    background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 50%, var(--surface-3) 75%);
    background-size: 200% 100%;
    animation: shimmer 1.4s linear infinite;
    min-height: 1em;
  }
  @keyframes shimmer {
    from { background-position: 200% 0; }
    to { background-position: -200% 0; }
  }

  .spin { animation: spin 900ms linear infinite; }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* -- Anmeldeseite ------------------------------------------------------- */
  .auth-page {
    min-height: 100dvh;
    display: grid;
    place-items: center;
    padding: var(--sp-6);
    /* Zwei warme Kreise, Geometrie und Staerke aus der Fassung 1.x
       uebernommen: oben links deutlich, unten rechts angedeutet. Die vorige
       Fassung setzte den zweiten Kreis auf --info und 10 % -- ein kaltes
       Blau, das man auf dunklem Grund nicht sah und das nicht zur Marke
       gehoert. Ueber color-mix statt festem rgba, damit es im hellen Thema
       nicht schmutzig wird. */
    background:
      radial-gradient(circle at 15% 15%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 45%),
      radial-gradient(circle at 85% 85%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 40%),
      var(--canvas);
  }
  /* Die Wortmarke liegt hinter allem (z-index 0), die Anmeldebox davor.
     Sie ist absichtlich nicht vertikal zentriert: die Box sitzt hoeher und
     steht dadurch vor dem oberen Teil des Motivs -- so war es im Vorgaenger,
     und so bleibt die Funktionszeile darunter lesbar. */
  /* ---------------------------------------------------------------------- #
     Geraetekachel der Statusuebersicht

     Der Zustand traegt die Kachel: er faerbt die Kante links, das Symbolfeld
     und die Plakette. Die Beschriftung steht trotzdem daneben -- Farbe allein
     unterscheidet nicht fuer jeden. Der Name steht in der Monoschrift, weil
     Geraetenamen Kennungen sind und in einer Liste untereinander lesbar
     bleiben muessen (FRM02 / FRM03 / FRM032).
     ---------------------------------------------------------------------- */
  .tile {
    --tile-accent: var(--text-faint);
    position: relative;
    overflow: hidden;
    /* Spalte, damit die Fusszeile unten haengen kann (siehe .tile__actions).
       Die Kacheln eines Rasters sind gleich hoch, ihr Inhalt aber nicht: mal
       fehlt der Switch, mal der Standort. Ohne diesen Halt stuenden die Knoepfe
       in jeder Kachel auf einer anderen Hoehe -- und das Auge sucht sie. */
    display: flex;
    flex-direction: column;
    padding: var(--sp-4);
    padding-left: calc(var(--sp-4) + 3px);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background:
      linear-gradient(180deg,
        color-mix(in srgb, var(--tile-accent) 6%, transparent),
        transparent 62%),
      var(--surface-1);
  }
  .tile::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 3px;
    background: var(--tile-accent);
  }
  .tile--online { --tile-accent: var(--success); }
  .tile--restarting { --tile-accent: var(--warning); }
  .tile--offline { --tile-accent: var(--danger); }
  .tile--unknown { --tile-accent: var(--warning); }
  .tile--disabled { --tile-accent: var(--text-faint); }

  .tile__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
  }
  .tile__badge {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: color-mix(in srgb, var(--tile-accent) 15%, transparent);
    border: 1px solid color-mix(in srgb, var(--tile-accent) 30%, transparent);
    color: var(--tile-accent);
  }
  .tile__name {
    margin-top: var(--sp-3);
    font-family: var(--font-mono);
    font-weight: 800;
    font-size: var(--fs-md);
    letter-spacing: 0.3px;
  }
  .tile__ip {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: color-mix(in srgb, var(--tile-accent) 72%, var(--text-faint));
  }
  .tile__meta {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 2px;
    font-size: var(--fs-xs);
    color: var(--text-faint);
  }

  /* Mitlesen auf der Wartungsseite: die Ausgabe eines laufenden Auftrags,
     direkt unter seinem Geraet. Dunkler Grund wie in jeder Konsole -- was
     dort steht, kommt von einer fremden Maschine und nicht aus dieser
     Oberflaeche. */
  .job-output {
    margin: 0;
    max-height: 220px;
    overflow: auto;
    padding: var(--sp-3) var(--sp-4);
    background: #101216;
    color: #d7dbe0;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
  }

  /* ---------------------------------------------------------------------- #
     Bildschirmsitzung (VNC)

     Die Leinwand bekommt einen dunklen Grund -- unabhaengig vom Thema der
     Oberflaeche: was darauf erscheint, ist der Bildschirm eines fremden
     Geraets, und ein heller Rahmen darum laesst ihn wie einen Teil dieser
     Seite aussehen. Er ist keiner.
     ---------------------------------------------------------------------- */
  .screen {
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: #101216;
    overflow: hidden;
  }
  .screen__canvas {
    width: 100%;
    /* Genug Hoehe, dass ein Bildschirm erkennbar ist, ohne die Seite zu
       sprengen; noVNC skaliert das Bild hinein. */
    height: min(72vh, 760px);
  }
  /* Im Fenster ist weniger Platz -- und das Fenster soll nicht ueber den
     Bildschirmrand hinauswachsen, nur weil das Geraet hochaufloesend ist. */
  dialog.modal .screen__canvas { height: min(62vh, 620px); }
  .screen__status {
    font-size: var(--fs-sm);
    color: var(--text-faint);
  }
  .screen__status--ok { color: var(--success); }
  .screen__status--warnung { color: var(--warning); }
  .screen__status--fehler { color: var(--danger); }

  /* Fusszeile der Kachel: links der Bildschirm, rechts der Neustart.
     ``margin-top: auto`` schiebt sie an den unteren Rand -- unabhaengig
     davon, wie viel darueber steht. Die beiden Eingriffe stehen damit in
     jeder Kachel an derselben Stelle, und der eine liegt nicht dort, wo eben
     noch der andere war. */
  .tile__actions {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-top: auto;
    padding-top: var(--sp-3);
  }
  .tile__actions > :last-child { margin-left: auto; }
  /* Steht nur ein Knopf da, bleibt er links -- sonst spraenge der Neustart bei
     einem Geraet ohne Bildschirmzugang an die Stelle, an der nebenan VNC
     steht. Das Fuellstueck haelt den Platz. */
  .tile__spacer { flex: 1 1 auto; }

  /* Marke und Karte stehen uebereinander im normalen Fluss. Vorher lag die
     Marke absolut positioniert hinter der Karte -- auf einem flachen Fenster
     schnitt die Karte mitten durch den Schriftzug und die Funktionszeile. Ein
     Stapel kann nicht kollidieren, und die Seite darf notfalls scrollen. */
  .auth-stack {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-8);
    width: 100%;
  }
  .auth-backdrop {
    width: min(72vw, 880px);
    pointer-events: none;
    /* Volle Deckung: solange die Marke hinter der Karte lag, war sie ein
       Wasserzeichen und musste zurueckhaltend sein. Jetzt steht sie ueber der
       Karte und ist das Erste, was man sieht -- halbtransparent sah sie dort
       nur blass aus. Die Farben kommen ohnehin aus zurueckhaltenden Token. */
    opacity: 1;
  }
  .auth-backdrop svg { width: 100%; height: auto; display: block; }
  @media (max-width: 640px) {
    .auth-backdrop { width: min(90vw, 460px); }
  }
  /* Wer wenig Bewegung/Reize moechte, bekommt das Motiv dezenter. */
  /* Wer weniger Reize moechte, bekommt die Marke ruhiger -- aber nicht
     unsichtbar: sie ist die Ueberschrift der Seite. */
  @media (prefers-contrast: more) {
    .auth-backdrop { opacity: 0.75; }
  }

  .auth-card {
    position: relative;
    z-index: 1;
    width: min(410px, 100%);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-lg);
    padding: var(--sp-8) var(--sp-6);
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
  }
  .auth-card--wide { width: min(520px, 100%); }
  .auth-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    text-align: center;
  }
  .auth-brand__mark {
    width: 46px;
    height: 46px;
    border-radius: var(--r-md);
    display: grid;
    place-items: center;
    background: var(--accent);
    color: var(--accent-text);
    font-weight: 800;
    font-size: var(--fs-lg);
  }
  .auth-brand h1 { font-size: var(--fs-xl); }
  .auth-brand p { color: var(--text-muted); font-size: var(--fs-sm); }
  .auth-footnote { text-align: center; font-size: var(--fs-xs); color: var(--text-faint); }
}

@layer utilities {
  .muted { color: var(--text-muted); }
  .faint { color: var(--text-faint); }
  .danger-text { color: var(--danger); }
  .success-text { color: var(--success); }
  .warning-text { color: var(--warning); }
  .accent-text { color: var(--accent); }
  .mono { font-family: var(--font-mono); font-size: 0.94em; }
  /* Ein Inline-SVG ist im Reset display:block (base.css). In einem Knopf
     stoert das nicht -- .btn ist inline-flex. In einem einfachen Link reisst
     es die Beschriftung in die naechste Zeile. Fuer solche Links deshalb
     ausdruecklich eine Zeile. */
  .link-icon {
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }

  .nowrap { white-space: nowrap; }
  .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
  .break-all { overflow-wrap: anywhere; }
  .tabular { font-variant-numeric: tabular-nums; }
  .fs-xs { font-size: var(--fs-xs); }
  .fs-sm { font-size: var(--fs-sm); }
  .fs-lg { font-size: var(--fs-lg); }
  .fw-600 { font-weight: 620; }
  .center { text-align: center; }
  .right { text-align: right; }
  .flex-1 { flex: 1; min-width: 0; }
  .mt-0 { margin-top: 0; }
  .mt-2 { margin-top: var(--sp-2); }
  .mt-3 { margin-top: var(--sp-3); }
  .mt-4 { margin-top: var(--sp-4); }
  .mb-2 { margin-bottom: var(--sp-2); }
  .mb-3 { margin-bottom: var(--sp-3); }
  .mb-4 { margin-bottom: var(--sp-4); }
  .gap-2 { gap: var(--sp-2); }
  .gap-4 { gap: var(--sp-4); }
  .w-full { width: 100%; }
  .hide-sm { }
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  @media (max-width: 760px) {
    .hide-sm { display: none !important; }
  }
}

/* -- Sortierbare Liste (Navigationsreihenfolge) --------------------------- */
/* Symbol und Beschriftung stehen in einer Zeile -- so wie im Menue selbst.
   Vorher brach die Beschriftung unter das Symbol, und die Liste sah aus wie
   etwas anderes als das, was sie sortiert. */

.reorder {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.reorder__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  cursor: grab;
}

.reorder__row > .flex-1 {
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.reorder__row.is-dragging {
  opacity: 0.5;
}

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

.reorder__pos {
  min-width: 1.6em;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  font-size: var(--fs-xs);
}

.reorder__moves {
  display: inline-flex;
  gap: 2px;
}

.reorder__group {
  list-style: none;
}

.reorder--child {
  margin: 4px 0 4px 28px;
}

.reorder--child .reorder__row {
  background: transparent;
  border-style: dashed;
}

/* -- Unterschrift --------------------------------------------------------- */
/* Die Zeichenflaeche braucht "touch-action: none": sonst scrollt ein iPad die
   Seite, statt einen Strich zu zeichnen -- und es sieht aus, als reagierte das
   Feld nicht. */

.viewer__sign {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

.viewer__signfelder {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.viewer__signpad {
  width: 100%;
  height: 160px;
  border: 1px dashed var(--border-strong, var(--border));
  border-radius: var(--r-sm);
  background: #fff;
  touch-action: none;
  cursor: crosshair;
}

.viewer__signaktionen {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: flex-end;
  flex-wrap: wrap;
}

.viewer__pdfscroll--setzen {
  cursor: crosshair;
}

.viewer__pdfscroll--setzen .page {
  outline: 2px dashed var(--accent, #c8791f);
  outline-offset: -2px;
}

/* Die gesetzte Unterschrift liegt auf der Seite -- in Prozent, damit sie beim
   Zoomen an ihrer Stelle bleibt. Sie nimmt Zeiger an: verschieben mit dem
   Block, Groesse mit dem Griff unten rechts, entfernen mit dem Kreuz.
   "touch-action: none" ist auf dem iPad die Bedingung dafuer, dass ein Finger
   zieht statt die Seite zu scrollen. */
/* Die Marke ist genau die Zeichnung: ihre Unterkante liegt auf dem
   Ankerpunkt -- dieselbe Kante, an der pdf-lib das Bild setzt. Der Text
   haengt darunter und darf die Hoehe nicht mitbestimmen. */
.viewer__unterschrift {
  position: absolute;
  z-index: 3;
  cursor: grab;
  touch-action: none;
  /* "outline" statt "border": ein Rahmen haette die Marke um zwei Pixel
     groesser gemacht und die Bildkante vom Ankerpunkt weggeschoben. */
  outline: 1px dashed transparent;
  outline-offset: 1px;
}

.viewer__unterschrift:hover,
.viewer__unterschrift.is-fassend {
  outline-color: var(--accent);
  cursor: grabbing;
}

.viewer__unterschrift img {
  display: block;
  width: 100%;
  pointer-events: none;
}

/* Dieselbe Rechnung wie im PDF (Breite durch 18): auf dem Schirm steht, was
   spaeter in der Datei steht. */
/* "width: max-content": der Strich ist so lang wie der Text darunter -- so
   wie im PDF. Waere er so breit wie der Block, zeigte die Vorschau etwas
   anderes als die Datei. */
.viewer__unterschrifttext {
  /* Unter der Zeichnung, nicht in ihr: "top: 100%" setzt die Oberkante des
     Strichs auf die Bildkante -- genau dort zieht ihn auch pdf-lib. Abstand,
     Strichstaerke und Innenabstand rechnet das Skript in Punkten. */
  position: absolute;
  top: 100%;
  left: 0;
  /* Helvetica: dieselbe Schrift, die das PDF benutzt (StandardFonts.Helvetica).
     Mit der Hausschrift sah die Vorschau anders aus als das Ergebnis. Die
     Groesse setzt das Skript je Unterschrift -- hier steht nur ein Rueckfall. */
  font-family: Helvetica, Arial, sans-serif;
  /* Die tatsaechliche Groesse setzt das Skript in Pixeln (Punkte mal
     Pixel-je-Punkt der Seite). Dieser Wert gilt nur, solange sie noch nicht
     gerechnet wurde -- geerbt wuerde sonst die Schriftgroesse der Seite. */
  font-size: 8px;
  line-height: 1.15;
  color: #111;
  border-top: 1px solid rgba(0, 0, 0, 0.85);
  pointer-events: none;
  white-space: nowrap;
  width: max-content;
}

.viewer__unterschriftgriff,
.viewer__unterschriftweg {
  position: absolute;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 1px solid var(--accent);
  border-radius: 3px;
  background: var(--surface-1);
  color: var(--accent);
  font-size: 11px;
  line-height: 1;
  opacity: 0;
  cursor: nwse-resize;
}

.viewer__unterschriftweg {
  cursor: pointer;
  top: -8px;
  right: -8px;
}

.viewer__unterschriftgriff {
  right: -8px;
  bottom: -8px;
}

.viewer__unterschrift:hover .viewer__unterschriftgriff,
.viewer__unterschrift:hover .viewer__unterschriftweg,
.viewer__unterschrift.is-fassend .viewer__unterschriftgriff {
  opacity: 1;
}

/* Auf einem Tastbildschirm gibt es kein "hover" -- dort stehen die Knoepfe
   immer, sonst kaeme man nie an sie heran. */
@media (hover: none) {
  .viewer__unterschriftgriff,
  .viewer__unterschriftweg {
    opacity: 1;
  }
}

/* -- Ergebnis der Signaturpruefung ---------------------------------------- */
/* Der Schleier liegt ueber der Seite und nimmt keine Klicks an: ein
   unterschriebenes Dokument ist flach, aber bei einem Irrtum soll die
   Kennzeichnung nicht auch noch die Bedienung blockieren. */

.viewer__pdf--unveraendert {
  box-shadow: inset 0 0 0 3px var(--success);
}

.viewer__pdf--unveraendert::after,
.viewer__pdf--veraendert::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
}

.viewer__pdf--unveraendert::after {
  background: rgba(47, 158, 68, 0.1);
}

.viewer__pdf--veraendert {
  box-shadow: inset 0 0 0 3px var(--danger);
}

.viewer__pdf--veraendert::after {
  background: rgba(201, 42, 42, 0.08);
}

.viewer__pruefung {
  margin: 0;
  padding: 6px 12px;
  font-size: var(--fs-xs);
  border-top: 1px solid var(--border);
}

.viewer__pruefung--gut {
  color: var(--success);
  background: rgba(47, 158, 68, 0.12);
}

.viewer__pruefung--schlecht {
  color: var(--danger);
  background: rgba(201, 42, 42, 0.12);
}

/* -- Tabelle, die schmal zu Bloecken wird -------------------------------- */
/* Eine Protokollzeile besteht aus sieben Angaben, von denen eine lang ist:
   das Detail. Auf einem breiten Schirm passt das nebeneinander. Wird es eng,
   ist eine Spalte von zwei Zentimetern fuer eine Pruefsumme keine Loesung --
   dann steht jede Angabe auf ihrer eigenen Zeile, beschriftet. */

.table--stapelbar .td--detail {
  /* Zeilenumbrueche aus dem Eintrag bleiben stehen: die Pruefsumme steht
     unter dem lesbaren Teil, nicht mitten darin. Und ein Hash ohne Trennstelle
     darf notfalls hart umbrechen. */
  white-space: pre-line;
  overflow-wrap: anywhere;
  max-width: 52ch;
}

@media (max-width: 1100px) {
  .table--stapelbar thead {
    display: none;
  }

  .table--stapelbar,
  .table--stapelbar tbody,
  .table--stapelbar tr,
  .table--stapelbar td {
    display: block;
    width: auto;
  }

  .table--stapelbar tr {
    padding: var(--sp-3) 0;
    border-bottom: 1px solid var(--border);
  }

  .table--stapelbar td {
    display: grid;
    grid-template-columns: 8.5rem 1fr;
    gap: var(--sp-2);
    align-items: baseline;
    border: 0;
    padding: 2px var(--sp-3);
    white-space: normal;
  }

  .table--stapelbar td::before {
    content: attr(data-spalte);
    color: var(--text-muted);
    font-size: var(--fs-xs);
  }

  .table--stapelbar .td--detail {
    max-width: none;
  }

  /* Leere Zellen brauchen keine Zeile. */
  .table--stapelbar td:empty {
    display: none;
  }
}

/* -- Dateiliste: Symbol und Name in einer Zeile -------------------------- */
/* Das Symbol ist ein Block-SVG; ohne diese Regel stand der Name darunter und
   eine Ordnerzeile sah doppelt so hoch aus wie eine Dateizeile. */

.file-cell {
  display: flex;
  align-items: center;
  gap: 6px;
}

.file-cell > svg {
  flex: 0 0 auto;
}
