/* =============================================================================
   MESH Review — Oberfläche der Plattform.  Kapitel 28 (V1.6), kanonisch.

   Diese Datei ist das EINZIGE Design-System-Modul der Plattform-Chrome (§28.10).
   Sie ersetzt mesh.css im Review-Screen vollständig und wird nicht mit ihr
   gemischt: §28.1 verbietet ausdrücklich, alte Tokens, Farbschemata, Abstände,
   Schatten, Topbar-Höhen und Sidebar-Breiten wiederzuverwenden.

   Deshalb ein eigener Namensraum:  --ui-*  für Tokens,  .ui-*  für Klassen.
   Das ist keine Kosmetik, sondern die Abnahmeprüfung aus §28.11 („Im Review-
   Screen ist kein Style aus den alten Artefakten mehr vorhanden"): ein `grep`
   auf `mesh-` im Review-Screen muss leer sein. Mit gleichem Präfix wäre dieser
   Satz nicht nachweisbar.

   Abgrenzung: Kapitel 10 und Anhang A beschreiben die Gestaltung der
   PROTOTYP-Websites des Kunden (DESIGN_SYSTEM.json). Damit hat diese Datei
   nichts zu tun — hier liegt ausschließlich das Werkzeug, das um jene Websites
   herum liegt.

   Der Satz, an dem sich hier jede Regel misst (§28.2):
       Das Produkt ist der Canvas. Die Oberfläche ist ein leichtes Overlay
       darüber — nie umgekehrt.
   ============================================================================= */

/* ---------------------------------------------------------------------------
   1  Tokens
   --------------------------------------------------------------------------- */

:root {
  /* -- Grund und Flächen ----------------------------------------------------
     Der Canvas ist die Website selbst. Ringsum steht KEIN grauer Schreibtisch
     (§28.1: kein Browser-Mockup, keine Fenster-Chrome), sondern ein sehr ruhiger
     neutraler Grund, der den Viewport meint — nicht eine Karte. */
  --ui-ground: #F4F4F5;

  /* Chrome: matte, schwebende Flächen (§28.6). Zwei Deckkräfte, weil eine Insel
     über bewegtem Website-Inhalt mehr Deckung braucht als ein ruhendes Sheet. */
  --ui-chrome: rgba(255, 255, 255, 0.82);
  --ui-chrome-solid: #FFFFFF;
  --ui-chrome-hover: rgba(255, 255, 255, 0.94);
  --ui-blur: saturate(180%) blur(20px);
  --ui-hairline: rgba(0, 0, 0, 0.06);
  /* Haarlinie mit Kontrast — für Umrisse von Bedienelementen verlangt
     WCAG 1.4.11 3:1. --ui-hairline ist eine Trennlinie, kein Umriss. */
  --ui-hairline-strong: rgba(0, 0, 0, 0.28);

  /* -- Schrift --------------------------------------------------------------
     §28.6: Inter, Geist oder SF-ähnlich — nicht mit System-Default mischen.
     Inter wird selbst gehostet (@font-face unten). Der Rest der Kette ist der
     Notnagel, falls die Schriftdatei fehlt: auf macOS ist das SF, also genau
     das, was §28.6 als Alternative nennt.
     Selbst gehostet und nicht von fonts.googleapis.com, weil die Plattform nach
     Kap. 24.5 ohne einwilligungspflichtige Fremdressourcen auskommen muss. */
  --ui-font: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --ui-font-num: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Codesatz — NICHT derselbe wie --ui-font-num.
     Der Ziffernsatz ist der Fliesstext mit tabellarischen Ziffern; ein Codesatz
     ist eine andere Schrift. Beide zu verwechseln kostet nichts, solange
     mesh.css mitlaedt: dort steht `code, .mesh-mono { font-family: … }` als
     nackter Elementselektor. In der Betriebsart `neu` laedt sie NICHT mehr —
     und dann verlieren `build_id`, `Commit`, `page_route`, `review_element_id`
     und die Exportpfade in der Verwaltung still ihre Schrift, ohne dass ein
     Test rot wird. Gemessen von Dev3, bevor es passiert ist: 16 Stellen in
     8 der 19 internen Ansichten. */
  --ui-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Klein und klar (§28.6). Keine Riesen-Überschriften in der Chrome. */
  --ui-size-xs: 11px;
  --ui-size-sm: 12px;
  --ui-size-md: 13px;
  --ui-size-lg: 14px;
  --ui-size-title: 15px;   /* Obergrenze in der Chrome, halbfett */

  /* -- Text ----------------------------------------------------------------- */
  --ui-text: #18181B;
  --ui-text-2: #52525B;
  /* #8B8B94 bis Kapitel 29. Gemessen an den 13 ausgelieferten Seiten war das
     die EINZIGE verbliebene Kontrastluecke — und sie lag an jeder Fundstelle:

         auf Weiss             3,38 : 1
         auf --ui-track        3,07 : 1
         auf --ui-accent-soft  2,95 : 1     (Grenze fuer normalen Text: 4,5)

     Betroffen war nichts Beilaeufiges: die Brotkrume, die Zeitstempel der
     Kommentare, die Build-Kennungen und der Satz „kein Freigaberecht" in der
     Zugriffstabelle. Wenn ein Token an ALLEN seinen Fundstellen durchfaellt,
     ist das Token falsch und nicht die Verwendung.

     #6B6B75 ist der hellste Wert, der alle drei Gruende nimmt (4,60 auf dem
     schlechtesten). Der Preis steht hier, damit ihn niemand suchen muss: Der
     Abstand zu --ui-text-2 sinkt von 2,29 auf 1,47 — die dritte Textstufe ist
     naeher an der zweiten als vorher. Wer sie wieder aufspreizen will, macht
     --ui-text-2 dunkler, nicht diesen Wert heller. */
  --ui-text-faint: #6B6B75;
  --ui-on-accent: #FFFFFF;

  /* -- Akzent ---------------------------------------------------------------
     GENAU EINE Akzentfarbe (§28.6) — für Pins, aktiven Modus und die primäre
     Aktion. Der bestehende Markenakzent von MESH WEB, den §28.6 ausdrücklich
     zulässt; 7,4:1 auf Weiß. Ein zweiter Akzent wäre bereits der Regenbogen,
     den derselbe Absatz verbietet. */
  --ui-accent: #2A4BD8;
  --ui-accent-hover: #1F3BB4;
  --ui-accent-soft: #ECEFFD;
  --ui-accent-ring: rgba(42, 75, 216, 0.32);

  /* Spur des Segment-Umschalters und Grund von Hover-Flaechen. Eigenes Token,
     weil ein durchscheinendes Schwarz im dunklen Modus nichts aufhellt. */
  --ui-track: rgba(0, 0, 0, 0.045);
  --ui-hover: rgba(0, 0, 0, 0.05);

  /* Rot ausschließlich für Löschen (§28.6) — nie als Dekoration, nie als
     Zustandsfarbe. Wer damit einen Zustand einfärbt, benutzt es falsch. */
  --ui-danger: #B02318;
  --ui-danger-soft: #FCEAE8;

  /* Zustandstoene — AUSSCHLIESSLICH fuer die interne Oberflaeche (Kapitel 29).
     §28.6 laesst dem Canvas genau eine Akzentfarbe und verbietet den Regenbogen;
     das gilt unveraendert. Intern bildet `_badge.blade.php` aber seit Runde 4
     fuenf Zustaende auf Toene ab, und §29.3 nennt Dichte und technische
     Genauigkeit hier ausdruecklich eine Qualitaet. Zwei Toene sind das Minimum
     dafuer, dass „customer_ready" und „internal" nicht gleich aussehen.
     Kundenseitig wird keiner von beiden benutzt — dort bleibt es beim Akzent. */
  --ui-ok: #186C3D;
  --ui-ok-soft: #E5F3EB;
  --ui-warn: #8A5200;
  --ui-warn-soft: #FDF2DF;

  /* Erledigt: neutral mit Haken, 50–60 % Deckkraft (§28.6). Eine eigene Farbe
     bekäme „erledigt" mehr Gewicht als „offen" — Archiv, nicht Müll (§28.3). */
  --ui-done: #6B7280;
  --ui-done-opacity: 0.55;

  /* -- Höhe und Schatten ----------------------------------------------------
     Zwei Lagen, weich (§28.6): die enge Lage setzt die Kante, die weite hebt
     die Fläche vom Canvas ab. Eine Lage allein wirkt entweder aufgeklebt oder
     schmutzig. */
  --ui-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.08);
  --ui-shadow-sheet: 0 2px 6px rgba(0, 0, 0, 0.08), 0 16px 48px rgba(0, 0, 0, 0.14);
  --ui-shadow-pin: 0 1px 2px rgba(0, 0, 0, 0.24), 0 2px 8px rgba(0, 0, 0, 0.18);

  /* -- Radius ---------------------------------------------------------------
     12–16 px Inseln · 20–24 px große Sheets · 999 px Pillen (§28.6). */
  --ui-radius-island: 14px;
  --ui-radius-panel: 12px;
  --ui-radius-sheet: 22px;
  --ui-radius-pill: 999px;
  --ui-radius-sm: 8px;

  /* -- Maße -----------------------------------------------------------------
     §28.6: keine harten Vollflächen-Leisten über 48 px. Zielhöhe der Insel
     40–44 px. Der Wert steht als Token, damit „über 48 px" an genau einer
     Stelle widerlegbar ist statt an zwölf. */
  --ui-island-h: 42px;
  --ui-island-top: 14px;      /* nicht bündig an der Kante (§28.7) */
  --ui-dock-w: 320px;         /* 300–340 px (§28.7) */
  --ui-gap: 8px;
  --ui-pad: 12px;

  /* -- Bewegung -------------------------------------------------------------
     120–220 ms, kein Bounce-Zirkus (§28.5). */
  --ui-t-fast: 120ms;
  --ui-t: 150ms;              /* Moduswechsel (§28.6) */
  --ui-t-pin: 180ms;          /* Pin erscheint */
  --ui-t-panel: 220ms;
  --ui-ease: cubic-bezier(0.2, 0, 0.13, 1);

  /* -- Schichten ------------------------------------------------------------
     Wörtlich aus §28.10. Als Tokens, damit niemand unterwegs eine 9999
     erfindet und die Insel unter einem Sheet verschwindet. */
  --ui-z-canvas: 0;
  --ui-z-pins: 10;
  --ui-z-dock: 25;
  --ui-z-island: 30;
  --ui-z-sheet: 40;
  --ui-z-toast: 50;
}

/* Dunkler Modus: darf existieren, aber nicht zuerst (§28.6). Deshalb nur eine
   Umwertung der Tokens — keine eigene Regelmenge, die gepflegt werden müsste.
   Kundenprüfungen finden am hellen Erscheinungsbild der Website statt; wer
   dunkel schaltet, bekommt eine ruhige Chrome, nicht ein zweites Produkt. */
@media (prefers-color-scheme: dark) {
  :root:not([data-ui-theme="light"]) {
    --ui-ground: #111111;
    --ui-chrome: rgba(28, 28, 32, 0.82);
    --ui-chrome-solid: #1C1C20;
    --ui-chrome-hover: rgba(38, 38, 44, 0.94);
    --ui-hairline: rgba(255, 255, 255, 0.10);
    --ui-hairline-strong: rgba(255, 255, 255, 0.34);
    --ui-text: #F4F4F5;
    --ui-text-2: #A1A1AA;
    /* #71717A war hier derselbe Fehler mit umgekehrtem Vorzeichen: 3,91 auf
       --ui-ground, 3,51 auf --ui-chrome-solid. #8A8A94 gibt 5,52 / 4,97. */
    --ui-text-faint: #8A8A94;
    --ui-accent: #7C9BFF;
    --ui-accent-hover: #9BB2FF;
    --ui-accent-soft: #1B2240;
    --ui-accent-ring: rgba(124, 155, 255, 0.36);
    --ui-track: rgba(255, 255, 255, 0.07);
    --ui-hover: rgba(255, 255, 255, 0.08);
    --ui-on-accent: #10162B;
    --ui-danger: #F2857A;
    --ui-danger-soft: #2A1614;
    --ui-ok: #7ED7B4;
    --ui-ok-soft: #12261F;
    --ui-warn: #EFBE72;
    --ui-warn-soft: #2A2114;
    --ui-done: #9CA3AF;
    --ui-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px rgba(0, 0, 0, 0.45);
    --ui-shadow-sheet: 0 2px 6px rgba(0, 0, 0, 0.55), 0 16px 48px rgba(0, 0, 0, 0.6);
  }
}
:root[data-ui-theme="dark"] {
  --ui-ground: #111111;
  --ui-chrome: rgba(28, 28, 32, 0.82);
  --ui-chrome-solid: #1C1C20;
  --ui-chrome-hover: rgba(38, 38, 44, 0.94);
  --ui-hairline: rgba(255, 255, 255, 0.10);
  --ui-hairline-strong: rgba(255, 255, 255, 0.34);
  --ui-text: #F4F4F5;
  --ui-text-2: #A1A1AA;
  --ui-text-faint: #8A8A94;   /* siehe oben: #71717A gab 3,91 auf --ui-ground */
  --ui-accent: #7C9BFF;
  --ui-accent-hover: #9BB2FF;
  --ui-accent-soft: #1B2240;
  --ui-accent-ring: rgba(124, 155, 255, 0.36);
  --ui-track: rgba(255, 255, 255, 0.07);
  --ui-hover: rgba(255, 255, 255, 0.08);
  --ui-on-accent: #10162B;
  --ui-danger: #F2857A;
  --ui-danger-soft: #2A1614;
  --ui-ok: #7ED7B4;
  --ui-ok-soft: #12261F;
  --ui-warn: #EFBE72;
  --ui-warn-soft: #2A2114;
  --ui-done: #9CA3AF;
  --ui-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px rgba(0, 0, 0, 0.45);
  --ui-shadow-sheet: 0 2px 6px rgba(0, 0, 0, 0.55), 0 16px 48px rgba(0, 0, 0, 0.6);
}

/* Schrift. Die Dateien liegen unter public/fonts/ und werden vom Webserver
   direkt ausgeliefert — kein Bundler (§19.1), keine Fremdressource (Kap. 24.5).
   `font-display: swap`: lieber sofort in der Notnagel-Kette lesen als die
   Insel für 200 ms leer stehen lassen. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
                 U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------------------------------------------------------------------------
   2  Grundlage
   --------------------------------------------------------------------------- */

/* Zwei Regeln, ohne die die Tokens luegen.

   `box-sizing`: ohne sie ist die Insel 44 px hoch statt der 42, die
   `--ui-island-h` sagt — Hoehe plus zweimal 1 px Rand. Gemessen von Dev2. Das
   Ergebnis liegt zwar noch in der Zielspanne 40–44 von §28.6, aber ein Token,
   das nicht sagt was herauskommt, ist als Beleg wertlos: „ueber 48 px ist an
   genau einer Stelle widerlegbar" gilt nur, wenn die Stelle stimmt.

   `[hidden]`: `.ui-hint` und `.ui-toast` setzen `display: flex` und schlagen
   damit das Merkmal — gemessen blieb der Hinweis trotz gesetztem `hidden`
   stehen. Ein Bauteil, das sich nicht verbergen laesst, ist ein Fehler im
   Fundament und nicht in seinem Verbraucher. */
.ui-root *,
.ui-root *::before,
.ui-root *::after { box-sizing: border-box; }

.ui-root [hidden] { display: none !important; }

.ui-root {
  margin: 0;
  font-family: var(--ui-font);
  font-size: var(--ui-size-md);
  line-height: 1.45;
  color: var(--ui-text);
  background: var(--ui-ground);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Der Canvas-Container: 100dvh minus nichts (§28.10). `dvh` statt `vh`, weil
   auf dem Telefon sonst die Adressleiste den unteren Rand abschneidet — und
   Kunden prüfen oft am Telefon (§28.10). */
.ui-canvas {
  position: relative;
  width: 100%;
  height: 100dvh;
  overflow: hidden;
  background: var(--ui-ground);
  z-index: var(--ui-z-canvas);
}

/* Sichtbarer Fokus auf JEDEM Bedienelement (§28.8, Punkt 9). Ein Ring, der auf
   heller Chrome UND auf beliebigem Website-Inhalt trägt: zwei Ringe statt eines
   durchscheinenden — der weiße innere trennt vom Untergrund, der farbige außen
   markiert. */
.ui-root :focus-visible {
  outline: 2px solid var(--ui-accent);
  outline-offset: 2px;
  border-radius: var(--ui-radius-sm);
}
.ui-root :focus:not(:focus-visible) { outline: none; }

/* Nur für Vorlesehilfen — Beschriftungen, die im Bild das Icon doppeln würden. */
.ui-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* §28.6 Grundregel: prefers-reduced-motion wird respektiert. Zentral, damit es
   nicht in jedem Baustein einzeln vergessen wird. Bewegung wird abgeschaltet,
   Zustände bleiben — der Moduswechsel ist danach sofort statt weich, nie
   unsichtbar. */
@media (prefers-reduced-motion: reduce) {
  .ui-root *,
  .ui-root *::before,
  .ui-root *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------------------
   3  Primitive: die schwebende Insel  (§28.7)
   Sie ERSETZT die Topbar. Sie ist kein schmaleres Exemplar davon:
   zentriert, schwebend, matt, und sie geht bei „Oberfläche ausblenden" weg.
   --------------------------------------------------------------------------- */

.ui-island {
  position: fixed;
  top: var(--ui-island-top);
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--ui-z-island);

  display: flex;
  align-items: center;
  gap: 2px;
  height: var(--ui-island-h);
  max-width: calc(100vw - 24px);
  padding: 0 6px;

  background: var(--ui-chrome);
  -webkit-backdrop-filter: var(--ui-blur);
  backdrop-filter: var(--ui-blur);
  border: 1px solid var(--ui-hairline);
  border-radius: var(--ui-radius-island);
  box-shadow: var(--ui-shadow);

  transition: opacity var(--ui-t) var(--ui-ease),
              transform var(--ui-t) var(--ui-ease);
}

/* Ausgeblendet: bei Untätigkeit, bei „Oberfläche ausblenden", im Präsentations-
   modus (§28.7). Hover am Rand oder Esc holt sie zurück — das macht das Modul,
   nicht das CSS. `visibility` mit in den Übergang, sonst fängt die unsichtbare
   Insel weiterhin Klicks ab. */
.ui-island[data-hidden="true"] {
  opacity: 0;
  transform: translateX(-50%) translateY(-8px);
  visibility: hidden;
  transition: opacity var(--ui-t) var(--ui-ease),
              transform var(--ui-t) var(--ui-ease),
              visibility 0s linear var(--ui-t);
}

.ui-island__sep {
  width: 1px;
  align-self: stretch;
  margin: 8px 4px;
  background: var(--ui-hairline);
}

.ui-island__group { display: flex; align-items: center; gap: 2px; }

/* Titel in der Chrome: höchstens 14–15 px, halbfett (§28.6). Was nicht passt,
   wird gekürzt — die Insel wächst nicht mit dem Projektnamen. */
.ui-island__title {
  max-width: 22ch;
  padding: 0 6px;
  font-size: var(--ui-size-lg);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Auf schmalen Viewports kompakter: Beschriftungen weg, nur Icons und Modus
   (§28.7). Der Moduswechsel bleibt beschriftet — er ist die primäre Affordanz
   und genau das, was ein ungeschulter Kunde lesen können muss (§28.11). */
@media (max-width: 640px) {
  .ui-island { --ui-island-h: 44px; max-width: calc(100vw - 16px); }
  .ui-island__title { display: none; }
  .ui-island__label { display: none; }
  .ui-island .ui-segment__btn { padding-inline: 12px;   text-decoration: none;
}
}

/* ---------------------------------------------------------------------------
   4  Primitive: Segment-Umschalter  (§28.3, §28.8)
   Der Moduswechsel ist die primäre Affordanz und der LAUTESTE Zustand der
   Oberfläche. Deshalb bekommt das aktive Segment eine gefüllte Pille, keinen
   dezenten Unterstrich.
   --------------------------------------------------------------------------- */

.ui-segment {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--ui-track);
  border-radius: var(--ui-radius-pill);
}
.ui-segment__btn {
  appearance: none;
  border: 0;
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 14px;
  font: inherit;
  font-size: var(--ui-size-md);
  font-weight: 550;
  color: var(--ui-text-2);
  background: transparent;
  border-radius: var(--ui-radius-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--ui-t) var(--ui-ease),
              background-color var(--ui-t) var(--ui-ease),
              box-shadow var(--ui-t) var(--ui-ease);
}
/* Der Segmentknopf ist ausserhalb der Insel oft ein <a> — die Reiter der
   Verwaltung sind welche. `text-decoration` stand bisher NUR in der
   Mobil-Regel unter `.ui-island`; ausserhalb war jeder Reiter unterstrichen,
   bis mesh.css das mit ihrem nackten `a`-Selektor verdeckte. Mit der
   Betriebsart `neu` faellt diese Deckung weg.

   Von Dev2 nach dem Umschalten gemessen: bis zu FUENF unterstrichene Reiter je
   Seite (`zugriff/index`). Ich hatte gemeldet, ich haette „alle fuenf Bauteile,
   die ein <a> sein koennen" versorgt — es sind sechs, und der sechste war der
   hier. */
.ui-segment__btn { text-decoration: none; }
.ui-segment__btn:hover { color: var(--ui-text); }
.ui-segment__btn:active { transition: none; filter: brightness(0.95); }

/* Aktiv = der Zustand der ganzen Review-Sitzung (§28.8, Punkt 1). */
.ui-segment__btn[aria-pressed="true"] {
  color: var(--ui-on-accent);
  background: var(--ui-accent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
}
.ui-segment__btn[aria-pressed="true"]:hover { background: var(--ui-accent-hover); }
.ui-segment__btn svg { width: 16px; height: 16px; flex: none; }

/* ---------------------------------------------------------------------------
   5  Primitive: Symbolknopf und Knopf
   Eine Linienstärke, 16–18 px (§28.6). Icons kommen als inline-SVG mit
   `stroke-width="1.6"`; die Größe setzt diese Datei, damit sie einheitlich ist.
   --------------------------------------------------------------------------- */

.ui-iconbtn {
  appearance: none;
  border: 0;
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 32px;
  height: 32px;
  padding: 0;
  color: var(--ui-text-2);
  background: transparent;
  border-radius: var(--ui-radius-sm);
  cursor: pointer;
  transition: color var(--ui-t-fast) var(--ui-ease),
              background-color var(--ui-t-fast) var(--ui-ease);
  text-decoration: none;
}
.ui-iconbtn svg { width: 17px; height: 17px; flex: none; }
.ui-iconbtn:hover { color: var(--ui-text); background: var(--ui-hover); }
.ui-iconbtn:active { background: var(--ui-track); transition: none; }
.ui-iconbtn[aria-pressed="true"] { color: var(--ui-accent); background: var(--ui-accent-soft); }
.ui-iconbtn[disabled] { opacity: 0.4; cursor: not-allowed; }

/* Symbolknopf mit Zähler — der Dock-Umschalter trägt die Zahl offener Punkte
   (§28.7). Tabellarische Ziffern, damit die Zahl beim Hochzählen nicht wackelt. */
.ui-iconbtn--counted { width: auto; padding: 0 9px; }
.ui-count {
  min-width: 17px;
  padding: 0 5px;
  font-size: var(--ui-size-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 17px;
  text-align: center;
  color: var(--ui-on-accent);
  background: var(--ui-accent);
  border-radius: var(--ui-radius-pill);
}
.ui-count[data-zero="true"] { color: var(--ui-text-faint); background: var(--ui-track); }

.ui-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  padding: 0 14px;
  font: inherit;
  font-size: var(--ui-size-md);
  font-weight: 550;
  color: var(--ui-text);
  background: var(--ui-chrome-solid);
  border: 1px solid var(--ui-hairline-strong);
  border-radius: var(--ui-radius-sm);
  cursor: pointer;
  white-space: nowrap;
  /* Ein Knopf kann ein <a> sein („Zur Projektübersicht", „Erneut anmelden" in der
     Insel). Der Browser unterstreicht den dann, und bis Kapitel 28 verdeckte das
     `a { text-decoration: none }` aus mesh.css — die im Canvas nicht mehr laedt.
     Von Paket D gemeldet, das sich dafuer einen Notnagel in die Aussenseiten
     legen musste; der gehoert hierher und nicht dorthin. */
  text-decoration: none;
  transition: background-color var(--ui-t-fast) var(--ui-ease),
              border-color var(--ui-t-fast) var(--ui-ease),
              color var(--ui-t-fast) var(--ui-ease);
}
.ui-btn:hover { background: var(--ui-hover); }
/* §28.8 · 9 verlangt vier Zustaende, und der aktive fehlte im ganzen Fundament
   (`grep -c ':active' ui.css` war 0 — von Dev2 gemessen). Er zaehlt gerade dort,
   wo fast nur Bedienflaechen stehen: in der Verwaltung ist der Druckpunkt die
   einzige Rueckmeldung zwischen Klick und Antwort des Servers.
   Kein Versatz, keine Bewegung — §28.6 will keinen Bounce-Zirkus. Nur ein
   Nachdunkeln, und ohne Uebergang, damit es sofort kommt. */
.ui-btn:active { background: var(--ui-track); transition: none; }
.ui-btn--primary {
  color: var(--ui-on-accent);
  background: var(--ui-accent);
  border-color: transparent;
}
.ui-btn--primary:hover { background: var(--ui-accent-hover); }
.ui-btn--primary:active { background: var(--ui-accent-hover); filter: brightness(0.92); transition: none; }
.ui-btn--ghost { background: transparent; border-color: transparent; color: var(--ui-text-2); }
.ui-btn--ghost:hover { background: var(--ui-hover); color: var(--ui-text); }
/* Rot ausschließlich für Löschen (§28.6). */
.ui-btn--danger { color: var(--ui-danger); border-color: transparent; background: var(--ui-danger-soft); }
.ui-btn--danger:hover { background: var(--ui-danger); color: #fff; }
.ui-btn--small { height: 26px; padding: 0 10px; font-size: var(--ui-size-sm); }
.ui-btn--block { width: 100%; }
.ui-btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.ui-btn svg { width: 16px; height: 16px; flex: none; }

/* ---------------------------------------------------------------------------
   6  Primitive: Comment-Dock  (§28.3, §28.7)
   Seitliches Panel, einklappbar. Als OVERLAY, nicht schiebend — sonst springt
   der Canvas beim Öffnen, und die Stelle, die man gerade ansieht, wandert weg.
   Das ist der Grund für die Entscheidung, nicht der Geschmack.
   --------------------------------------------------------------------------- */

.ui-dock {
  position: fixed;
  /* Unterhalb der Insel und von den Kanten abgesetzt — eine schwebende Flaeche,
     kein Rahmen (§28.6). Zwei Gruende, beide gemessen:
     1. Eine Spalte, die an der Oberkante beginnt, laeuft auf schmalen Breiten
        unter die zentrierte Insel; auf der Probeseite war „Erledigt" verdeckt.
     2. Eine randlose Spalte von oben bis unten IST die Sidebar, die §28.1
        ausschliesst — sie sieht aus wie ein Rahmen um den Canvas.
     Die Skizze in §28.7 zeigt das Dock ebenfalls unterhalb der Insel. */
  top: calc(var(--ui-island-top) + var(--ui-island-h) + 8px);
  left: 12px;
  bottom: 12px;
  z-index: var(--ui-z-dock);
  width: var(--ui-dock-w);
  max-width: calc(100vw - 24px);

  display: flex;
  flex-direction: column;
  overflow: hidden;

  background: var(--ui-chrome);
  -webkit-backdrop-filter: var(--ui-blur);
  backdrop-filter: var(--ui-blur);
  border: 1px solid var(--ui-hairline);
  border-radius: var(--ui-radius-island);
  box-shadow: var(--ui-shadow);

  /* Transform, nicht Breite (§28.6: „Panel | Breite bzw. Transform — nicht
     hartes Ein-/Ausblenden"). Transform läuft auf dem Compositor und ruckelt
     nicht, während im iframe daneben eine Website scrollt. */
  transform: translateX(0);
  transition: transform var(--ui-t-panel) var(--ui-ease),
              visibility 0s linear 0s;
}
.ui-dock[data-open="false"] {
  transform: translateX(calc(-100% - 16px));
  visibility: hidden;
  transition: transform var(--ui-t-panel) var(--ui-ease),
              visibility 0s linear var(--ui-t-panel);
}

.ui-dock__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--ui-gap);
  padding: 10px var(--ui-pad);
  border-bottom: 1px solid var(--ui-hairline);
}

/* Offen / Erledigt — Erledigt ist Archiv, nicht Müll (§28.3). */
.ui-dock__tabs { display: inline-flex; gap: 2px; padding: 3px; background: var(--ui-track); border-radius: var(--ui-radius-pill); }

.ui-dock__search {
  flex: 1;
  min-width: 0;
  height: 28px;
  padding: 0 10px;
  font: inherit;
  font-size: var(--ui-size-sm);
  color: var(--ui-text);
  background: var(--ui-chrome-solid);
  border: 1px solid var(--ui-hairline-strong);
  border-radius: var(--ui-radius-pill);
}
/* Klippt statt zu zerschneiden. Gemessen: „In Kommentaren suchen" braucht in
   diesem Feld 137 px und hat 137 — null Reserve, und im Bild stand „In
   Kommentaren such". Eine Passung ohne Spielraum wartet nur auf eine andere
   Schriftdarstellung. Auslassungspunkte machen den Rest wenigstens ehrlich;
   die kurze Beschriftung ist trotzdem die bessere Antwort. */
.ui-dock__search { text-overflow: ellipsis; }
.ui-dock__search::placeholder { color: var(--ui-text-faint); text-overflow: ellipsis; }

.ui-dock__body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 6px 0 16px; }

/* Gruppen sind Seiten (§28.7) — Akkordeon, nicht flache endlose Liste. */
.ui-dock__group { border-bottom: 1px solid var(--ui-hairline); }
.ui-dock__group > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px var(--ui-pad);
  font-size: var(--ui-size-sm);
  font-weight: 600;
  color: var(--ui-text-2);
  cursor: pointer;
  list-style: none;
}
.ui-dock__group > summary::-webkit-details-marker { display: none; }
.ui-dock__group > summary:hover { color: var(--ui-text); background: var(--ui-hover); }

/* Einträge: Avatar, Textausschnitt, Pin-Nummer, relative Zeit (§28.7). */
.ui-dock__item {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  width: 100%;
  padding: 9px var(--ui-pad);
  text-align: left;
  background: transparent;
  border: 0;
  border-left: 2px solid transparent;
  font: inherit;
  cursor: pointer;
  transition: background-color var(--ui-t-fast) var(--ui-ease),
              border-color var(--ui-t-fast) var(--ui-ease);
}
.ui-dock__item:hover { background: var(--ui-hover); }

/* Bidirektional synchron (§28.8, Punkt 5): dieselbe Hervorhebung, die der Pin
   auf dem Canvas bekommt — sonst ist „verbunden" eine Behauptung. */
.ui-dock__item[data-active="true"] {
  background: var(--ui-accent-soft);
  border-left-color: var(--ui-accent);
}
.ui-dock__item[data-done="true"] { opacity: var(--ui-done-opacity); }
.ui-dock__text {
  font-size: var(--ui-size-md);
  color: var(--ui-text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ui-dock__meta {
  grid-column: 2;
  font-size: var(--ui-size-xs);
  color: var(--ui-text-faint);
  display: flex;
  gap: 6px;
  align-items: center;
}

/* Leerzustand: ein Satz, kaum Illustration (§28.7). */
.ui-dock__empty {
  padding: 28px var(--ui-pad);
  text-align: center;
  font-size: var(--ui-size-md);
  color: var(--ui-text-faint);
}

/* ---------------------------------------------------------------------------
   7  Primitive: Pin  (§28.3, §28.6)
   Pins müssen auf BELIEBIGEN Website-Hintergründen lesbar bleiben (§28.10) —
   deshalb Ring und Schatten, nicht nur Füllung. Ein Pin ohne Ring verschwindet
   auf einem Hero-Bild in der eigenen Farbe.
   --------------------------------------------------------------------------- */

.ui-pinlayer {
  position: absolute;
  inset: 0;
  z-index: var(--ui-z-pins);
  pointer-events: none;   /* die Pins selbst schalten es wieder ein */
}

.ui-pin {
  position: absolute;
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;   /* der Punkt sitzt auf der Koordinate, nicht daneben */
  padding: 0;
  border: 0;
  cursor: pointer;

  font-family: var(--ui-font-num);
  font-size: var(--ui-size-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;   /* §28.6: tabellarische Ziffern */
  color: var(--ui-on-accent);

  background: var(--ui-accent);
  border-radius: var(--ui-radius-pill) var(--ui-radius-pill) var(--ui-radius-pill) 4px;
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.9), var(--ui-shadow-pin);

  transition: transform var(--ui-t-fast) var(--ui-ease),
              box-shadow var(--ui-t-fast) var(--ui-ease);
}

/* Skalierung 0,85 → 1 mit Einblenden, 180 ms (§28.6).

   NUR fuer den eben gesetzten Pin — deshalb `data-enter`, nicht `.ui-pin`
   selbst. §28.6 beschreibt „Pin erscheint": das Setzen einer Stelle, nicht das
   Zeichnen einer Liste. Lag das Einblenden auf jedem Pin, waere der Canvas nach
   jedem Seitenaufbau 180 ms lang pinlos und alle Pins ploppten gemeinsam auf —
   gemessen, nicht vermutet: im ersten Schnappschuss der Probeseite fehlten
   genau deshalb alle vier.

   Wer einen Pin setzt, setzt `data-enter="true"` und nimmt es nach dem
   `animationend` wieder weg. */
.ui-pin[data-enter="true"] { animation: ui-pin-in var(--ui-t-pin) var(--ui-ease) both; }
@keyframes ui-pin-in {
  from { opacity: 0; transform: scale(0.85); }
  to   { opacity: 1; transform: scale(1); }
}
.ui-pin:hover { transform: scale(1.12); }
.ui-pin[data-active="true"] {
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.9),
              0 0 0 5px var(--ui-accent-ring), var(--ui-shadow-pin);
}
/* Erledigt: gedimmt, nicht gelöscht (§28.3) — neutral **mit Haken** (§28.6).
   Den Haken bringt das Markup als <svg> statt der Zahl; die Groesse setzt diese
   Datei, damit er in jedem Pin gleich sitzt. */
.ui-pin[data-done="true"] {
  background: var(--ui-done);
  opacity: var(--ui-done-opacity);
}
.ui-pin svg { width: 14px; height: 14px; stroke-width: 2.6; }
/* Die Stelle gibt es im aktuellen Build nicht mehr (Kap. 9.3, `anchor_status`
   = stale). Hohl statt gefuellt, mit innenliegendem Ring: die uebliche Sprache
   fuer „zeigt auf etwas, das nicht mehr da ist". Bewusst KEIN Fehlton — beim
   Kunden ist ein verlorener Anker nichts, was er verursacht hat (SCHALE.md),
   und Rot ist nach §28.6 ausschliesslich fuer Loeschen.
   Von Dev2 ohne neues Token geloest und hier nachgezogen, damit derselbe
   Zustand nicht in zwei Dateien zweimal beschrieben wird. */
.ui-pin[data-anchor="stale"] {
  color: var(--ui-accent);
  background: var(--ui-chrome-solid);
  box-shadow: inset 0 0 0 2px var(--ui-accent),
              0 0 0 1.5px rgba(255, 255, 255, 0.9), var(--ui-shadow-pin);
}

/* Bündel bei Nähe (§28.3 / Kap. 7.4) — ein Punkt mit Zahl. */
.ui-pin--cluster { border-radius: var(--ui-radius-pill); }
/* Der noch ungesendete Pin. Er verschwindet bei Esc oder Fokusverlust
   rückstandslos (§28.8, Punkt 4) — hier ist nur seine Ansicht. */
.ui-pin--draft { background: var(--ui-accent); box-shadow: 0 0 0 1.5px #fff, 0 0 0 6px var(--ui-accent-ring), var(--ui-shadow-pin); }

/* ---------------------------------------------------------------------------
   8  Primitive: Pin-Composer und Thread  (§28.7)
   Kleines Popover AM PIN, kein Formular über die volle Breite. Nach dem
   Absenden wird aus dem Popover der Thread — dieselbe Fläche, nicht eine zweite.
   --------------------------------------------------------------------------- */

.ui-composer {
  /* KEINE Positionierung, kein z-index, keine Breite.

     Das Popover am Pin gibt es in diesem Produkt bereits: `openAt` aus
     preview.js liefert den verankerten Behälter, positioniert ihn am Pin,
     klappt ihn an den Rändern um und **misst seine eigene Höhe**
     (`anchorBox.offsetHeight`). Trüge der Inhalt darin `position: absolute`,
     wäre er layoutlos und der Behälter fiele auf 0 Höhe zusammen — von Dev3
     gemessen, nicht vermutet.

     Deshalb beschreibt dieses Primitiv nur die FLÄCHE, nicht ihren Ort. Wer
     verankert, bestimmt den Ort; hier steht, wie die Fläche aussieht. Diese
     Klasse gehört damit auf den verankerten Behälter selbst
     (`.preview-anchored`), nicht auf einen Knoten darin — sonst lägen zwei
     Flächen übereinander, eine mit Schatten und Rand innerhalb der anderen. */
  padding: 10px;

  background: var(--ui-chrome-solid);
  border: 1px solid var(--ui-hairline);
  border-radius: var(--ui-radius-panel);
  box-shadow: var(--ui-shadow-sheet);

  /* Der Ursprung der Transformation liegt am Pin (§28.6) — das Popover wächst
     aus dem Punkt heraus, statt aus seiner eigenen Mitte. Welcher Ursprung es
     ist, setzt der Verankernde über `--ui-origin`, weil nur er weiß, an welcher
     Ecke der Pin steht. */
  transform-origin: var(--ui-origin, top left);
}

/* Das Einblenden ist opt-in — dasselbe Muster wie beim Pin, absichtlich und
   nicht zufaellig gleich.

   Der verankerte Behaelter wird wiederverwendet (§21.7): laege die Bewegung auf
   `.ui-composer` selbst, liefe sie beim zweiten Oeffnen nie wieder, weil der
   Knoten die Animation schon abgespielt hat. Dev2 hat das mit
   `animation: none` → Reflow → zurueck geloest; das ist richtig, gehoert aber
   nicht zweimal ins System.

   Also derselbe Handgriff wie am Pin: beim Oeffnen `data-enter="true"` setzen,
   nach `animationend` wieder wegnehmen. Ein Mechanismus fuer beide Eintritte
   statt zweier, die dasselbe tun. */
.ui-composer[data-enter="true"] { animation: ui-pop-in var(--ui-t-pin) var(--ui-ease) both; }
@keyframes ui-pop-in {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: scale(1); }
}

/* Textfeld wächst mit (§28.7). `field-sizing` wo vorhanden, sonst setzt das
   Modul die Höhe — deshalb hier kein festes `height`. */
.ui-textarea {
  display: block;
  width: 100%;
  min-height: 62px;
  max-height: 40vh;
  padding: 8px 10px;
  font: inherit;
  font-size: var(--ui-size-md);
  color: var(--ui-text);
  background: transparent;
  border: 1px solid var(--ui-hairline-strong);
  border-radius: var(--ui-radius-sm);
  resize: none;
  field-sizing: content;
}
.ui-textarea::placeholder { color: var(--ui-text-faint); }

/* Die Werkzeugleiste erscheint erst bei Fokus (§28.7). */
.ui-composer__tools {
  display: none;
  align-items: center;
  gap: 2px;
  margin-top: 6px;
}
.ui-composer[data-focus="true"] .ui-composer__tools { display: flex; }
.ui-composer__actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--ui-gap); margin-top: 8px; }

.ui-thread { display: flex; flex-direction: column; gap: 10px; max-height: 46vh; overflow-y: auto; }
.ui-thread__msg { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; }
.ui-thread__author { font-size: var(--ui-size-sm); font-weight: 600; }
.ui-thread__time { font-size: var(--ui-size-xs); color: var(--ui-text-faint); }
.ui-thread__body { grid-column: 2; font-size: var(--ui-size-md); }
/* Interne Notiz (Kap. 4.2). Sie erreicht ein Kundenfrontend nie — die Filterung
   ist serverseitig; diese Regel ist die Kennzeichnung fürs Team, nicht der
   Schutz. Ton bewusst neutral: eine Notiz ist kein Fehlzustand. */
.ui-thread__msg[data-internal="true"] { border-left: 2px solid var(--ui-hairline-strong); padding-left: 8px; }

/* ---------------------------------------------------------------------------
   9  Primitive: Sheet, Hinweis, Toast
   Teilen, Benachrichtigungen und Beteiligte als leichte Sheets oder Popover,
   nicht als eigene Unter-Anwendung (§28.3).
   --------------------------------------------------------------------------- */

.ui-sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--ui-z-sheet);
  background: rgba(0, 0, 0, 0.28);
  animation: ui-fade-in var(--ui-t) var(--ui-ease) both;
}
@keyframes ui-fade-in { from { opacity: 0; } to { opacity: 1; } }

.ui-sheet {
  position: fixed;
  z-index: var(--ui-z-sheet);
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(440px, calc(100vw - 32px));
  padding: 20px;
  background: var(--ui-chrome-solid);
  border: 1px solid var(--ui-hairline);
  border-radius: var(--ui-radius-sheet);
  box-shadow: var(--ui-shadow-sheet);
  animation: ui-sheet-in var(--ui-t-panel) var(--ui-ease) both;
}
@keyframes ui-sheet-in {
  from { opacity: 0; transform: translate(-50%, -46%) scale(0.98); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.ui-sheet__title { margin: 0 0 4px; font-size: var(--ui-size-title); font-weight: 650; }
.ui-sheet__text  { margin: 0 0 14px; font-size: var(--ui-size-md); color: var(--ui-text-2); }
.ui-sheet__actions { display: flex; justify-content: flex-end; gap: var(--ui-gap); margin-top: 16px; }

/* Der EINE nicht-modale Hinweis beim ersten Besuch (§28.7, Kap. 26.4).
   Nicht-modal heißt: er verdeckt den Canvas nicht und hält niemanden auf.
   Ein Overlay über dem Canvas ist mit Kapitel 28 ausgeschlossen. */
.ui-hint {
  position: fixed;
  z-index: var(--ui-z-island);
  top: calc(var(--ui-island-top) + var(--ui-island-h) + 10px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(520px, calc(100vw - 24px));
  padding: 9px 10px 9px 14px;
  font-size: var(--ui-size-md);
  color: var(--ui-text);
  background: var(--ui-chrome);
  -webkit-backdrop-filter: var(--ui-blur);
  backdrop-filter: var(--ui-blur);
  border: 1px solid var(--ui-hairline);
  border-radius: var(--ui-radius-pill);
  box-shadow: var(--ui-shadow);
  animation: ui-hint-in var(--ui-t-panel) var(--ui-ease) both;
}
@keyframes ui-hint-in {
  from { opacity: 0; transform: translateX(-50%) translateY(-6px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* Der Stapel. Drei Pakete rendern Toasts, und `flash.blade.php` allein kann
   drei erzeugen (Status, Entwicklungsnotiz, Fehler) — bis zu fuenf Flaechen auf
   EINER festen Stelle, alle mit derselben Schicht. Bei gleichem z-index gewinnt
   der spaetere Knoten, und im Bild stand woertlich

       „Ihr Kom… Link kopiert …eichert."

   Von Dev3 gemessen (Flash 532–748, Insel 590–690, Dock 609–671). Das ist ein
   Fehler des Fundaments, nicht der drei Verbraucher: `.ui-toast` gab jedem
   dieselbe Lage, ohne zu sagen, was bei mehreren gilt.

   Deshalb dieser Behaelter. Wer hineinrendert, stapelt von selbst — ohne
   ausgedachte Versaetze und in jeder Kombination.

   `column` und ausdruecklich NICHT `column-reverse`. Hier stand zuerst
   `column-reverse` mit der Begruendung, der juengste stehe damit unten am Rand
   — Dev2 hat nachgemessen, dass es genau umgekehrt ist: Bei `column-reverse`
   laeuft die Hauptachse von unten nach oben, der ZUERST eingehaengte Knoten
   liegt am Rand, spaetere stapeln sich darueber.

   Die Richtung ist nicht Geschmack. Toasts werden angehaengt, also ist der
   letzte der juengste; er gehoert an den Rand, weil dort das Auge steht. Und
   er darf nicht wandern, wenn ein aelterer darueber verschwindet — der
   juengste ist der mit dem Rueckgaengig-Knopf (§28.8 · 6), und ein Knopf, der
   unter dem Zeiger wegrutscht, ist schlimmer als einer, der ungewohnt sitzt.
   Mit `column` schrumpft der Behaelter nach oben weg und der unterste bleibt,
   wo er ist.

   Ein Toast OHNE Behaelter bleibt gueltig und positioniert sich weiter selbst;
   sonst braeche diese Aenderung jeden bestehenden Verbraucher auf einen Schlag.
   Wer eine HANDLUNG traegt (Rueckgaengig), gehoert nicht in die Mitte, sondern
   in eine eigene Ecke — die Meldung darf die Handlung nicht verdecken. */
.ui-toasts {
  position: fixed;
  z-index: var(--ui-z-toast);
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: calc(100vw - 2 * var(--ui-pad));
  pointer-events: none;   /* der Canvas darunter bleibt bedienbar */
}
.ui-toasts > .ui-toast {
  position: static;
  transform: none;
  pointer-events: auto;
}

.ui-toast {
  position: fixed;
  z-index: var(--ui-z-toast);
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  font-size: var(--ui-size-md);
  color: var(--ui-text);
  background: var(--ui-chrome-solid);
  border: 1px solid var(--ui-hairline);
  border-radius: var(--ui-radius-pill);
  box-shadow: var(--ui-shadow-sheet);
}

/* ---------------------------------------------------------------------------
   10  Kleinteile: Avatar, Marke, Menü, Feld
   --------------------------------------------------------------------------- */

.ui-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  font-size: var(--ui-size-xs);
  font-weight: 700;
  color: var(--ui-text-2);
  background: var(--ui-track);
  border: 1px solid var(--ui-hairline);
  border-radius: var(--ui-radius-pill);
  cursor: pointer;
  list-style: none;
  text-decoration: none;
}
.ui-avatar::-webkit-details-marker { display: none; }

/* Aufklappbare Fläche in der Insel — `<details>`, damit sie ohne JavaScript
   bedienbar bleibt und die Tastatur sie von selbst erreicht. */
.ui-menu { position: relative; }
.ui-menu__panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: var(--ui-z-sheet);
  min-width: 208px;
  padding: 6px;
  background: var(--ui-chrome-solid);
  border: 1px solid var(--ui-hairline);
  border-radius: var(--ui-radius-panel);
  box-shadow: var(--ui-shadow-sheet);
  animation: ui-pop-in var(--ui-t-fast) var(--ui-ease) both;
}
.ui-menu__item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 9px;
  font: inherit;
  font-size: var(--ui-size-md);
  color: var(--ui-text);
  text-align: left;
  text-decoration: none;
  background: transparent;
  border: 0;
  border-radius: var(--ui-radius-sm);
  cursor: pointer;
}
.ui-menu__item:hover { background: var(--ui-hover); }

/* §28.8, Punkt 9: jede Bedienflaeche hat Hover, Aktiv, sichtbaren Fokus und
   Deaktiviert. Ein Menueeintrag, der etwas ein- und ausschaltet („Kommentare
   auch beim Ansehen zeigen"), sah an und aus gleich aus — von Dev2 gemeldet.
   Der Haken kommt aus dem Markup; hier steht nur, wie der Zustand aussieht. */
.ui-menu__item[aria-pressed="true"] { color: var(--ui-accent); font-weight: 600; }
.ui-menu__item[aria-pressed="true"] .ui-menu__check { opacity: 1; }
.ui-menu__check { margin-left: auto; opacity: 0; flex: none; width: 15px; height: 15px; }
.ui-menu__item[disabled] { opacity: 0.45; cursor: not-allowed; }
.ui-menu__sep { height: 1px; margin: 5px 4px; background: var(--ui-hairline); }
.ui-menu__label { padding: 7px 9px 3px; font-size: var(--ui-size-xs); color: var(--ui-text-faint); }

.ui-field { display: block; margin-bottom: 12px; }
.ui-field__label { display: block; margin-bottom: 5px; font-size: var(--ui-size-sm); font-weight: 600; color: var(--ui-text-2); }
.ui-input {
  display: block;
  width: 100%;
  height: 34px;
  padding: 0 10px;
  font: inherit;
  font-size: var(--ui-size-md);
  color: var(--ui-text);
  background: var(--ui-chrome-solid);
  border: 1px solid var(--ui-hairline-strong);
  border-radius: var(--ui-radius-sm);
}
.ui-input::placeholder { color: var(--ui-text-faint); }
/* Deaktiviert — der vierte Zustand aus §28.8 · 9, ebenfalls von Dev2 vermisst.
   Er ist in der Verwaltung nicht theoretisch: `kunden/zeigen` schaltet die Wahl
   des Hauptansprechpartners ab, solange der Kunde keinen Nutzer hat. Ohne
   sichtbaren Zustand sieht das Feld aus, als haette man es uebersehen. */
.ui-input[disabled], .ui-textarea[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
  background: var(--ui-track);
}

/* Zustand am Eintrag: Offen / In Arbeit / Bitte prüfen / Erledigt (Kap. 27.3).
   Dezent — Avatar und Beschriftung, kein Regenbogen (§28.6). */
.ui-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  font-size: var(--ui-size-xs);
  font-weight: 600;
  color: var(--ui-text-2);
  background: var(--ui-track);
  border-radius: var(--ui-radius-pill);
}
.ui-badge--accent { color: var(--ui-accent); background: var(--ui-accent-soft); }

/* Fuenf Toene fuer Zustaende. Die Namen sind KEINE Erfindung: `intern/_badge.blade.php`
   ordnet seit Runde 4 jedem Zustand einen von genau diesen fuenf zu
   (`muted · info · ok · warn · error`). Sie hier gleich zu benennen macht aus der
   Zuordnung eine Identitaet — `'ui-badge--'.$ton` genuegt, und der Sonderfall, den
   die Datei fuer `error` braucht, faellt weg.

   Grundklasse `.ui-badge` IST der Ton `muted`; es gibt deshalb kein `--muted`.

   Zum Rot: §28.6 haelt Rot vom Kundenbild fern („ausschliesslich fuer Loeschen").
   Intern ist ein fehlgeschlagener Build ein Mangel, den MESH WEB behebt — SCHALE.md
   hat das in Runde 6 entschieden: der Fehlton haengt an der Zielgruppe, nicht am
   Sachverhalt. Derselbe Zustand darf innen und aussen verschieden aussehen. */
.ui-badge--info   { color: var(--ui-accent); background: var(--ui-accent-soft); }
.ui-badge--ok     { color: var(--ui-ok);     background: var(--ui-ok-soft); }
.ui-badge--warn   { color: var(--ui-warn);   background: var(--ui-warn-soft); }
.ui-badge--error  { color: var(--ui-danger); background: var(--ui-danger-soft); }
