Files
ApiCore/docs/superpowers/specs/2026-10-13-t5-optik-design.md
Nick SzaunigandClaude Opus 5.5 1806255211 docs: T5 Bewegungs-Sprache (Spec §17) und Plan-Review 2026-10-16
Spec: Zweck, Stufen Voll/Dezent/Aus über useEffectiveMotionLevel,
Tokens aus dem, was Beute und CSS heute nutzen (eine Quelle in
lib/motion/tokens.ts), Regeln je Muster, was nie animiert,
Performance-Regeln, Abnahme; neue Inhaber-Frage 4.
Plan: 22 Korrekturen gegen den aktuellen Code (u. a. 31 statt 17
Checkbox-Stellen, fehlende AREAS, Lebenslinie unter der Beute-Karte,
Fokusring auf der Holo-Bühne), neue Tasks A11 (Bewegungs-Sprache
durchsetzen) und A12 (Beute-Bühne und Sonne-Modus).

Refs #26

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 14:17:03 +02:00

103 KiB
Raw Permalink Blame History

T5 Optik mit eigener Handschrift — Design

Stand: 2026-10-14, Nachtrag 2026-10-16 (§17 Bewegungs-Sprache, Frage 4 in §16; Anlass Beute-Spec §12.4, Issue #26) · Betrifft: ApiCore-Frontend (plus ein kleiner Zusatz am Backend-Endpunkt GET /autopilot/today) · Teilprojekt T5 aus docs/superpowers/specs/2026-10-12-plattform-rueckgrat-und-fahrplan-design.md §10

1. Ziel

ApiCore soll man draußen am Stand lesen können: in der Mittagssonne, mit Handschuhen, mit einer Hand. Und man soll es auf den ersten Blick als Imker-App erkennen, nicht als Verwaltungsvorlage.

  • Jeder Text und jedes Bedienelement erfüllt WCAG 2.2 AA. Die wichtigen Stellen liegen deutlich darüber, damit noch genug Kontrast übrig bleibt, wenn die Sonne auf dem Display spiegelt.
  • Es gibt hell, dunkel und einen Kontrastmodus „Sonne“. Der Wechsel geht mit einem Tipp und flackert nicht.
  • Ein Motiv trägt die Handschrift: die Wabenzelle. Sie zeigt den Zustand der Völker (Waben-Standkarte, Kartenmarker), sitzt am Zeiger des Bienenjahr-Rads, rahmt die Bereichs-Icons und bildet das Markenzeichen. Verläufe, Glas-Effekte und Zierbewegung fallen weg.
  • Der Baukasten wird durchgesetzt. Jede Auswahlgruppe, jede Checkbox, jede Tab-Leiste und jedes Plus/Minus-Feld kommt aus genau einem Baustein, mit einem gemeinsamen Fokusstil.

Technisch heißt das:

  • Farben stehen nur noch als Tokens in globals.css. Es gibt vier geprüfte Sätze: hell, dunkel, Sonne hell, Sonne dunkel. Ein Wächter-Test misst jedes Paar aus Abschnitt 4.4. Ein Token, das einem Satz fehlt, lässt den Test scheitern.
  • Neue Grafiken sind handgeschriebenes SVG, wie heute das Varroa-Diagramm. Keine neue Laufzeit-Bibliothek. next-themes fliegt raus.
  • Wo T4-Daten (Saison, nächste Schritte, Varroa-Bänder) schon da sind, zeigen Rad und Lebenslinie sie an. Fehlen sie, greift ein Kalender-Rückfall. Die Oberfläche funktioniert vor T4 und bricht nach T4 nicht.

Voraussetzungen:

  • T3-Plan docs/superpowers/plans/2026-10-13-t3-erfassung-am-stand.md: Tasks 7 (Navigation, nav-config.ts, Sheets), 8 (NumberStepper, RatingScale mit Richtung und Tönen, Kontrast-Wächter), 11 (Varroa-Trend) und 14 (Stand-Seite mit Ampel, colonyStatusLight, StatusLightBadge) sind gemergt. T5 baut darauf auf und nichts davon neu.
  • Für T5b: T4-Plan docs/superpowers/plans/2026-10-13-t4-logik-ketten.md, Lieferung T4a mit den Tasks 13, 15, 17, 18 und 19, ist gemergt. T4b (Tasks 25, 26, 31) ist optional; ohne T4b gilt die Saison nach Kalender.

Bindende Vorgaben (Inhaber, aus Fahrplan, T3 und T4)

  • ADHS-freundlich: wenig kognitive Last, ein Hauptknopf je Ansicht, kurze deutsche Sätze, Zustand nie nur über Farbe (immer Icon plus Wort).
  • Sicherheit hat Priorität 1, Konsistenz ist oberstes Gesetz: ein Muster, gemeinsame Bausteine, keine Kopien.
  • Touch-Ziele mindestens 44 px, Sheet-Zeilen 56 px. WCAG 2.2 AA.
  • Dark Mode wird angeschlossen, sonst fliegen die Tokens raus (Fahrplan §10). Diese Spec schließt ihn an.
  • Vor jedem Next.js-Code die passende Anleitung unter ApiCore-Frontend/node_modules/next/dist/docs/ lesen (AGENTS.md). Für T5 sind das vor allem 01-app/02-guides/preventing-flash-before-hydration.md, progressive-web-apps.md und 01-app/03-api-reference/03-file-conventions/01-metadata/manifest.md sowie app-icons.md.

Entscheidungen

Frage Entscheidung
Wie wird der Hauptknopf lesbar? Honig als Fläche, dunkle Tinte als Text (9,4:1). Das ersetzt Weiß auf Honigverlauf (2,3:1). Für Text, Links, Icons und den Fokusring auf hellen Flächen gibt es ein eigenes Token --primary-ink (dunkler Bernstein, 6,5:1). text-primary ist verboten, weil heller Honig als Schrift nicht lesbar ist. Abweichung zur Wahl: Offene Frage 1.
Wie unterscheidet sich ein gewählter Zustand? Honigfläche plus 2 px Rahmen in Tinte plus Haken-Icon. Die Fläche allein hätte gegen die Karte nur 1,9:1. Rahmen und Haken tragen den Zustand, auch für Farbenblinde und in der Sonne.
Ampel-Abzeichen im Dunkelmodus Heute steht dort dunkle Schrift auf dunkler Tönung (1,4:1). Darum bekommt jede Zustandsfarbe drei Tokens: Fläche (--condition-x), Text auf der Fläche (-foreground) und Text oder Icon auf normalen Flächen und Tönungen (-ink).
Kontrastmodus „Sonne“ als eigener Tokensatz (html[data-contrast="sun"]): reinweiße Fläche, fast schwarze Tinte, Rahmen in Tinte, keine Tönungen, Schatten oder Unschärfe, 3-px-Fokus, Nebentext 16 px. Sonne funktioniert hell und dunkel. Schaltbar im Profil und mit einem Tipp im Benutzermenü; prefers-contrast: more schaltet Sonne vor, solange der Nutzer nichts gewählt hat.
Wie wird umgeschaltet, ohne zu flackern? Ein kleines Inline-Skript im <head> setzt vor dem ersten Bild class="dark", data-contrast und data-motion auf <html> (Muster aus der Next-Anleitung „Preventing Flash“). Die Werte liegen je Gerät in localStorage, wie heute die Bewegungsstufe. next-themes wird entfernt. Ein Mechanismus für alle drei Einstellungen.
Standard-Farbschema Hell, nicht „Wie Gerät“. Viele Handys stehen dauerhaft auf dunkel; draußen bei Tag ist eine helle Oberfläche besser lesbar. Wahl: Offene Frage 3.
Grundschrift Fließtext, Formular-Beschriftungen, Auswahl-Knöpfe, Listentitel und Hauptknöpfe haben 16 px. Nebentext 14 px. text-xs wird 13 px und ist nur für Achsen, Legenden und Fußzeilen erlaubt. In Sonne 16 bzw. 14 px.
Kompakte Größen Kompakt darf nur ein Gerät mit feinem Zeiger (@media (pointer: fine)) sein, nicht jeder Bildschirm ab sm. Heute bekommt ein Tablet am Stand 32-px-Knöpfe und 36-px-Felder.
Fokusstil Einer für alles: 2 px durchgehender Ring in --ring mit 2 px Abstand (Sonne: 3 px). Er sitzt als Basisregel auf :focus-visible. Komponenten löschen ihn nicht mehr (outline-none, ring-ring/50 werden entfernt). Der Abstand sorgt dafür, dass der Ring gegen den Hintergrund gemessen wird, nicht gegen den Knopf.
Ein Motiv Die Wabenzelle (Sechseck mit spitzer Oberseite) ist das einzige grafische Motiv. Verläufe (--gradient-honey/-sage/-sky) werden entfernt, ebenso der Glas-Effekt in Sonne. Das Wabenmuster HoneycombPattern bleibt nur auf Login und Herkunftsseite.
Waben-Standkarte: Datenquelle Der bestehende Endpunkt GET /apiaries/:id/colony-overview (T3, mit T4-Feldern) je Stand über useQueries. Cache und Offline-Paket-Rückfall der Stand-Seite gelten damit mit. Kein neuer Sammel-Endpunkt.
Randstärke der Zelle Wie im Fahrplan zeigt die Randstärke die Tage seit der letzten Durchsicht, aber in drei Stufen statt stufenlos: dünn = frisch, mittel = bald fällig, dick und gestrichelt = fällig oder nie durchgesehen. Stufenlose Strichstärken sind draußen nicht unterscheidbar. Die genaue Zahl steht im Namen der Zelle und in der Liste.
Lebenslinie und Rad: Daten Nur vorhandene Endpunkte (Volk, Durchsichten, Varroa, Behandlungen, Fütterungen, Schwarmkontrollen, Historie, Königin; ab T4 next-steps und Varroa-Bewertung). Einzige Backend-Änderung: GET /autopilot/today bekommt additiv den Saisonkalender und segmentSince je Stand (9.1).
Diagramm-Bibliothek Keine. Handgeschriebenes SVG wie colony-varroa.tsx. Beim Bau den dataviz-Skill laden (wie T3/T4).
Bewegung Geändert 2026-10-16, siehe §17. Eine Bewegungs-Sprache für die ganze App, abgeleitet aus der Beute: Bewegung nur, wo sie Orientierung gibt (woher kommt das Sheet, was ist weg, was hat geklappt), mit wenigen Tokens und den drei Stufen „Voll“, „Dezent“, „Aus“. Rad, Lebenslinie und Waben bleiben Daten und erscheinen weiter sofort (6.8, 8.5, 9.5). motion ist seit der Beute installiert und wird nur dort genutzt, wo CSS es nicht kann.
Icons Lucide wie bisher. Eine Tabelle AREAS (Abschnitt 11) ist die einzige Quelle für Icon und Farbton je Bereich. Navigation, Kopfzeilen, „+ Neu“ und T4-Vorschläge lesen daraus.
App-Icons Ein Master-SVG, daraus erzeugte PNGs (192/512 „any“, 192/512 „maskable“, 512 „monochrome“, 180 Apple, Favicon). Die erzeugten Dateien liegen im Repo; das Skript nutzt sharp als neue devDependency.

2. Umfang

2.1 Im Umfang (§10 T5 → Lösung)

Punkt aus §10 T5 Lösung Abschnitt
Waben-Standkarte: Stand = Wabe, Volk = Zelle, Füllung über condition-* plus Icon, Randstärke = Tage seit Durchsicht ApiaryComb auf der Stand-Seite und der Bienenstände-Liste 6
Mini-Waben ersetzen die Leaflet-Marker L.divIcon mit SVG-Rosette, Listen-Rückfall 7
Lebenslinie: senkrechte Zeitleiste, Stärkekurve, Band für Milben pro Tag, Königin als Punkt in Zeichenfarbe ColonyLifeline auf der Volksseite 8
Bienenjahr-Rad auf dem Dashboard, ersetzt die Zählkacheln YearWheel mit Saison, Fenstern, Zeiger, Aufgabenpunkten 9
Text auf Hauptknopf ≥ 4,5:1, Rahmen ≥ 3:1, Grundschrift 16 px neue Tokens, -ink-Tokens, Schriftregeln 4
Optional ein Kontrastmodus „Sonne“ 4.3, 5
Dark Mode anschließen oder Tokens raus angeschlossen, Tokens repariert 4, 5
Manifest in Markenfarben mit PNG und maskable Icons neues Markenzeichen, Icon-Satz, Shortcuts 12
Je ein Baustein für Auswahlgruppe, Checkbox, Tabs, Plus/Minus ChoiceGroup, Checkbox/CheckboxField, Tabs, NumberStepper 10
Feste Icon- und Farbzuordnung pro Bereich AREAS, AreaIcon 11
Fokusstil für alle eigenen Bedienelemente globale Fokusregel 4.6, 10.6
Nachtrag 2026-10-16: eine Bewegungs-Sprache für die App, passend zur Beute (Beute-Spec §12.4) Stufen, Tokens, Regeln je Muster, Wächter 17
§11 T5-Befunde: Weiß auf Honig 2,3:1, Eingaberahmen 1,56:1, Dark Mode unerreichbar, Auswahlgruppen von Hand, Checkboxen in 4, Tabs in 3 Varianten alle behoben 4.4, 5, 10

2.2 Befunde aus dem Code, die T5 zusätzlich behebt

Gemessen mit derselben OKLCH-Rechnung wie src/lib/forms/rating-contrast.spec.ts.

Befund Wert heute Lösung Abschnitt
Links und Icons in text-primary auf dem Seitenhintergrund 3,66:1 --primary-ink, Codemod für 32 Stellen 4.2
Fokusring ring-ring/50 auf der Karte 1,94:1 (dunkel 2,59) durchgehender Ring mit Abstand 4.6
Ampel-Abzeichen (Text auf 15-%-Tönung) im Dunkelmodus 1,36–1,43:1 -ink-Tokens 4.2
Warn-Badge im Dunkelmodus 1,49:1 --warning-ink 4.2
Aktiver Navigationseintrag (bg-primary text-primary-foreground) 4,04:1 Honig plus Tinte, 9,38:1 4.2
Weiße Icons auf Honigverlauf (Seitenkopf, Karten, Markenzeichen) 2,39:1 AreaIcon, BrandMark 11, 12
Warn-Icons text-warning auf der Karte 2,90:1 text-warning-ink, 6,09:1 4.2
Diagrammfarbe chart-3 2,90:1 3,93:1 4.2
Ampelfläche „mittel“ als gewählte Skalenstufe 2,65:1 3,03:1, dazu Rahmen und Haken 4.2, 10.2
Ab sm (640 px) Knöpfe 32 px und Felder 36 px, auch auf Tablets mit Touch – Kompaktmaß nur bei pointer: fine 4.5
Toaster nutzt useTheme aus next-themes ohne Provider und folgt damit dem System, die App aber nicht – eigener Appearance-Hook 5
data-motion wird erst in useEffect gesetzt erster Seitenübergang läuft ungebremst Inline-Skript setzt es vor dem ersten Bild 5
TabSwitcher und ActiveHistoryTabs haben role="tablist" ohne Tabpanel und ohne Pfeiltasten – ein Tabs-Baustein auf Base UI 10.4
Farbverläufe je Seite von Hand gewählt (Königinnen Salbei, Kalender Himmel, Inventar mal Salbei, mal Himmel) – Farbe aus AREAS 11
Abweichende Icons: Ernte-Detail Boxes statt Scissors, Schleuderung neu Droplet statt FlaskConical, Lager zusätzlich Package (= Abfüllung) – eine Quelle AREAS 11
Manifest: theme_color #0a0a0a, background_color #ffffff, nur icon.svg; das Icon zeigt drei gelbe Balken statt des Markenzeichens – neuer Icon-Satz 12
Leaflet-Popups sind immer weiß, auch im Dunkelmodus – Popup-Stil aus Tokens 7
ui/* hat eigene dark:-Klassen, die das Token-System umgehen – entfernt, Tokens tragen den Modus 4.1

2.3 Nicht im Umfang

  • Neue Fachlogik. Ampelregeln, Saison, Fälligkeit und Varroa-Bänder kommen aus T3 und T4.
  • Echte Aufstellung der Völker am Stand (Position in der Reihe). Die Zellen stehen nach Volkscode, siehe Offene Frage 2.
  • Neue Diagramme in der Statistik. Die Statistik bekommt nur die neuen Tokens und den Tabs-Baustein.
  • Etiketten-Designer und PDF-Ausgaben. Sie bekommen nur Tokens und Bausteine, wo sie Bedienelemente der App nutzen; Druck bleibt immer hell.
  • Native App, Umgebungslicht-Sensor (zu wenig Browserunterstützung), Haptik.
  • Ein E2E-Framework (Playwright) im Frontend. Die Sichtprüfung läuft per Screenshot-Matrix (13.4).

2.4 Abgrenzung zu T3 und T4

Baustein Liefert T5 ändert
T3 Task 8 RatingScale, RATING_TONE_CLASS, rating-contrast.spec.ts Töne je Stufe, Kontrast-Wächter für Ampelflächen Wächter geht in src/styles/contrast.spec.ts auf (alle Paare, vier Sätze). Gewählte Stufe bekommt Rahmen und Haken wie jede Auswahl.
T3 Task 8 NumberStepper Plus/Minus-Feld API bleibt. Weitere Zählfelder stellen darauf um (10.5).
T3 Task 14 colonyStatusLight, StatusLightBadge, ApiaryColoniesCard Ampel je Volk, Zeilenliste Die Ampel füllt die Zellen. Die Zeilenliste bleibt als Rückfall unter der Wabe.
T3 Task 7 nav-config.ts, quick-actions.ts, Sheets eine Navigationsquelle Icons kommen aus AREAS.
T3 Task 11 Varroa-Trend (SVG) Muster für handgeschriebene Diagramme Vorlage für Lebenslinie und Rad.
T4 Task 15 ColonyOverviewRow.inspectionDueOn, ColonyNextSteps, AutopilotToday Fälligkeit, nächste Schritte, Saison je Stand Randstufen der Zelle, Zukunftszone der Lebenslinie, Rad. AutopilotToday bekommt zwei Felder (9.1).
T4 Task 18 SeasonChip, SEGMENT_LABELS, Heute-Karten Saison-Chips auf dem Dashboard Die Saison je Stand zieht in die Rad-Karte. Die Chip-Zeile entfällt dort, um Doppelanzeige zu vermeiden. SeasonChip bleibt auf der Stand-Seite.
T4 Task 17 INTENT_ICONS Icon je Absicht Werte aus AREAS (Durchsicht, Varroa, Fütterung …).
T4 Task 19 Varroa-Bänder, rating je Messung Ampel je Messung Lebenslinie färbt mit rating.light und markiert band.redAbove.
T4 Task 31 PHASE_SOURCE_LABELS „gemeldet“, „geschätzt“, „Kalender“ Rad zeigt die Quelle damit.

3. Gestaltungsgrundsätze für diese App

  1. Sonne zuerst. Draußen spiegelt das Display, und Spiegelung frisst Kontrast. Darum liegen Fließtext und Hauptknopf über 7:1, nicht knapp über 4,5:1. Flächen sind hell und ruhig, Text ist dunkel. Unterschiede zeigen sich über Helligkeit, Form und Strichstärke, nicht über Sättigung.
  2. Handschuhe. Jedes Ziel mindestens 44 × 44 px, Zeilen in Sheets 56 px. Kein Ziel, das nur durch Hover oder einen langen Druck erreichbar ist. Benachbarte Ziele haben mindestens 4 px Abstand.
  3. Ein Blick, eine Sache. Jede Ansicht hat einen Hauptknopf in Honig. Alles andere ist umrandet oder Text. Eine Grafik beantwortet genau eine Frage:
    • Waben: Welches Volk braucht mich?
    • Lebenslinie: Wie hat sich das Volk entwickelt?
    • Rad: Wo stehen wir im Bienenjahr, was kommt?
  4. Nie nur Farbe. Jeder Zustand hat ein Icon und ein Wort, jede Grafik einen Text und eine Tabelle. Die Zellen tragen den Volkscode und ein Zustands-Icon. Gestrichelte Ränder und Schraffuren unterscheiden auch ohne Farbsehen.
  5. Vertraute Bedienung, eigene Handschrift im Detail. Knöpfe, Felder, Tabs und Sheets verhalten sich wie überall im Web. Die Handschrift sitzt in Material (Wachs und Honig), Tinte und dem einen Motiv Wabenzelle, nicht in erfundenen Bedienelementen.
  6. Ruhe. Keine Verläufe, kein Glas, keine Zierbewegung, keine Großbuchstaben-Laufschrift. Schatten sind leise und entfallen in Sonne. Die Farbtöne der Bereiche erscheinen nur als Icon-Kachel, nicht als Fläche. Bewegung folgt §17. Glas, Leuchten und Ruhebewegung gibt es nur auf der Beute-Bühne (§17.8).

Verboten (der Wächter-Test prüft, was sich prüfen lässt):

  • Weiß auf Honig
  • text-primary als Schriftfarbe
  • ring-ring/50 und outline-none ohne Ersatz
  • Farbverlauf als Hintergrund
  • Text unter 13 px
  • Zustand nur über Farbe
  • eigene role="radio|checkbox|tab"-Bauten außerhalb des Baukastens
  • Bewegung gegen §17: motion-safe: für Verschiebung, transition-all, animierte Breite, Höhe oder Unschärfe, bewegte Datenwerte

4. Tokens

4.1 Grundregeln

  • Jede Farbe hat eine Rolle. Fläche (--primary, --condition-good, …) trägt ihren Text auf der Fläche (--primary-foreground, --condition-good-foreground). Für Text und Icons auf normalen Flächen gibt es Tinte (--primary-ink, --warning-ink, --condition-good-ink).

  • Vier Sätze in dieser Reihenfolge in globals.css:

    1. :root (hell)
    2. .dark (dunkel)
    3. :root[data-contrast="sun"] (Sonne hell)
    4. .dark[data-contrast="sun"] (Sonne dunkel)

    Die Sonne-Sätze überschreiben nur die Tokens aus 4.3.

  • @custom-variant dark (&:where(.dark, .dark *)); statt heute (&:is(.dark *)), damit die Klasse direkt auf <html> greift. Dazu kommen die Varianten sun, pointer-fine und pointer-coarse.

  • ui/* und components/* enthalten keine dark:-Klassen mehr. Ein Modus ist nur ein anderer Tokensatz. Einzige Ausnahme: Leaflet-Kacheln (7.4).

  • Entfernt: --gradient-honey, --gradient-sage, --gradient-sky. Die Komponente PageHeader/EntityCard verliert den Prop gradient.

  • @media print setzt immer den hellen Satz.

  • @media (forced-colors: active):

    • Zellen, Ränder, Ringe und Rad-Segmente bekommen CanvasText-Striche.
    • Zustände bleiben über Icon, Muster und Strichstärke erkennbar.
    • SVG-Flächen nutzen forced-color-adjust: auto.

4.2 Werte hell und dunkel

OKLCH ist die Quelle, Hex dient nur zur Orientierung. Alle Werte liegen im sRGB-Farbraum (vom Skript geprüft).

Token Hell Dunkel Verwendung
--background oklch(0.968 0.014 85) #f9f4ea oklch(0.19 0.016 55) #1a120d Seite („Wachs“)
--foreground oklch(0.21 0.03 55) #23140a oklch(0.95 0.018 80) #f5ede1 Text („Tinte“)
--card, --popover, --sidebar oklch(0.995 0.004 90) #fefdfa oklch(0.235 0.018 55) #251c16 Karten, Sheets
--muted oklch(0.925 0.018 82) #ece5d9 oklch(0.28 0.018 55) #302720 ruhige Flächen, Rad-Segmente
--muted-foreground oklch(0.44 0.03 60) #5f4f42 oklch(0.76 0.025 75) #bbafa0 Nebentext
--primary oklch(0.80 0.145 78) #f0b13f oklch(0.80 0.14 78) #efb146 Honig: Hauptknopf, Auswahl, aktiver Eintrag
--primary-foreground oklch(0.21 0.035 55) #251408 oklch(0.19 0.03 55) #1e1006 Text auf Honig
--primary-ink neu oklch(0.47 0.115 55) #8a4603 oklch(0.82 0.13 80) #f0ba59 Links, Akzent-Icons, aktiver Tab-Strich
--ring = --primary-ink = --primary-ink Fokusring
--secondary / -foreground oklch(0.44 0.07 145) / oklch(0.98 0.015 100) oklch(0.66 0.08 145) / oklch(0.16 0.02 145) Salbei, Zweitknopf
--accent / -foreground oklch(0.935 0.03 78) / oklch(0.25 0.045 50) oklch(0.31 0.03 60) / oklch(0.95 0.018 80) Hover, Menüs
--destructive oklch(0.50 0.17 28) #af2b25 oklch(0.70 0.15 28) #ed7668 Text, Icon, Rahmen
--destructive-foreground neu oklch(0.985 0.005 30) oklch(0.17 0.03 30) Text auf voller Fläche (Bestätigen-Dialog)
--border oklch(0.86 0.022 78) #d9d0c1 oklch(0.34 0.02 60) #40362e nur Trennlinien und Kartenkanten (Deko)
--input oklch(0.60 0.035 65) #8f7c6b oklch(0.56 0.025 70) #7e7265 Rahmen jedes Bedienelements
--warning / -foreground oklch(0.79 0.15 80) / oklch(0.23 0.04 60) oklch(0.80 0.14 80) / oklch(0.20 0.03 60) Warnfläche
--warning-ink neu oklch(0.50 0.11 65) #8d5406 oklch(0.82 0.13 82) #eebb58 Warn-Icon und -Text
--success / -foreground oklch(0.46 0.08 145) / oklch(0.98 0.015 100) oklch(0.70 0.09 145) / oklch(0.16 0.02 145) Erfolg
--condition-excellent / -foreground / -ink 0.62 0.13 150 / 0.15 0.04 150 / 0.40 0.09 150 0.68 0.12 150 / 0.17 0.04 150 / 0.80 0.11 150 Ampel „sehr gut“
--condition-good / … 0.645 0.13 125 / 0.17 0.04 125 / 0.41 0.09 125 0.72 0.12 125 / 0.18 0.04 125 / 0.83 0.11 125 Ampel „gut“, Zelle grün
--condition-average / … 0.665 0.125 88 / 0.19 0.04 90 / 0.44 0.09 85 0.77 0.13 90 / 0.20 0.04 90 / 0.86 0.11 90 Ampel „Achtung“, Zelle gelb
--condition-weak / … 0.665 0.14 58 / 0.18 0.04 55 / 0.46 0.12 52 0.74 0.13 60 / 0.19 0.04 55 / 0.82 0.11 60 Ampel „schwach“
--condition-critical / … 0.62 0.17 30 / 0.15 0.04 30 / 0.47 0.16 28 0.68 0.16 28 / 0.16 0.04 30 / 0.78 0.11 28 Ampel „kritisch“, Zelle rot
--chart-1 … --chart-5 0.56 0.13 55, 0.46 0.08 145, 0.60 0.12 85, 0.50 0.17 28, 0.52 0.08 250 0.78 0.13 70, 0.70 0.09 145, 0.82 0.12 90, 0.70 0.15 28, 0.72 0.08 250 Diagramme; Rad-Fenster: Tracht = 3, Varroa = 4, Einwinterung = 5
--area-betrieb neu oklch(0.46 0.08 150) oklch(0.74 0.09 150) Bereichston (Abschnitt 11)
--area-erfassung neu oklch(0.50 0.12 60) oklch(0.80 0.12 70)
--area-arbeit neu oklch(0.48 0.08 250) oklch(0.76 0.07 250)
--area-ernte neu oklch(0.52 0.11 75) oklch(0.80 0.10 80)
--area-neutral neu oklch(0.45 0.02 60) oklch(0.76 0.02 70)
--queen-white/-yellow/-red/-green/-blue neu 0.97 0.005 90, 0.86 0.17 95, 0.58 0.20 27, 0.58 0.15 145, 0.52 0.15 255 gleich, außer rot 0.62 0.20 27, grün 0.66 0.15 145, blau 0.62 0.15 255 Zeichenfarbe der Königin, immer mit 2-px-Ring in --foreground
--radius 0.75rem gleich unverändert
--shadow-soft, --shadow-elevated unverändert unverändert in Sonne none

Bestehende Klassen werden so umgestellt (Codemod, Abschnitt 10.8):

  • text-primary → text-primary-ink
  • text-warning → text-warning-ink
  • text-condition-x-foreground in Abzeichen → text-condition-x-ink (COLONY_CONDITION_TONE_CLASS, StatusLightBadge)
  • border-primary bei Auswahlzuständen → border-foreground

4.3 Kontrastmodus „Sonne“

Ziel: draußen bei voller Sonne alles mit maximaler Helligkeitsdifferenz lesbar, ohne dass sich Bedienung oder Anordnung ändern.

Token Sonne hell Sonne dunkel
--background, --card, --popover, --sidebar oklch(1 0 0) #ffffff oklch(0.10 0.005 55) #040302
--foreground, --primary-foreground, --ring oklch(0.13 0.02 55) #0d0602 --foreground/--ring oklch(0.99 0.005 85)
--muted oklch(0.95 0.01 80) oklch(0.20 0.01 55)
--muted-foreground oklch(0.32 0.03 60) oklch(0.88 0.02 80)
--border oklch(0.40 0.03 60) oklch(0.70 0.02 70)
--input oklch(0.20 0.03 55) oklch(0.92 0.01 80)
--primary-ink oklch(0.38 0.10 52) wie dunkel

Dazu kommen Regeln unter [data-contrast="sun"]:

  • Schrift: --text-sm: 1rem, --text-xs: 0.875rem. Tailwind 4 liest die Größen aus diesen Variablen, deshalb wirkt das ohne Klassenänderung.
  • Fokus: --focus-width: 3px.
  • Rahmen: Rahmen von Bedienelementen 2 px (--control-border-width: 2px).
  • Flächen: keine Schatten (--shadow-*: none). Kopfzeile undurchsichtig (backdrop-filter: none, bg-card). Tönungen bg-*/15 werden zu bg-card mit 2-px-Rahmen in der jeweiligen Tinte (Hilfsklasse .tint statt /15).
  • Grafik: Wabenränder 3/5/7 px statt 2/4/6 px. Keine Honig-Tönung unter der Stärkekurve.

4.4 Gemessene Kontraste (Ist → Soll)

Grenzen: Text 4,5:1, Bedienrahmen, Fokus und Grafik 3:1. Für Fließtext und Hauptknopf ist das Ziel 7:1. Die Werte unten sind die Sollwerte, die der Wächter-Test prüft.

Paar Ist hell Soll hell Ist dunkel Soll dunkel Sonne hell Sonne dunkel Grenze
Text auf Hauptknopf 2,31 (Verlaufsanfang) / 4,04 9,38 1,72 (Verlaufsende) 9,77 10,61 9,77 7
Grundtext / Seite 15,25 16,23 15,21 15,98 20,15 20,01 7
Grundtext / Karte 17,15 17,56 13,46 14,45 20,15 20,01 7
Nebentext / Seite 5,77 7,13 6,78 8,60 12,77 14,33 4,5
Nebentext / --muted 4,88 6,27 5,29 6,81 11,03 12,62 4,5
Link (--primary-ink) / Seite 3,66 6,48 7,05 10,48 10,37 11,66 4,5
Eingaberahmen / Karte 1,56 3,92 1,66 3,58 18,19 16,25 3
Eingaberahmen / Seite – 3,62 – 3,96 18,19 16,25 3
Fokusring / Karte (mit Abstand) 1,94 7,01 2,59 9,47 20,15 20,01 3
Fokusring / Seite – 6,48 – 10,48 20,15 20,01 3
Warn-Icon/-Text / Karte 2,90 6,09 6,84 9,49 6,18 11,68 4,5
Warn-Text / Warn-Tönung 14,14 5,53 1,49 6,92 5,60 9,37 4,5
Fehlertext / Karte 6,41 6,45 4,10 5,87 6,54 7,23 4,5
Fehlertext / Fehler-Tönung 5,48 5,50 3,66 5,06 5,57 6,58 4,5
Salbei-Knopf 7,11 7,11 5,56 6,43 7,11 6,43 4,5
Ampel-Abzeichen (Tinte / Tönung), schlechteste Stufe 12,61 6,06 1,36 6,45 6,14 8,49 4,5
Text auf Ampelfläche, schlechteste Stufe 4,71 5,02 4,66 6,32 5,02 6,32 4,5
Ampelfläche / Karte (Zelle, gewählte Stufe), schlechteste 2,65 3,03 4,10 5,41 3,07 6,66 3
Diagramm chart-1…5 / Karte, schlechteste 2,90 (chart-3) 3,93 4,10 5,87 3,99 7,23 3
Bereichston / Karte, schlechtester – 5,54 – 7,52 5,54 7,52 3
Königinnenfarbe mit Tinten-Ring / Karte – 17,56 (Ring) – 14,45 (Ring) 20,15 20,01 3

Abzeichen im hellen Modus fallen bewusst von 12–16:1 auf 6–7,4:1. Statt fast schwarzer Schrift zeigen sie nun den Farbton als Tinte, damit „Kritisch“ und „Gut“ auch ohne Icon verschieden aussehen. Sie bleiben deutlich über 4,5:1.

Die Honigfläche hat gegen die Karte nur 1,87:1. Das ist zulässig: Ein Knopf wird über seinen Text erkannt, und gewählte Zustände tragen Rahmen und Haken (Entscheidungen). Im Rad bekommt das aktuelle Segment einen 2-px-Rahmen in Tinte (9.3).

4.5 Schrift und Größen

Rolle Größe Klasse Familie
Fließtext, Formular-Beschriftung, Auswahl-Knöpfe, Listentitel, Knopftext, Eingabe 16 px text-base Inter
Nebentext (Datum, Hinweis, Begründung) 14 px (Sonne 16) text-sm Inter
Achsen, Legenden, Fußzeilen 13 px (Sonne 14) text-xs (--text-xs: 0.8125rem) Inter
Überschriften h1–h4 20 / 24 / 30 px wie heute Space Grotesk, nie in Knöpfen oder Labels
Codes, Losnummern wie Umgebung font-mono IBM Plex Mono
  • Diese Bausteine stellen Inhaltstext von text-sm auf text-base um:
    • Field-Label
    • ChoiceGroup-Option
    • CheckboxField-Label
    • Button-Größe touch (heute text-[0.95rem])
    • Input/Textarea (md:text-sm entfällt; 16 px verhindert auch das Zoomen in iOS)
    • Zeilentitel in Listen, DetailHeader-Untertitel
    • EmptyState-Beschreibung
  • Größen: size="default" bei Button und Input hat 44 px Höhe. 32 bzw. 36 px gibt es nur mit pointer-fine: statt sm:. size="touch" bleibt 44 px. In ui/button.tsx, ui/input.tsx, ui/select.tsx, ui/textarea.tsx und ui/tabs.tsx wird sm:h-* zu pointer-fine:h-*.
  • Ziele: Icon-Knöpfe ohne Text (icon, icon-sm) bekommen bei pointer-coarse eine Trefferfläche von 44 px über ein ::after-Element, ohne optisch zu wachsen.

4.6 Fokusstil

In @layer base:

:root { --focus-width: 2px; --focus-offset: 2px; }
:where(:focus-visible) { outline: var(--focus-width) solid var(--ring); outline-offset: var(--focus-offset); }
  • Alle outline-none, focus-visible:ring-3 focus-visible:ring-ring/50 und has-[:focus-visible]:outline-* in ui/*, components/* und app/* entfallen (18 Stellen ring-ring/50 in 17 Dateien, 22 outline-none).
  • Wo der Fokus auf einem inneren Element liegt (Label-Zeile mit verstecktem Input, Wabenzelle), zeigt der Baustein den Ring am äußeren Element über :has(:focus-visible). Bei der Wabenzelle ist das ein zweiter Sechseckpfad (6.6).
  • Fehlerzustand (aria-invalid): Rahmen in --destructive, 2 px, dazu der Fehlertext am Feld. Der Fokusring bleibt in --ring.
  • Fokus wird nicht verdeckt (WCAG 2.4.11): Das bestehende scroll-padding bleibt.

5. Darstellung umschalten

5.1 Einstellungen

Einstellung Werte Standard Attribut auf <html> Speicher (je Gerät)
Farbschema „Hell“, „Dunkel“, „Wie Gerät“ Hell (Offene Frage 3) class="dark" oder nichts localStorage apicore.theme
Kontrast „Normal“, „Sonne“ Normal; ohne gespeicherten Wert Sonne bei prefers-contrast: more data-contrast="sun" apicore.contrast
Bewegung „Voll“, „Dezent“, „Aus“ Voll (wie heute) data-motion apicore.motion (heute schon)

Alles wird je Gerät gespeichert, nicht am Konto. Draußen am Handy will man Sonne, am Schreibtisch nicht. Das folgt dem bestehenden Muster der Bewegungsstufe.

5.2 Ablauf

  1. Vor dem ersten Bild: src/components/appearance-script.tsx rendert im <head> von app/layout.tsx ein Inline-Skript nach dem Muster InlineScript der Next-Anleitung „Preventing Flash“.
    • Es liest die drei Schlüssel in try/catch und setzt Klasse und Attribute.
    • Es setzt den Inhalt von <meta name="theme-color"> auf die Seitenfarbe des aktiven Satzes.
    • <html> bekommt suppressHydrationWarning.
    • Das Skript kommt aus derselben Konstante wie der Hook (APPEARANCE_KEYS), damit Schlüssel und Werte nie auseinanderlaufen.
    • CSP: script-src 'self' 'unsafe-inline' (proxy.ts) erlaubt das schon. Wird die CSP später verschärft, kommt der SHA-256-Hash des Skripts in die Liste. Das Skript enthält keine Nutzerdaten.
  2. Zur Laufzeit: useAppearance() (src/lib/appearance/appearance.ts) liefert die Werte und Setter.
    • Ein Setter schreibt localStorage und das Attribut.
    • Er hört auf matchMedia('(prefers-color-scheme: dark)'), solange „Wie Gerät“ gewählt ist, und auf prefers-contrast, solange nichts gewählt ist.
    • Er hört auf das storage-Ereignis, damit ein zweiter Tab mitzieht.
    • motion-preference.ts behält seine API; applyMotionLevel wird vom Skript übernommen, der useEffect in providers.tsx entfällt.
  3. Toaster: components/ui/sonner.tsx liest useAppearance().resolvedTheme statt next-themes. next-themes wird aus package.json entfernt.
  4. Metadaten: viewport.themeColor in app/layout.tsx:
    • [{ media: '(prefers-color-scheme: light)', color: '#f9f4ea' }, { media: '(prefers-color-scheme: dark)', color: '#1a120d' }]
    • Das Skript überschreibt den Wert bei manueller Wahl.

5.3 Bedienung

  • Profil → Karte „Darstellung“ (ersetzt die Karte „Bewegung & Effekte“, appearance-section.tsx statt motion-section.tsx). Drei ChoiceGroup-Felder (Aussehen „Karten“ mit Beschreibung, 10.2):
    • Farbschema: „Hell – am besten bei Tageslicht“, „Dunkel – für abends“, „Wie Gerät“.
    • Kontrast: „Normal“, „Sonne – maximaler Kontrast, größere Schrift, für draußen“.
    • Bewegung: Texte wie heute.
    • Darunter steht: „Gilt nur für dieses Gerät.“ Die Änderung wirkt sofort und ohne Übergang.
  • Schnellschalter: Im Benutzermenü (Avatar) steht oben ein Eintrag „Sonnenmodus“ als DropdownMenuCheckboxItem (role="menuitemcheckbox", 44 px), mit Sonnen-Icon und Haken. Vom Stand aus sind das zwei Tipps, ohne Seitenwechsel. Nach dem Umschalten erscheint der Toast „Sonnenmodus an“ bzw. „aus“.
  • Abmelden löscht diese Einstellungen nicht. Sie sind Gerätevorlieben, keine Daten.

5.4 Seiten außerhalb der App

  • Login und /herkunft/[lot] folgen denselben Tokens und Einstellungen. Ein Kunde ohne gespeicherten Wert sieht „Hell“.
  • Druck (@media print) und PDF-Vorschau bleiben hell.

6. Waben-Standkarte

Frage, die sie beantwortet: Welches Volk an diesem Stand braucht mich, und wann war ich zuletzt dran?

6.1 Daten

  • Je Stand ColonyOverview aus GET /apiaries/:id/colony-overview, Query-Key ["apiaries", apiaryId, "colony-overview"] (wie T3 Task 14). Offline greift withPackFallback (T3 Task 15).
  • Je Zeile: code, status, Bedingungen, lastInspectionDate, openWarnings, openCritical. Ab T4 zusätzlich inspectionDueOn, segment, nextStepTitle.
  • Zustand der Zelle = colonyStatusLight(row, { today, inspectionsVisible }), unverändert aus T3/T4.
  • Auf der Bienenstände-Liste lädt useQueries die Übersicht aller aktiven Stände, höchstens 20. Bei mehr Ständen zeigen die übrigen Karten nur die Zusammenfassung, ohne Wabe; das ist im Betrieb nicht zu erwarten.

6.2 Aufbau

Stand-Seite, Karte „Völker“ (Handy, 328 px Inhaltsbreite):

┌ Völker (7) ─────────────────────────────────────────┐
│ 5 Gut · 1 Achtung · 1 Kritisch · 2 Durchsichten fällig│
│                                                      │
│    ⬡B1   ⬡B2   ⬡B3   ⬡B4         ⬡ = Zelle 64 px     │
│       ⬡B5   ⬡B6   ⬡B7            versetzte Reihe     │
│                                                      │
│ ⬡ Gut  ⬡ Achtung  ⬡ Kritisch · Rand: dünn = frisch,  │
│ dick = Durchsicht fällig                             │
│ ──────────────────────────────────────────────────── │
│ Braucht Blick                                        │
│  B3  ▲ Achtung  Futter schwach · 16 Tage ohne Durchs.│
│  B6  ✕ Kritisch Weisellos                            │
│ [ Alle 7 Völker als Liste ▾ ]                        │
└──────────────────────────────────────────────────────┘
  • Zusammenfassung oben: ein Satz mit Icons, nach Ampel gezählt, dazu fällige Durchsichten (ab T4 aus inspectionDueOn, vorher Stufe 3 der Randregel).
  • Wabe: die Zellen.
  • Legende: immer sichtbar, eine Zeile, 13 px. Sie erklärt die drei Füllungen mit Wort und Icon und den Rand.
  • „Braucht Blick“: nur Völker mit Gelb oder Rot, mit Gründen. Das ist die Arbeitsliste. Bei null Völkern steht dort „Alle Völker sind in Ordnung.“
  • „Alle 7 Völker als Liste“: ein Knopf mit aria-expanded, der die T3-Zeilenliste aufklappt. Das ist der Tabellen-Rückfall für alle, die die Wabe nicht lesen können oder wollen. Ab T4 zeigt die Liste auch „Durchsicht fällig in 2 Tagen“ und den nächsten Schritt (T4 Task 18).
  • Der Hauptknopf der Seite bleibt „Runde starten“ (T3).

Bienenstände-Liste (/apiaries, Tab „Aktiv“): Jeder aktive Stand ist eine Karte.

┌ ⬡ Nordhang ──────────────── Schwarmzeit · alle 7 Tage ┐   ← AreaIcon, Name (Link), SeasonChip (T4)
│ 7 Völker · 1 Achtung · 1 Kritisch                     │
│   ⬡ ⬡ ⬡ ⬡                                              │   Zellen 56 px, verlinken zum Volk
│    ⬡ ⬡ ⬡                                               │
└───────────────────────────────────────────────────────┘
  • Die Liste behält ihren Hauptknopf „+ Neuer Bienenstand“. Die Karten haben keine eigenen Knöpfe. Der Name führt zum Stand, die Zellen führen zu den Völkern.
  • Der Tab „Historie“ bleibt eine EntityCard-Liste ohne Wabe.
  • Die Legende steht einmal oben über allen Karten.

6.3 Zelle

  • Form: Sechseck mit spitzer Oberseite. Breite (Abstand der senkrechten Seiten) 64 px bei pointer-coarse, 56 px bei pointer-fine, auf der Liste 56 bzw. 48 px. Höhe = Breite × 2/√3. Der Innenkreis (= Breite) ist immer mindestens 48 px, also mindestens 44 px Trefferfläche.

  • Anordnung:

    • Reihen mit n Zellen. n = floor((Breite_Container − (b + 4)/2) / (b + 4)), mindestens 3, höchstens 10.
    • Jede zweite Reihe ist um (b + 4)/2 nach rechts versetzt und um ein Viertel der Höhe nach oben verzahnt. Abstand 4 px.
    • n misst ein ResizeObserver (useContainerWidth).
    • Reihenfolge nach Volkscode, natürlich sortiert, wie Liste und Runde (Offene Frage 2).
  • Füllung nach Ampel: Grün --condition-good, Gelb --condition-average, Rot --condition-critical. Text und Icon in --condition-*-foreground. Alle drei Flächen haben mindestens 3:1 gegen die Karte (4.4).

  • Inhalt:

    • oben das Zustands-Icon (16 px; CheckCircle2, AlertTriangle, XCircle wie StatusLightBadge)
    • darunter der Volkscode (16 px halbfett; bis 6 Zeichen, länger mit Auslassung, voller Code im Namen)
    • Bei mehr als einem Grund steht ein kleiner Zähler „2“ unten rechts in der Zelle (13 px).
  • Rand = Tage seit der letzten Durchsicht, in Tinte (--foreground), innen gezeichnet, damit die Zelle gleich groß bleibt:

    Stufe Bedeutung ab T4 (mit inspectionDueOn) vor T4 (INSPECTION_STALE_DAYS = 14) Strich Sonne
    1 frisch alles gut Fälligkeit mehr als 3 Tage entfernt, oder Winterruhe (inspectionDueOn = null) höchstens 10 Tage her 2 px durchgehend 3 px
    2 bald fällig bald hin fällig heute bis in 3 Tagen 11–14 Tage her 4 px durchgehend 5 px
    3 fällig jetzt hin Fälligkeit überschritten oder nie durchgesehen mehr als 14 Tage oder nie 6 px gestrichelt (6 4), kleines Uhr-Icon oben rechts 7 px
    • Ohne inspections:read (inspectionsVisible = false) gibt es einen 1-px-Rand in --input und keine Aussage zur Durchsicht.
    • Die 3-Tage-Grenze ist dieselbe wie „SOON“ in der T4-Rangfolge.
    • Die Funktion rimStepOf(row, today, inspectionsVisible) liegt in src/lib/honeycomb/cell-state.ts.
  • Zustand „Winterruhe“: Ab T4 heißt die Rand-Erklärung im Namen „Winterruhe – keine Durchsicht nötig“.

6.4 Bedienung

  • Jede Zelle ist ein Link zur Volksseite. Tippen öffnet das Volk; es gibt kein Menü und keinen langen Druck.
  • Hover (nur pointer-fine) hebt die Zelle mit einem 2-px-Rahmen in --primary-ink hervor, ohne Bewegung und ohne Größenänderung.
  • Kein Ziehen, kein Zoomen. Bei vielen Völkern wächst die Wabe in die Höhe.

6.5 Barrierefreiheit

  • Semantik: <ul aria-label="Völker am Stand {Name}"> mit <li><a> je Zelle. Die Sechseck-Form ist ein aria-hidden-SVG im Link; Text und Icon liegen als HTML darüber.
  • Name jeder Zelle (aria-label, beginnt mit dem sichtbaren Code, damit Sprachsteuerung funktioniert): „B3 – Achtung: Futter schwach. Letzte Durchsicht vor 16 Tagen.“, ab T4 „… Durchsicht seit 2 Tagen fällig.“
  • Tab-Reihenfolge = Codereihenfolge, Zeile für Zeile.
  • Zusammenfassung und Legende sind normaler Text. Die aufklappbare Liste ist die Tabellen-Alternative.
  • Forced Colors: Fläche Canvas, Rand CanvasText. Die Stufen bleiben über Strichstärke und Strichelung erkennbar, der Zustand über das Icon.

6.6 Fokus in der Zelle

Der Link selbst hat outline: none als einzige erlaubte Ausnahme. Stattdessen zeichnet ein zweiter Sechseckpfad 3 px außerhalb der Zelle den Ring in --ring (.comb-cell:focus-visible .comb-focus { stroke: var(--ring) }, Breite var(--focus-width)). Der Wächter-Test führt diese Datei in der Ausnahmeliste.

6.7 Leistung

  • Ein <symbol id="comb-hex"> je Seite, jede Zelle nutzt <use href>. Keine neue Abhängigkeit.
  • Budget: ApiaryComb mit allen Teilen höchstens 5 kB gzip. 100 Zellen rendern in unter 30 ms Scripting auf einem Mittelklasse-Handy (Messung mit dem React Profiler bei 4-facher CPU-Drosselung).
  • Die Layout-Rechnung combLayout(count, columns) ist eine reine Funktion, mit useMemo.

6.8 Bewegung

Keine Animation. Ändert sich der Zustand nach einem Sync, wechselt die Füllung sofort.

7. Mini-Waben auf der Karte

Frage: Wo steht welcher Stand, und braucht dort etwas meinen Blick?

7.1 Marker

  • L.divIcon({ html: miniCombSvg(summary), className: "stand-marker", iconSize: [48, 52], iconAnchor: [24, 26], popupAnchor: [0, -24] }) ersetzt L.icon mit den PNG-Markern. Die Dateien in public/leaflet/ werden gelöscht.
  • miniCombSvg(summary) (src/lib/honeycomb/mini-comb-svg.ts, reine Funktion, liefert einen SVG-String):
    • Hintergrund-Sechseck (Radius 24) in --card mit 2 px Rand in --foreground. So hebt sich der Marker von jeder Kachel ab.
    • Darauf eine Rosette aus 7 Zellen (Radius 7): eine Mitte, sechs ringsum.
    • Bis zu 7 Völker sind je eine Zelle, sortiert nach Ampel, das schlechteste in die Mitte. Freie Plätze sind Wachs-Umrisse (--muted, Rand --input).
    • Bei mehr als 7 Völkern zeigen die Zellen die 7 schlechtesten. Darunter steht ein Zählchip „12“ (13 px fett, --card mit Tinten-Rand).
    • Farben über var(--condition-*). Weil das divIcon im DOM liegt, ziehen Theme und Sonne ohne Neuaufbau mit.
  • Unter dem Marker steht der Standname als Chip (13 px, --card, Tinten-Rand), ab Zoomstufe 9. Darunter wird er ausgeblendet, damit sich nichts überlappt.
  • Vor dem Laden der Übersicht zeigt der Marker eine einzelne Wachs-Zelle ohne Zahl.

7.2 Bedienung

  • Tippen oder Enter öffnet das Popup:
    • Standname
    • Zusammenfassung wie in 6.2
    • Knopf „Stand öffnen“ (44 px, size="touch", Umriss)
  • Keine Bedienung einzelner Zellen auf der Karte. Sie wären für Handschuhe zu klein.
  • Popup im Token-Stil: .leaflet-popup-content-wrapper, .leaflet-popup-tip mit --popover/--popover-foreground und Tinten-Rand. Den Schließknopf .leaflet-popup-close-button vergrößert ein Pseudo-Element auf 44 px.

7.3 Barrierefreiheit und Rückfall

  • L.marker(..., { keyboard: true, title }). Nach add setzt der Code aria-label am Marker-Element: „Stand Nordhang: 7 Völker, 1 kritisch, 1 Achtung“.
  • Unter der Karte steht die Liste „Stände auf der Karte“ (<ul>): Name als Link, Zusammenfassung, „2 Durchsichten fällig“. Sie ist die Alternative zur Karte und hilft auch, wenn die Kacheln offline nicht laden.
  • Ohne Netz: Hinweis „Die Karte braucht Internet. Die Stände stehen unten als Liste.“ (role="status").

7.4 Dunkel und Sonne

  • Dunkel: Die Kachel-Ebene bekommt filter: brightness(0.8) contrast(1.1) (.dark .leaflet-tile-pane). Keine Farbumkehr, weil invertierte Karten Wasser und Wald falsch färben.
  • Sonne: Kacheln unverändert, Marker-Rand 3 px.

7.5 Leistung

Ein SVG-String je Stand, gemerkt nach den Ampel-Zählern. Keine neue Abhängigkeit. Leaflet bleibt per dynamic(..., { ssr: false }) nachgeladen.

8. Lebenslinie des Volks

Frage: Wie hat sich dieses Volk entwickelt, und was kommt als Nächstes?

8.1 Daten

buildLifeline(input, today) in src/lib/colonies/lifeline.ts ist eine reine Funktion. Sie liefert ein LifelineModel mit Zeitachse, Spuren und Ereignissen. useColonyLifeline(colonyId) sammelt die Queries; sie nutzen dieselben Keys wie die Karten der Volksseite, also keine Doppel-Requests.

Spur Quelle Key ab T4
Lebensbeginn Colony.createdAt, ColonyHistory.lineageAsResult ["colonies", id], ["colonies", id, "history"] –
Stärke (besetzte Waben) Inspection.colonyStrength, date ["colonies", id, "inspections"] Obergrenze 40 (T4 4.14)
Milben pro Tag VarroaMeasurement mit method = NATURAL_MITE_FALL und calculatedValue, Dauer aus durationMinutes ["colonies", id, "varroa"] (Key wie colony-varroa.tsx) rating.light, rating.band.redAbove (T4 Task 19)
Ampel ohne T4 varroaLight(rate, threshold) (T3) ["varroa", "threshold"] entfällt
Behandlungen Treatment.date, endDate, product ["colonies", id, "treatments"] –
Königin ColonyHistory.queenHistory (Beginn, Ende), je Königin markingColor, code, hatchYear ["queens", queenId] (höchstens 5) –
Ereignisse Fütterungen, Schwarmkontrollen, Umsetzen (apiaryHistory), Ableger/Vereinigung (lineage*), Königinwechsel vorhandene Keys –
Zukunft offene Aufgaben des Volks mit Termin in den nächsten 21 Tagen, höchstens 3 ["tasks", …] GET /colonies/:id/next-steps → steps[].dueOn, title (höchstens 3)
  • Einheitswechsel der Stärke: colonyStrength zählt erst seit T3 besetzte Waben, vorher war es eine Skala.
    • Die Konstante STRENGTH_IN_FRAMES_SINCE (lifeline.ts) ist das Rollout-Datum von T3 Task 9. Es wird beim Bau aus dem Deploy-Protokoll eingetragen.
    • Ältere Werte erscheinen nicht in der Kurve, nur in der Tabelle mit der Einheit „Skala (alt)“.
    • So vermischt die Kurve keine Einheiten.
  • Rechte: Fehlt ein Leserecht (403), fällt die Spur weg, ohne Fehlermeldung. Das gilt für inspections:read (Stärke), varroa:read (Milben), treatments:read (Behandlungen) und queens:read (Königin). Die Kopfzeile nennt nur vorhandene Spuren.

8.2 Aufbau

Senkrechte Zeitleiste, oben die Gegenwart, darüber eine schmale Zukunftszone. Handy, 328 px:

 Lebenslinie                                   Stärke 0 ─5─10─15─20   Milben/Tag ┆10
 ┌──────┬────┬────┬────────────────────────────┬──────────────┐
 │      │    │ ⬡  │ Durchsicht mit Schwarmk. · 15.05.   (Zukunft, gestrichelt)
 │ heute│━━━━│━━━━│━━━━━━━━━━━━━━━━━━━━━━━━━━━━│━━━━━━━━━━━━━━│
 │ Mai  │ ┃  │ ●  │            ●9               │ ▇▇▇ 2,1      │
 │      │ ┃  │    │         ╱                   │              │
 │      │ ┃  │ 🍴 │      ●6                     │              │
 │ Apr  │ ┃  │    │    ╱                        │ ▇▇▇▇▇▇ 8,4 ▲ │
 │      │ ◉  │ 👑 │  ●4    Königin Q-12 (blau)  │              │
 │ Mär  │    │    │                             │░░░ Behandlung│
 └──────┴────┴────┴────────────────────────────┴──────────────┘
   Monat Königin Ereignisse   Stärke (besetzte Waben)   Milben pro Tag
 ● Q-12 · Schlupf 2025 · blau · seit 20.04.2026
 [ Ganzes Leben zeigen ]   [ Als Tabelle ▾ ]

Spalten (Handy / Desktop):

Spalte Breite Inhalt
Monat 44 px Monatsnamen (13 px), Jahreswechsel fett
Königin 16 px Schiene 4 px in Zeichenfarbe mit 1 px Tinten-Rand für die Amtszeit. Zu Beginn ein Punkt (Radius 6, 2 px Ring in --foreground). Ohne Farbe: graue gestrichelte Schiene.
Ereignisse 24 px Icons 16 px aus AREAS in einer Kreiskachel 20 px. Am selben Tag nur ein Icon mit Zähler.
Stärke Rest (≥ 160 px) Achse 0 bis max(20, aufgerundet auf 5) Waben, Raster bei 5er-Schritten gepunktet. Linie in --foreground, 2 px. Punkte (Radius 4) je Durchsicht. Fläche unter der Linie --primary 18 % (Sonne ohne Fläche). Lücke über 45 Tage: Linie unterbrochen, gestrichelte Verbindung, im Text „keine Durchsicht“.
Milben 64 px (Desktop 96) Stufenband: je Messung ein Balken über ihre echte Liegezeit (1–7 Tage, mindestens 4 px hoch), Breite ∝ Milben/Tag bis zum Doppelten der Schwelle (darüber ▶). Farbe nach Ampel (--condition-good/-average/-critical), dazu Wert als Text bei Gelb, Rot und der letzten Messung. Schwelle: vor T4 eine gestrichelte senkrechte Linie mit Wert im Kopf; ab T4 je Balken ein Strich bei band.redAbove, weil die Grenze mit der Jahreszeit wechselt. Behandlungen: schraffierter Hintergrund (--chart-4 30 %, 45°-Streifen) von date bis endDate, Icon Syringe in der Ereignisspalte.
  • Zeitmaß: 1,6 px je Tag (etwa 48 px je Monat).
  • Standardausschnitt: die letzten 12 Monate (höchstens seit Lebensbeginn) plus 21 Tage Zukunft.
  • „Ganzes Leben zeigen“ schaltet auf die gesamte Lebenszeit. Der Knopftext wechselt zu „Letzte 12 Monate“ (aria-pressed).
  • Heute: waagerechte Linie in --foreground, 2 px, über alle Spalten, beschriftet „heute“.
  • Zukunft (über heute):
    • Höchstens drei Schritte als hohle Sechsecke in der Ereignisspalte, mit Titel und Datum in einer Zeile in der Stärke-Spalte.
    • Die Zukunftszone ist gestrichelt umrandet.
    • Ab T4 sind das next-steps, vorher offene Aufgaben.
    • Der Hinweis withdrawalUntil (T4) erscheint als Satz unter der Grafik: „Wartezeit bis 20.08.“
  • Wertbeschriftung: Die Stärke-Kurve beschriftet nur den letzten Wert, den höchsten und den niedrigsten Wert im Ausschnitt sowie Punkte, die mindestens 24 px Abstand haben. So überlappen keine Zahlen.

8.3 Platzierung und Bedienung

  • Volksseite: eigene Karte „Lebenslinie“ direkt unter der T4-Karte „Als Nächstes“, auf dem Desktop in der rechten Spalte oben. Der Hauptknopf der Seite bleibt „Durchsicht erfassen“.
  • Bedienung: nur zwei Knöpfe, „Ganzes Leben zeigen“ und „Als Tabelle“ (aria-expanded). Punkte und Balken sind nicht antippbar; sie wären zu klein für Handschuhe. Details stehen in der Tabelle und in den bestehenden Karten (Durchsichten, Varroa, Behandlungen).
  • Leer: Ohne Durchsicht und Messung steht dort „Noch keine Durchsicht. Die Lebenslinie wächst mit jeder Durchsicht.“ und der Knopf „Durchsicht erfassen“ als Umriss. Königin und Ereignisse erscheinen trotzdem, falls vorhanden.
  • Laden: ein Skelett-Block in voller Höhe des Ausschnitts, kein Spinner.
  • Fehler: QueryState-Muster mit „Erneut versuchen“.

8.4 Barrierefreiheit

  • <figure> mit <svg role="img" aria-labelledby="{titleId} {descId}">.
  • <title>: „Lebenslinie {Code}“.
  • <desc> (aus dem Modell erzeugt, höchstens 4 Sätze):
    1. „Volk B3 seit 12.03.2025.“
    2. „Stärke zuletzt 9 Waben am 02.10., höchstens 14 Waben im Juli.“
    3. „Milben zuletzt 2,1 pro Tag am 28.09. – Gut.“
    4. „Königin Q-12, Schlupf 2025, blau, seit 20.04.2026. 2 Behandlungen in diesem Ausschnitt.“
  • „Als Tabelle“ klappt eine <table> auf: Spalten Datum | Ereignis | Wert. Je Durchsicht „Durchsicht · 9 besetzte Waben · 5 Brutwaben“, je Messung „Varroa · 8,4 Milben/Tag · Achtung“, je Behandlung „Behandlung Oxuvar · 01.09.–14.09.“, dazu Königin, Fütterung, Umsetzen und Ableger. Die Tabelle stammt aus demselben LifelineModel wie die Grafik und hat dieselbe Zeitspanne.
  • Zeichenfarbe der Königin immer zusätzlich als Wort („blau“).
  • Forced Colors: Linie und Balken in CanvasText, Ampel über Text und Schraffur.

8.5 Leistung und Bewegung

  • Ein SVG, höchstens 600 Knoten für drei Jahre Daten (etwa 150 Durchsichten).
  • buildLifeline und das Rendern bleiben unter 50 ms Scripting bei 4-facher CPU-Drosselung.
  • Komponente höchstens 8 kB gzip, keine Abhängigkeit.
  • Keine Animation, kein Aufbau-Effekt.

9. Bienenjahr-Rad auf dem Dashboard

Frage: Wo stehen wir im Bienenjahr, und welches Arbeitsfenster kommt?

9.1 Daten

  • Saisonabschnitte und Kalender:

    • Vor T4 aus src/lib/season/season-defaults.ts. Das sind exakt die T4-Standardwerte (PHENOLOGY.calendarFallback: Salweide 03-20, Kirsche 04-15, Apfel 04-28, Holunder 05-30, Linde 06-20, Trachtende 08-01, Herbst 09-15, Winter 11-15; swarmSeason Kirsche bis Linde; SEASON_AUTOPILOT.winterVarroaWindow 11-15 bis 12-20).
    • Ab T4 kommen sie aus dem Server, damit Änderungen in „Verwaltung > Automatisierung“ auch im Rad ankommen.
  • Additive Erweiterung von GET /autopilot/today (T5-Task am Backend, Recht colonies:read wie bisher, keine Geheimnisse):

    interface AutopilotToday {
      // … unverändert aus T4 Task 15 …
      seasons: { apiaryId: string; apiaryName: string; segment: SeasonSegmentKey; phase: PhenologyPhaseKey | null;
        phaseSource: PhaseSourceKey | null;
        segmentSince: string;            // neu, aus SeasonState
        reportedOn: string | null;       // neu
        note: SeasonNote }[];            // neu: 'NO_COORDINATES' | 'WEATHER_GAPS' | 'WEATHER_STALE' | null
      seasonCalendar: {                  // neu, aus readRule(PHENOLOGY) und readRule(SEASON_AUTOPILOT)
        calendarFallback: Record<PhenologyPhaseKey, string>;   // 'MM-DD'
        swarmSeason: { from: PhenologyPhaseKey; until: PhenologyPhaseKey };
        winterVarroaWindow: { from: string; to: string };
      };
    }
    
    • Gebaut in NextStepsService.today aus SeasonService.statesFor (hat segmentSince schon) und readRule.
    • Fehlen die Felder (älteres Backend), nutzt das Frontend season-defaults.ts.
  • Bezugsstand:

    • Das Rad zeigt den Abschnitt, in dem die meisten aktiven Stände sind.
    • Bei Gleichstand gilt der weiter fortgeschrittene Abschnitt. Lieber eine Durchsicht zu früh als einen Schwarm verpasst (T4 7.3).
    • Abweichende Stände nennt die dritte Zeile im Zentrum: „Südhang: noch Frühjahr“. Bei mehr als einem Stand steht dort „2 Stände weichen ab – siehe Tabelle“.
  • Aufgaben: offene Aufgaben mit Termin aus der bestehenden Dashboard-Query ["tasks", "dashboard"].

    • Je Kalenderwoche ein Punkt.
    • Überfällige Aufgaben bilden einen eigenen Punkt kurz vor dem Zeiger.
    • Vorschläge (T4) zählt das Rad nicht. Sie haben ihre eigene Liste.
  • Modell: buildYearWheel(input, today) in src/lib/season/year-wheel.ts (reine Funktion):

    interface YearWheelModel {
      year: number; today: string;
      segments: { key: SeasonSegmentKey; from: string; to: string; source: 'REPORT' | 'GTS' | 'CALENDAR'; current: boolean; estimated: boolean }[];
      windows: { key: 'TRACHT' | 'VARROA_SUMMER' | 'EINWINTERUNG' | 'VARROA_WINTER'; from: string; to: string }[];
      taskWeeks: { weekStart: string; count: number }[]; overdueCount: number;
      reference: { apiaryId: string; apiaryName: string } | null;
      deviations: { apiaryName: string; segment: SeasonSegmentKey }[];
    }
    
    • Fenster:

      • Tracht = Salweide bis Trachtende
      • Varroa Sommer = Trachtende bis Herbst
      • Einwinterung = Herbst bis Winter
      • Varroa Winter = winterVarroaWindow

      Die Fenster liegen nacheinander und überlappen nicht.

    • Der aktuelle Abschnitt beginnt bei segmentSince (gemeldet, geschätzt oder Kalender).

    • Vergangene und künftige Grenzen sind Kalenderwerte mit estimated = true und werden gestrichelt gezeichnet.

    • Ist ein Kalenderdatum schon vorbei, der Abschnitt aber noch nicht erreicht (späte Blüte), beginnt der nächste Abschnitt im Rad „ab morgen, noch offen“.

    • Die Winterruhe über den Jahreswechsel wird als zwei Bögen gezeichnet.

9.2 Aufbau

Feste Größe 320 × 320 px. Unter 328 px Containerbreite skaliert das Rad mit, Text bleibt dabei mindestens 13 px.

                 J
           D  ·  ┃  ·  F                 Monatsinitialen r=150 (13 px)
        N   ╭────┃────╮   M              Außenring r=124, 26 px: Saisonabschnitte
          ╭─╯ ╭──┃──╮ ╰─╮                  aktuell: Honig + Tinten-Rand, sonst --muted
     O   │  ╭─╯     ╰─╮  │   A           Innenring r=96, 10 px: Tracht / Varroa ▨ / Einwinterung
         │  │ HERBST   │  │              Aufgabenpunkte r=78
     S   │  │ seit 15.09.·Kalender │  M  Zentrum r<62: drei Zeilen Text
         │  │ Einwinterung bis ~15.11.│
          ╰─╮ ╰─────╯ ╭─╯              Zeiger: von r=66 bis r=140, 3 px Tinte,
        A   ╰─────⬡───╯   J             am Ende eine Wabenzelle (Honig, Tinten-Rand)
           J           J
 ▬ Tracht   ▨ Varroa-Behandlung   ▬ Einwinterung     ● Aufgabe   ◉ überfällig
 Jetzt: Herbst seit 15.09. (Kalender) · Danach: Winterruhe ab ca. 15.11.
 [ Als Tabelle ▾ ]                                     Zum Kalender →
  • Winkel: Januar oben, im Uhrzeigersinn. winkel(datum) = 360° × (Tag im Jahr − 1) / Tage im Jahr − 90°; Schaltjahre werden berücksichtigt.

  • Außenring:

    • Segmente mit 2 px Lücke in --card, Kontur 1,5 px in --input.
    • Das aktuelle Segment hat eine Honigfläche und 2 px Rahmen in --foreground.
    • Geschätzte Grenzen sind gestrichelt.
    • Keine Beschriftung im Ring: Bei 26 Tagen Frühjahr wäre der Bogen zu kurz für lesbare Schrift. Namen stehen im Zentrum, in der Zeile darunter und in der Tabelle.
  • Innenring:

    • Tracht in --chart-3 (voll)
    • Varroa in --chart-4 mit 45°-Schraffur (zwei Fenster)
    • Einwinterung in --chart-5 (voll)

    Muster plus Legende, damit die Fenster auch ohne Farbsehen unterscheidbar sind.

  • Aufgabenpunkte:

    • Radius 3 px (1 Aufgabe), 4,5 px (2–3) bzw. 6 px (4 und mehr), in --foreground.
    • Überfällige: ein Punkt in --condition-critical mit 2 px Tinten-Ring, 4° vor dem Zeiger.
  • Zentrum:

    1. Abschnitt (22 px, Space Grotesk halbfett)
    2. „seit {Datum} · {Quelle}“ (14 px; Quelle aus PHASE_SOURCE_LABELS: „gemeldet am 18.04.“, „geschätzt“, „Kalender“; bei note zusätzlich „Wetterdaten lückenhaft“ bzw. „ohne Koordinaten“)
    3. das laufende Arbeitsfenster mit Ende, oder die Abweichung eines Stands
  • Zeiger: Linie in --foreground mit der Wabenzelle als Kappe. Er steht auf heute und bewegt sich nie animiert.

  • Darunter: die Legende (eine Zeile, 13 px) und der Satz „Jetzt … · Danach …“.

  • Karte: Überschrift „Bienenjahr“ (h2). Rechts steht der Textlink „Zum Kalender“ (44 px Trefferfläche) als einzige Aktion; „Als Tabelle“ ist eine Aufklapp-Steuerung.

9.3 Dashboard nach T5

Reihenfolge von oben nach unten:

  1. Kopf: Gruß als h1 in Tinte auf dem Seitenhintergrund und die Datumszeile, ohne Honigbanner und ohne Weiß auf Honig. Die Honigfarbe gehört allein dem Hauptknopf.
  2. „Als Nächstes“ (T4 aus /autopilot/today, offline aus Aufgaben): einziger Honigknopf der Seite.
  3. „Außerdem heute“.
  4. T4-Karte „Durchsicht fällig“ je Stand.
  5. „Im Blick behalten“ (Meldungen).
  6. „Bienenjahr“ (Rad). Es ersetzt die Kacheln „Überblick“ (Zähler für Stände, Völker, Königinnen, Aufgaben, Honigchargen, Bestand). Die Zähler entfallen ersatzlos:
    • Die Bereiche sind über die Navigation erreichbar.
    • „unter Minimum“ steht schon als Meldung LOW_STOCK in „Im Blick behalten“.
    • Die T4-Chips „Saison je Stand“ ziehen in das Zentrum und die Tabelle des Rads.

9.4 Barrierefreiheit

  • <svg role="img"> mit <title> „Bienenjahr {Jahr}“ und <desc>: „Heute, 14.10.: Herbst seit 15.09. nach Kalender. Einwinterung läuft bis etwa 15.11. Danach Varroa-Winterbehandlung 15.11. bis 20.12. Offene Aufgaben mit Termin: 3 in den nächsten 4 Wochen, 1 überfällig.“

  • „Als Tabelle“ öffnet zwei Tabellen:

    • Abschnitt | von | bis | Quelle (je Stand eine Spalte, wenn Stände abweichen)
    • Fenster | von | bis

    Dazu kommt die Zeile „Aufgaben je Woche“ für die nächsten 4 Wochen.

  • Keine interaktiven Teile im SVG. Die Punkte wären zu klein (eine Woche entspricht etwa 15 px Bogen).

9.5 Leistung und Bewegung

  • Etwa 150 SVG-Knoten, Rechnung in useMemo, höchstens 6 kB gzip, keine Abhängigkeit.
  • Keine Animation, auch nicht bei „Voll“. Der Zeiger springt nicht und dreht sich nicht.

10. Baukasten durchsetzen

10.1 Bausteine

Baustein Datei ersetzt Stellen heute
Auswahlgruppe ChoiceGroup components/form/choice-group.tsx (bleibt) Ausgang des Winters (winter-check-dialog.tsx, Knöpfe mit aria-pressed), Linien-Ausrichtung im Etiketten-Designer (zwei Knöpfe), Bewegungsstufe im Profil (native Radio-Karten) 3; die übrigen der 7 aus der Analyse vom 12.10. hat Paket 0 schon umgestellt
Checkbox Checkbox + CheckboxField components/ui/checkbox.tsx (umgebaut), components/form/checkbox-field.tsx (neu) ToggleChip (4 Dateien), native type="checkbox" (9 Stellen: Ernte-Dialog, Webhook anlegen/bearbeiten ×3, Aufgabenvorlage, Etiketten-Designer ×2, Aufgabenliste, harvest-select-field), Base-UI-Checkbox (3 Dateien), honey-check (Heute-Liste) 4 Varianten, 17 Stellen
Tabs Tabs components/ui/tabs.tsx (umgebaut) TabSwitcher (2 Seiten), ActiveHistoryTabs (9 Seiten, wird Vorlage auf Tabs), Statistik (ui/tabs) 3 Varianten, 12 Stellen
Plus/Minus NumberStepper components/form/number-stepper.tsx (bleibt) ganzzahlige Zählfelder am Stand (10.5) 6 Felder
Fokus globals.css 4 Fokus-Varianten in Komponenten 44 Stellen
Bereichs-Icon AreaIcon components/area-icon.tsx (neu) weiße Icons auf Verlauf in PageHeader, DetailHeader, EntityCard 3 Komponenten, 10 gradient-Props
Markenzeichen BrandMark components/brand-mark.tsx (neu) Hexagon auf Verlauf in Sidebar, Kopfzeile, Login, Herkunft 4

Der Wächter-Test src/styles/baukasten-guard.spec.ts prüft die Regeln aus 10.7.

10.2 Auswahlgruppe

  • Nicht gewählt: Fläche --card, Rahmen 1 px --input (3,9:1), Text 16 px.
  • Gewählt (Antwort): Fläche --primary, Text --primary-foreground halbfett, Rahmen 2 px --foreground, Haken-Icon (16 px) vor dem Label.
  • Gewählt, „Nicht geprüft“ (null): Fläche --muted, Rahmen 2 px gestrichelt --foreground, Strich-Icon. Damit bleibt der T3-Gedanke „Nicht geprüft ist gedämpft“, ist aber erkennbar gewählt.
  • Skalen (RatingScale): Die Ton-Fläche (selectedClassName) bleibt. Rahmen und Haken kommen dazu; der Haken steht vor der Zahl („✓ 4“).
  • Neues Prop appearance:
    • "buttons" (Standard, wie heute Raster)
    • "cards": Option mit Label (16 px) und Beschreibung (14 px), volle Breite, mindestens 56 px hoch; für Darstellung, Bewegung und Ausgang des Winters
  • Neues Feld ChoiceOption.icon?: Lucide-Icon vor dem Label, z. B. beim Ausgang des Winters.
  • Tastatur bleibt wie heute: ein Tab-Halt, Pfeiltasten, Home und End.
  • Toolbar-Umschalter im Etiketten-Designer (fett, kursiv) sind keine Auswahlgruppe. Sie bekommen aria-pressed und aria-label am bestehenden Button (Prop pressed). Ausnahme im Wächter.
  • Kalender: Die Tageswahl im Monatsraster bleibt ein Raster aus Tag-Knöpfen mit aria-pressed (Ausnahme im Wächter). Sie bekommt nur den globalen Fokus und die neue Auswahl-Optik.

10.3 Checkbox

  • ui/checkbox.tsx (Base UI Checkbox):
    • Kästchen 20 px, Rahmen 2 px --input, Radius 6 px.
    • Gewählt: Fläche --primary, Rahmen --foreground, Haken 14 px in --primary-foreground, Strichstärke 3.
    • indeterminate: Strich-Icon.
    • Deaktiviert: 50 % Deckkraft, Hinweistext am Feld statt nur Grau.
  • CheckboxField mit label, description?, checked, onCheckedChange, disabled?, appearance:
    • "row" (Standard): ganze Zeile klickbar, mindestens 44 px (in Sheets 56 px über size="sheet"), Text 16 px
    • "tile": umrandete Kachel wie ToggleChip; gewählt mit Honig-Tönung und 2-px-Tinten-Rand; Sonne: --card mit Tinten-Rand
    • "task": die Abhak-Zelle der Heute-Liste; die honey-check-Animation bleibt als einzige „Moment“-Bewegung, jetzt auf dem Base-UI-Primitiv statt eines eigenen role="checkbox"
  • ToggleChip wird nach der Umstellung gelöscht.

10.4 Tabs

  • ui/tabs.tsx mit einer Variante segment (Standard):
    • Leiste in --muted, Innenabstand 4 px.
    • Reiter 44 px (pointer-fine: 36 px), Text 16 px.
    • Aktiv: Fläche --card, Text --foreground halbfett, unten ein 3-px-Strich in --primary-ink. Der Zustand zeigt sich also über Fläche, Gewicht und Strich, nicht nur über Farbe.
  • Base UI liefert role="tab"/tabpanel, aria-controls und Pfeiltasten.
  • Die Variante line entfällt; die Statistik nutzt segment.
  • ActiveHistoryTabs wird eine Vorlage auf Tabs (Props value, onValueChange, activeCount, historyCount, children je Panel). Die 9 Listen legen ihre Liste in das Panel. TabSwitcher wird gelöscht; die beiden Einstellungsseiten nutzen Tabs direkt.

10.5 Plus/Minus

  • Regel: Ein ganzzahliger Zählwert, der typischerweise unter 100 liegt und am Stand erfasst wird, nutzt NumberStepper. Messwerte mit Komma (kg, Liter, %) und Einstellungswerte am Schreibtisch nutzen Input inputMode="decimal" bzw. "numeric".
  • Umstellung, beim Bau je Feld bestätigen:
    • Besetzte Wabengassen in der Einwinterung (winter-seams)
    • Milbenzahl in varroa-edit-dialog (wie im T3-Neuformular)
    • Weiselzellen in Schwarmkontrolle neu und bearbeiten
    • Entnahme und gezählter Bestand in stock-card
    • Startposition im Etikettendruck
  • Bleibt Eingabefeld:
    • Wartezeit in Tagen (Einstellwert)
    • Schlupfjahr (Jahreszahl)
    • Mengen in Rohstoffen und Herstellung (Gramm)
    • MFA-Codes
    • Spalten und Zeilen im Etiketten-Designer
    • Werte in Aufgabenvorlagen und Regeln

10.6 Fokus für eigene Bedienelemente

Alle eigenen Bedienelemente nehmen den globalen Fokus aus 4.6:

  • ChoiceGroup, CheckboxField, NumberStepper
  • Tage im Kalender
  • honey-check
  • Wabenzelle (eigener Pfad, 6.6)
  • Leaflet-Marker
  • Links in Zeilen (ApiaryColoniesCard, TodayRow)

10.7 Wächter-Regeln (baukasten-guard.spec.ts)

Der Test durchsucht src/**/*.tsx ohne *.spec.tsx. Ausnahmen stehen als Liste mit Begründung im Test.

Regel Ausnahmen
kein role="radio", role="radiogroup", type="radio" außerhalb von choice-group.tsx –
kein role="checkbox", type="checkbox" außerhalb von ui/checkbox.tsx –
kein role="tablist", role="tab" außerhalb von ui/tabs.tsx –
aria-pressed nur in ui/button.tsx (Prop pressed) und calendar/page.tsx Kalender-Tage
kein ring-ring/50, kein outline-none honeycomb-cell.tsx (6.6)
kein text-primary ohne -foreground/-ink –
kein gradient- und kein text-white herkunft/[lot]/page.tsx nur für das Foto-Overlay, falls nötig
kein dark: apiary-map.tsx (Kachel-Filter, nur in CSS)
keine Schriftgröße unter 13 px (text-[10px], text-[11px], text-[12px]) –
PageHeader, DetailHeader, EntityCard ohne Prop gradient – (Typfehler)

10.8 Migrationsreihenfolge (Seiten brechen nie)

Jeder Schritt ist ein eigener, grüner Commit des Orchestrators. Lint, Typecheck, vitest run und next build laufen nach jedem Schritt.

  1. Tokens additiv:
    • Neue Tokens und Varianten in globals.css ergänzen (-ink, area-*, queen-*, --destructive-foreground, Sonne-Sätze, pointer-*, sun). Bestehende Werte bleiben noch.
    • contrast.spec.ts prüft schon die neuen Paare.
  2. Darstellung:
    • Appearance-Modul und Inline-Skript.
    • Profil-Karte „Darstellung“ und Schnellschalter.
    • next-themes raus, Toaster umgestellt.
    • Dunkel ist ab hier erreichbar, aber noch mit alten Werten. Darum bleibt der Schalter „Dunkel“ bis Schritt 4 hinter einem Entwicklungs-Flag (NEXT_PUBLIC_APPEARANCE_DARK=1).
  3. Codemod Farben (ein Skript scripts/codemod-t5-colors.mjs, Ergebnis per Diff geprüft):
    • text-primary → text-primary-ink, text-warning → text-warning-ink
    • Abzeichen-Klassen auf -ink
    • border-primary bei Auswahl → border-foreground
    • Die Honig-Fläche bleibt bg-primary.
  4. Tokenwerte umstellen:
    • Werte aus 4.2 einsetzen, Verläufe entfernen.
    • ui/button Standard ohne Verlauf, dark:-Klassen aus ui/* entfernen.
    • Der Wächter contrast.spec.ts wird ab hier hart (alle vier Sätze).
    • Das Entwicklungs-Flag aus Schritt 2 entfällt.
  5. Fokus und Größen:
    • Globale Fokusregel.
    • Entfernen der Komponenten-Fokusklassen (Codemod), sm:h-* → pointer-fine:h-*, Schriftgrößen aus 4.5.
  6. AREAS und AreaIcon:
    • nav-config.ts, quick-actions.ts, PageHeader, DetailHeader, EntityCard (Prop area statt gradient), BrandMark, Icon-Abweichungen aus 2.2.
    • Ist T4 Task 17 gemergt: INTENT_ICONS aus AREAS.
  7. Auswahlgruppe: neue Optik, appearance="cards", die drei Umstellungen.
  8. Checkbox: Checkbox umbauen, CheckboxField neu; dann je Datei umstellen (Formulare zuerst, dann Einstellungen, zuletzt honey-check). ToggleChip löschen.
  9. Tabs: ui/tabs umbauen, ActiveHistoryTabs als Vorlage, die 9 Listen und 2 Einstellungsseiten umstellen. TabSwitcher löschen.
  10. Plus/Minus: Felder aus 10.5.
  11. Wächter: baukasten-guard.spec.ts wird hart.
  12. Manifest und Icons (12), Service-Worker-Version.
  13. T5b (Abschnitte 6–9):
    • Wabe auf der Stand-Seite, dann Bienenstände-Liste
    • Marker
    • Lebenslinie
    • Backend-Zusatz /autopilot/today, dann Rad und Dashboard-Umbau

Schritte 1–5 berühren viele Dateien mechanisch. Sie laufen in einem kurzen Fenster, in dem keine anderen Frontend-Tasks offen sind (15).

11. Icon und Farbe je Bereich

Quelle ist src/lib/areas.ts:

export type AreaTone = 'betrieb' | 'erfassung' | 'arbeit' | 'ernte' | 'neutral';
export interface Area { key: AreaKey; label: string; icon: LucideIcon; tone: AreaTone; href?: string }
export const AREAS: Readonly<Record<AreaKey, Area>>;
  • AreaIcon({ area, size }) zeichnet das Icon in einer Wabenzelle (Sechseck, 40 px; in Listen 32 px).
  • Die Zelle ist mit dem Bereichston in 14 % getönt und hat 1 px Rand im Ton; das Icon steht im Ton. Sonne: Fläche --card, Rand 2 px.
  • Die Navigation bleibt neutral (Icons in --muted-foreground, aktiv Honig). Bunte Navigationslisten wären Unruhe.
  • Der Ton erscheint nur in:
    • AreaIcon (Seitenkopf, Detailkopf, Karten)
    • im „+ Neu“-Sheet
    • bei den Ereignis-Icons der Lebenslinie
    • bei den Absichts-Icons der T4-Vorschläge

Regel: Ein Bereichston ist nie eine Zustandsfarbe. Rot, Grün und Gelb der Ampel sowie --destructive und --warning sind für Zustände reserviert.

Bereich Schlüssel Icon (lucide) Ton Hinweis
Start dashboard LayoutDashboard neutral
Suche search Search neutral
Bienenstände apiaries MapPin betrieb auch Kartenansicht
Völker colonies Layers betrieb Beute = gestapelte Zargen; das Sechseck ist der Marke vorbehalten
Königinnen queens Crown betrieb bisher Salbei-Verlauf von Hand
Überwinterung winter Snowflake betrieb
Pflanzenentwicklung (T4b) phenology Flower2 betrieb
Durchsicht inspections ClipboardCheck erfassung
Varroa varroa Bug erfassung
Schwarmkontrolle swarm-control Sprout erfassung bleibt, weil schon gelernt (T3)
Behandlung treatments Syringe erfassung
Fütterung feedings Utensils erfassung bleibt, weil schon gelernt (T3)
Aufgaben tasks ListTodo arbeit
Kalender calendar CalendarDays arbeit bisher Himmel-Verlauf von Hand
Vorschläge (T4) suggestions Lightbulb arbeit wie T4 Task 17
Meldungen notifications Bell arbeit
Ernten harvests Scissors ernte Detailseite bisher Boxes
Schleuderungen extractions FlaskConical ernte Neu-Seite bisher Droplet
Honigchargen honey-batches Droplet ernte
Abfüllungen bottling Package ernte
Produkte products ShoppingBag ernte
Rohstoffe raw-materials Boxes ernte
Herstellungschargen production-batches Factory ernte
Lager storage Warehouse ernte bisher zusätzlich Package
Inventar equipment Wrench ernte bisher Salbei oder Himmel
Statistik statistics BarChart3 neutral
Benutzer & Rollen users-roles Users neutral
Automatisierung automation Zap neutral
Etikettenvorlagen label-templates Tag neutral bisher Himmel
Backup backup Database neutral
Audit-Log audit-log History neutral bisher Salbei
Profil profile UserRound neutral bisher Salbei
  • Eine Seite, die einen anderen Bereich nur erwähnt (z. B. die Schleuderung in der Honigcharge), darf dessen Icon tragen. Ein Icon bedeutet immer seinen Bereich.
  • Die öffentliche Herkunftsseite behält ihre eigenen Icons.

12. Manifest und Icons

12.1 Markenzeichen

  • Form: eine verdeckelte Wabenzelle.
    • Sechseck mit spitzer Oberseite in Honig (#f0b13f) mit 6 % Rand in Tinte.
    • Darin ein kleineres Sechseck in hellem Wachs (oklch(0.88 0.11 85) #f9d280) als Deckel, leicht nach oben versetzt.
    • Es ist bei 16 px als Honig-Sechseck lesbar.
  • Master: public/brand/apicore-mark.svg.
  • App-Icon: Hintergrund in dunkler Tinte oklch(0.24 0.035 55) #2c1b0f, Zelle mittig. Honig auf Tinte hat 8,7:1. Das ist eine Weiterentwicklung des bisherigen Schwarz-Gelb, kein Bruch.
  • BrandMark-Komponente: dieselbe Zelle als SVG (24/32 px) neben dem Schriftzug „ApiCore“ (Space Grotesk). Sie ersetzt Hexagon auf Verlauf in Sidebar, Kopfzeile, Login und Herkunftsseite.

12.2 Dateien

Datei Größe purpose Inhalt
public/icon.svg skalierbar any Zelle auf Tinte, Ecken 22 % gerundet (ersetzt die Balken)
public/icons/icon-192.png, icon-512.png 192, 512 any wie icon.svg
public/icons/maskable-192.png, maskable-512.png 192, 512 maskable Tinte randlos, Zelle innerhalb der sicheren Zone (Kreis mit 40 % Radius, also Zelle höchstens 66 % der Kante)
public/icons/monochrome-512.png 512 monochrome Zellen-Silhouette, weiß auf transparent (Android-Themen-Icons)
public/icons/shortcut-inspection-96.png, shortcut-varroa-96.png, shortcut-today-96.png 96 – Bereichs-Icon in Tinte auf Wachs
src/app/apple-icon.png 180 – Zelle auf Tinte, ohne eigene Rundung (iOS maskiert). Ersetzt apple-icon.tsx (Satori-Balken).
src/app/favicon.ico 16, 32, 48 – Zelle ohne Hintergrund
  • Erzeugt werden die Dateien mit scripts/generate-icons.mjs aus dem Master-SVG (sharp als devDependency; heute nur transitiv über Next vorhanden). Das Skript ist idempotent. Die Ausgabe wird committet, damit das Docker-Image nicht vom Skript abhängt.

12.3 app/manifest.ts

{
  id: "/", name: "ApiCore – Imkereiverwaltung", short_name: "ApiCore",
  description: "Digitale Imkereiverwaltung für Wabenhain", lang: "de", dir: "ltr",
  start_url: "/", scope: "/", display: "standalone",
  background_color: "#f9f4ea",          // = --background hell
  theme_color: "#f9f4ea",               // Statusleiste; dunkel über viewport.themeColor (5.2)
  categories: ["productivity", "business"],
  icons: [ /* any 192/512, maskable 192/512, monochrome 512, icon.svg any */ ],
  shortcuts: [
    { name: "Durchsicht erfassen", short_name: "Durchsicht", url: "/inspections/new", icons: [{ src: "/icons/shortcut-inspection-96.png", sizes: "96x96" }] },
    { name: "Varroa messen", short_name: "Varroa", url: "/varroa/new", icons: [ … ] },
    { name: "Heute", url: "/dashboard", icons: [ … ] },
  ],
}
  • Die Shortcuts sind Routen der Service-Worker-Shell (T3 Task 15). Sie öffnen also auch offline.
  • Ein langer Druck auf das App-Icon führt direkt zur Durchsicht.
  • proxy.ts: Der matcher nimmt /icons/ und apple-icon.png aus, wie heute icon.svg.
  • public/sw.js:
    • SHELL_ROUTES bleiben.
    • Die neuen Icons kommen in den Shell-Cache.
    • Die Cache-Version steigt (apicore-shell-v5), damit installierte Apps das neue Icon und die neuen Tokens bekommen.

13. Tests

TDD wie in T3/T4: erst der fehlschlagende Test, dann der Code. Vitest + RTL im Frontend, Jest/Supertest für den Backend-Zusatz.

13.1 Wächter

  • src/styles/contrast.spec.ts (ersetzt src/lib/forms/rating-contrast.spec.ts; gleiche OKLCH-Rechnung, ergänzt um Alpha-Mischung für Tönungen):
    • Liest die vier Sätze aus globals.css. Die Sonne-Sätze werden auf ihren Grundsatz gelegt.
    • Prüft jede Zeile aus 4.4 mit ihrer Grenze. Dazu kommen alle Ampelflächen (Text auf Fläche ≥ 4,5, Fläche auf Karte ≥ 3, Tinte auf Tönung ≥ 4,5), alle chart-* und area-* (≥ 3) und alle queen-* mit Tinten-Ring.
    • it('every token in @theme inline exists in light and dark'): der Wächter für „Dark angeschlossen“.
    • it('all tokens are inside the sRGB gamut').
  • src/styles/baukasten-guard.spec.ts: Regeln aus 10.7.
  • src/lib/season/season-defaults.spec.ts: it('defaults equal the T4 PHENOLOGY and SEASON_AUTOPILOT defaults'). Der Test liest die Backend-Datei mit den Config-Standards (ApiCore-Backend/src/automation/rule-config/*, genauer Pfad aus T4 Task 1) per readFileSync und vergleicht die MM-DD-Werte. Ändert jemand nur eine Seite, schlägt der Test fehl.
  • src/app/manifest.spec.ts:
    • Icons für 192/512 „any“ und „maskable“ sind vorhanden.
    • Jede Datei existiert unter public/ mit der angegebenen Größe (PNG-Kopf lesen).
    • background_color und theme_color entsprechen dem hellen --background.
    • Die Shortcut-URLs stehen in SHELL_ROUTES.

13.2 Unit

  • appearance.spec.ts:
    • Standard „Hell“
    • „Wie Gerät“ folgt matchMedia und reagiert auf Wechsel
    • prefers-contrast: more ohne gespeicherten Wert → Sonne
    • Blockierter Speicher fällt auf den Standard zurück
    • storage-Ereignis zieht mit
    • Das Inline-Skript nutzt dieselben Schlüssel (String enthält APPEARANCE_KEYS) und setzt theme-color
  • areas.spec.ts:
    • Jeder NAV_GROUPS-Eintrag und jede QUICK_ACTION hat einen AREAS-Schlüssel und dasselbe Icon
    • Kein Ton ist eine Zustandsfarbe
  • honeycomb/layout.spec.ts:
    • Spaltenzahl aus der Breite (3 bis 10)
    • Versatz jeder zweiten Reihe
    • Verzahnung um ein Viertel der Höhe
    • Reihenfolge nach Code natürlich sortiert (V-2 vor V-10)
  • honeycomb/cell-state.spec.ts (Tabelle):
    • Randstufen vor T4 (10/11/14/15 Tage, nie)
    • Randstufen ab T4 (fällig in 4 Tagen → 1, in 3 → 2, gestern → 3, null → 1 mit „Winterruhe“)
    • ohne inspections:read → neutral
    • Name der Zelle beginnt mit dem Code und nennt Ampel, Gründe und Tage
  • mini-comb-svg.spec.ts:
    • schlechtestes Volk in der Mitte
    • freie Plätze als Umriss
    • mehr als 7 → Zählchip
    • nur var(--…)-Farben, kein Hex
  • lifeline.spec.ts:
    • Werte vor STRENGTH_IN_FRAMES_SINCE nicht in der Kurve, aber in der Tabelle
    • Lücke über 45 Tage bricht die Linie
    • Varroa-Balken über die echte Liegezeit
    • rating.light (T4) hat Vorrang vor varroaLight (T3)
    • Königinnen-Amtszeiten aus queenHistory, Farbe aus markingColor
    • unbekannte Farbe → gestrichelt
    • Behandlungsspannen
    • Zukunft: T4-Schritte, sonst Aufgaben, höchstens 3
    • fehlende Spur bei 403
    • <desc>-Text
  • year-wheel.spec.ts:
    • Winkel für 01.01., 01.07. und 31.12. im Schaltjahr
    • Winterruhe als zwei Bögen
    • aktueller Abschnitt ab segmentSince (Kirsche am 05.04. gemeldet → Schwarmzeit ab 05.04.)
    • späte Blüte → nächster Abschnitt „ab morgen, noch offen“
    • Fenster lückenlos und überlappungsfrei
    • Bezugsstand nach Mehrheit, bei Gleichstand fortgeschrittener
    • Abweichungen
    • Aufgaben je Woche, überfällige gesammelt
    • ohne seasonCalendar → season-defaults

13.3 Komponenten (RTL)

  • ChoiceGroup:
    • gewählte Option hat aria-checked="true", das Haken-Icon und den Tinten-Rahmen
    • „Nicht geprüft“ gewählt hat das Strich-Icon
    • appearance="cards" zeigt die Beschreibung
    • Pfeiltasten wie bisher
  • CheckboxField:
    • ganze Zeile schaltet
    • Name aus dem Label
    • Zeile mindestens 44 px (Klasse min-h-11), im Sheet min-h-14
    • tile und task haben role="checkbox" vom Primitiv
  • Tabs: Pfeiltasten wechseln, Panel ist tabpanel mit aria-labelledby, ActiveHistoryTabs zeigt Zähler.
  • AppearanceSection: Wahl setzt Klasse und Attribute auf document.documentElement und speichert. Schnellschalter im Benutzermenü ist ein menuitemcheckbox.
  • ApiaryComb:
    • Liste mit benannten Links in Codereihenfolge
    • Legende sichtbar
    • „Braucht Blick“ nur mit Gelb und Rot
    • „Alle Völker als Liste“ klappt die T3-Liste auf (aria-expanded)
    • leerer Stand
    • offline aus dem Paket
  • ApiaryMap: Marker mit aria-label, Popup mit „Stand öffnen“, Liste unter der Karte, Offline-Hinweis. Leaflet wird gemockt.
  • ColonyLifeline:
    • figure mit Titel und Beschreibung
    • Tabelle aufklappbar mit denselben Einträgen
    • „Ganzes Leben zeigen“ ändert den Ausschnitt
    • Leerzustand mit Knopf
    • fehlende Varroa-Rechte → keine Milbenspalte
  • YearWheel:
    • Zentrum zeigt Abschnitt, Quelle und Fenster
    • Tabelle aufklappbar
    • „Zum Kalender“
    • Dashboard ohne Zählkacheln und ohne Weiß auf Honig
  • Backend test/next-steps.e2e-spec.ts: it('GET /autopilot/today carries seasonCalendar and segmentSince per apiary'), it('seasonCalendar follows a changed PHENOLOGY config').

13.4 Sichtprüfung

  • Screenshot-Matrix:

    • Werkzeug: Chrome DevTools (MCP chrome-devtools: emulate, resize_page, take_screenshot) gegen npm run build && npm run start mit Demo-Daten.
    • Seiten:
      • Start, Stand (Wabe), Bienenstände, Karte, Volk (Lebenslinie)
      • Durchsicht neu, Varroa neu, Profil > Darstellung
      • Login, Herkunft
    • Modi: hell, dunkel, Sonne hell, Sonne dunkel.
    • Breiten: 390 px (Touch), 820 px (Tablet, Touch-Emulation, prüft pointer-coarse), 1366 px.
    • Dazu forced-colors: active und prefers-reduced-motion: reduce je einmal auf Start, Stand und Volk.
    • Die Bilder liegen als Anhang am PR, nicht im Repo.
  • Automatisch:

    • Lighthouse Barrierefreiheit mindestens 95 auf Start, Stand, Volk, Durchsicht neu und Profil, in hell und Sonne.
    • Danach einmal den Impeccable-Detektor über die geänderten Dateien laufen lassen (impeccable detect --json); 0 Befunde.
  • Draußen-Test mit dem Inhaber: Handy, Mittagssonne, Arbeitshandschuhe, eine Hand, je einmal Normal und Sonne. Bestanden, wenn jede Aufgabe ohne Hilfe und ohne Fehlgriff gelingt:

    1. den Hauptknopf auf Start finden und tippen
    2. auf 50 cm Abstand sagen, welches Volk an einem Stand Kritisch ist und welches lange nicht durchgesehen wurde
    3. Brutwaben mit dem Stepper von 0 auf 6 bringen
    4. den Sonnenmodus einschalten
    5. im Rad sagen, welcher Abschnitt gerade ist

    Ergebnis und Zeit je Aufgabe stehen im PR. Ein Fehlschlag führt zu Token- oder Größenänderung, nicht zu neuer Bedienung.

14. Aufwand

Baustein PT
Tokens: vier Sätze, -ink-Tokens, Bereichs- und Königinnenfarben, Codemod Farben, dark: aus ui/*, Kontrast-Wächter 1,0
Darstellung: Appearance-Modul, Inline-Skript, Profil-Karte, Schnellschalter, next-themes raus, Toaster, theme-color 0,5
Fokus und Größen: globaler Fokus, Codemod, pointer-fine statt sm, Schrift 16 px 0,5
Baukasten: Auswahlgruppe (0,25), Checkbox mit 17 Stellen (0,5), Tabs mit 12 Stellen (0,5), Plus/Minus (0,25), AREAS/AreaIcon/BrandMark (0,5), Wächter (0,25) 2,25
Manifest, Icons, Skript, Service-Worker-Version 0,5
T5a „Sonnentauglich und Baukasten“ 4,75
Waben-Standkarte: Layout, Zelle, Stand-Seite, Bienenstände-Liste 1,25
Mini-Waben-Marker, Popup-Stil, Listen-Rückfall 0,5
Lebenslinie: Modell, SVG, Tabelle 1,5
Bienenjahr-Rad: Backend-Zusatz (0,25), Modell, SVG, Tabelle, Dashboard-Umbau 1,5
Sichtprüfung (Matrix, Draußen-Test), Doku 0,5
T5b „Handschrift“ 5,25
Summe 10,0

Der Fahrplan nannte etwa 5 PT. Mehr wird es durch vier Punkte, die §10 nicht einpreist:

  1. Der Dunkelmodus ist heute nicht nur unerreichbar, sondern unlesbar (Abzeichen 1,4:1). Er braucht einen vollständigen zweiten Satz, und mit Sonne werden es vier geprüfte Sätze.
  2. Checkboxen und Tabs liegen an 29 Stellen, nicht an einer Handvoll.
  3. Der Fokus ist an 44 Stellen von Hand gebaut.
  4. Tablets bekommen heute Desktop-Größen.

Vorschlag: zwei Lieferungen.

  • T5a (≈ 4,75 PT) bringt den größten Nutzen draußen und hängt nur an T3. Sie kann vor oder parallel zum T4-Backend laufen.
  • T5b (≈ 5,25 PT) braucht T4a für die vollen Daten, läuft aber auch ohne (Kalender-Rückfall). Falls gekürzt werden muss, lassen sich die Mini-Waben-Marker (0,5 PT) auf später verschieben, ohne dass etwas anderes fehlt.

15. Rollout

  1. Reihenfolge:
    • T3-Frontend Tasks 7, 8, 11 und 14 sind gemergt.
    • Dann T5a Schritte 1–5 (10.8) in einem Fenster ohne offene Frontend-Tasks. Am besten vor den T4-Frontend-Tasks 17–22, damit T4 schon auf dem neuen Baukasten baut (CheckboxField, AreaIcon, neue Tokens).
    • T5a Schritte 6–12 laufen parallel zum T4-Backend.
    • T5b nach T4a-Frontend (Tasks 17, 18, 19), weil T5b dieselben Dateien berührt (dashboard/page.tsx, apiary-colonies-card.tsx, Volksseite).
  2. Keine Migration, keine Cluster-Änderung. T5a ist ein reines Frontend-Image. T5b bringt zusätzlich den Backend-Zusatz an /autopilot/today (additiv, alte Frontends ignorieren die Felder). Deploy wie gewohnt, jeder Cluster-Schritt nur mit Freigabe des Inhabers (Rückgrat-Runbook).
  3. Service Worker:
    • Die Shell-Version steigt (apicore-shell-v5). Installierte Apps holen beim nächsten Start Tokens und Icons.
    • Das Icon auf dem Homescreen aktualisiert Android von selbst (kann Stunden dauern). iOS erst nach Entfernen und neu Hinzufügen; das steht im Changelog.
  4. Nutzerhinweis: Einmalig erscheint auf Start der Hinweis „Neu: Sonnenmodus für draußen – im Benutzermenü.“ mit „Ausprobieren“ und „Schließen“ (gemerkt je Gerät). Danach nicht mehr.
  5. Erste Woche:
    • Draußen-Test (13.4) mit dem Inhaber.
    • Tokenwerte dürfen danach nur zusammen mit contrast.spec.ts geändert werden; der Test hält die Grenzen.
  6. Doku:
    • ARCHITECTURE bekommt neue Annahmen unter der dann nächsten freien Nummer (T3 und T4 vergeben 22–27 und folgende):
      • „Darstellung je Gerät“: hell, dunkel, Sonne; Inline-Skript; keine Kontodaten
      • „Ein Baukasten“: Bausteine aus 10.1, Wächter, Ausnahmen
    • ARCHITECTURE §2: AREAS als einzige Quelle für Icon und Ton, Wabenzelle als einziges Motiv.
    • TASKS.md: Abschnitt T5 mit Haken; Abschnitt R „Redesign“ verweist auf T5.
    • Empfohlen: ApiCore-Frontend/DESIGN.md per /impeccable document aus dem fertigen Stand erzeugen, damit spätere Seiten den Baukasten finden.

16. Offene Fragen an den Inhaber

  1. Farbe des Hauptknopfs. Heute steht weiße Schrift auf einem Honigverlauf (2,3:1, draußen kaum lesbar).

    • A: Heller Honig (#f0b13f) mit dunkler Tinten-Schrift, 9,4:1. Wirkt wie ein Honigetikett. Links und Icons bekommen ein eigenes dunkles Bernstein.
    • B: Dunkler Bernstein (#9b4900) mit weißer Schrift, 6,1:1. Ein Token für Fläche und Text, dafür eher rostbraun als honigfarben.
    • C: Wie heute (verletzt AA, nicht empfohlen).
    • Empfehlung: A. Höchster Kontrast, bleibt in der Sonne lesbar, und die Honigfarbe wird zum Erkennungszeichen des einen Hauptknopfs.
  2. Reihenfolge der Zellen in der Waben-Standkarte.

    • A: Nach Volkscode, wie Liste und Standrunde.
    • B: Wie die Beuten wirklich am Stand stehen. Dafür gibt es ein neues Feld Colony.standSlot mit Migration, Backup-Eintrag und einer Ansicht „Anordnen“ (+1 PT).
    • C: Schlechtestes Volk zuerst.
    • Empfehlung: A jetzt. Wer seine Völker nach Platz nummeriert, hat damit schon die echte Reihenfolge. B lohnt erst, wenn die Codes nicht zur Aufstellung passen. C würde die Zellen bei jeder Durchsicht umsortieren und das räumliche Gedächtnis stören.
  3. Standard-Farbschema auf einem neuen Gerät.

    • A: Hell, Dunkel nur auf Wunsch.
    • B: Wie das Gerät. Ein Handy im Dunkelmodus zeigt die App dann auch draußen dunkel.
    • Empfehlung: A. Draußen bei Tag ist hell besser lesbar. Wer abends dunkel will, stellt einmal „Dunkel“ oder „Wie Gerät“ ein, und das Gerät merkt es sich.
  4. Bewegungs-Sprache (neu 2026-10-16, die Rückfrage aus Beute-Spec §12.4). Wie viel Bewegung bekommt die App außerhalb der Beute?

    • A: Wie §17. Die App bleibt ruhig, die Beute ist die eine Bühne. Bewegung zeigt nur, woher etwas kommt, was weg ist und was geklappt hat. Das Abhaken bleibt der eine Moment.
    • B: Mehr Bewegung in der ganzen App: Karten heben sich, Zahlen zählen hoch, Seitenwechsel mit gleitenden Übergängen (View Transitions). Draußen kostet das Lesezeit und Akku.
    • C: Keine Bewegung außerhalb der Beute, auch kein Gleiten der Sheets.
    • Empfehlung: A. Hält die Beute als Höhepunkt und die Arbeit am Stand schnell. Die Stufe „Voll“ bleibt Standard; wer am Stand Ruhe will, stellt „Dezent“ ein.

17. Bewegungs-Sprache

Nachtrag 2026-10-16. Anlass: Die Beute (Spec 2026-10-15-beute-3d-design.md §9.3, §9.8, §12.4) ist auf Wunsch des Inhabers stark animiert: Glas-Holo, Flug in den Fokus, Heben der Rähmchen, Federn. Die übrige App soll sich wie dieselbe App anfühlen. Dieser Abschnitt ersetzt die Regel „Keine neue Animation“ aus §1. Bestätigung durch den Inhaber: Frage 4 in §16.

17.1 Zweck

Draußen am Stand hat der Imker Sonne auf dem Display, Handschuhe an und eine Hand frei. Bewegung hat dort genau drei Aufgaben:

  1. Herkunft zeigen: Ein Sheet kommt von seiner Kante, ein geöffnetes Teil fliegt aus seinem Stapel. Man sieht, wo man ist.
  2. Veränderung zeigen: Eine Zeile geht weg, die anderen rücken nach. Man sieht, was sich geändert hat.
  3. Erfolg bestätigen: Der Haken zeichnet sich, der Knopf gibt beim Drücken nach. Man weiß, dass der Tipp angekommen ist, auch mit Handschuh.

Bewegung schmückt nie und hält nie auf. Kein Ziel wartet auf eine Animation, und keine Zahl bewegt sich, während jemand sie liest.

17.2 Stufen

Es gilt die bestehende Bewegungsstufe je Gerät (apicore.motion, §5.1). Die Wörter in der Oberfläche heißen überall „Voll“, „Dezent“ und „Aus“ (die Beute-Spec nennt „Dezent“ an einer Stelle „ruhig“).

Stufe Wann Ortswechsel (Gleiten, Fliegen, Heben, Nachrücken) Zustandswechsel (Deckkraft, Farbe) Ruhebewegung (Beute schwebt, Pegel wogt, Puls)
full „Voll“ Standard, solange das System nicht reduziert an, mit den Tokens aus 17.3 an an, nur sichtbar und im Vordergrund-Tab
subtle „Dezent“ gewählt, oder „Voll“ plus prefers-reduced-motion: reduce aus, Dinge stehen sofort am Ziel Überblendung --motion-fade (150 ms) aus
off „Aus“ gewählt aus sofort (globale Regel in globals.css) aus
  • Eine Quelle für JavaScript: useEffectiveMotionLevel() aus src/lib/motion/motion-preference.ts. Den Hook nutzt die Beute schon (hive-canvas.tsx, hive-visual-slot.tsx). Er verbindet die gewählte Stufe mit prefers-reduced-motion und reagiert sofort auf beides. Ab §5 setzt das Inline-Skript data-motion vor dem ersten Bild. useAppearance().setMotion löst dabei weiter MOTION_EVENT aus, damit Szene und DOM sofort mitziehen.
  • Eine Quelle für CSS: die Variante motion-full: (gibt es schon in globals.css). Jede Verschiebung, Skalierung oder Drehung steht hinter motion-full:. motion-safe: fragt nur das System und übergeht die Stufe in der App; für Ortswechsel ist es darum verboten. Farb- und Deckkraft-Übergänge brauchen keine Variante, weil „Aus“ ihre Dauer global auf null setzt.
  • motion (motion/react): Ein AppMotion-Rahmen im (app)-Layout setzt MotionConfig je Stufe. Bei full steht reducedMotion auf "never", weil der Hook das System schon einrechnet. Bei subtle steht es auf "always": Dann laufen nur Deckkraft und Farbe. Bei off gilt transition={{ duration: 0 }}.
  • Sonne ändert keine Bewegung. Sonne betrifft nur Kontrast und Größen. Das Umschalten von Farbschema, Kontrast und Stufe wirkt sofort und ohne Übergang (§5.3).

17.3 Tokens

Die Werte kommen aus dem, was die Beute schon ausliefert, und aus den bestehenden CSS-Bewegungen. Darum fühlen sich Bühne und App gleich an. CSS-Variablen stehen in globals.css (:root). Den Spiegel in TypeScript liefert src/lib/motion/tokens.ts. Die Beute liest ihre Feder und ihre Überblendung künftig aus dieser Datei (quality.ts, use-hive-motion.ts), damit es nur eine Quelle gibt.

Token Wert Herkunft Verwendung
--motion-fade 150 ms SUBTLE_FADE_SECONDS (Beute), Overlay der Sheets jede Überblendung, jeder Austritt, alle Wechsel bei „Dezent“
--motion-enter 200 ms ui/sheet.tsx (200 ms) Eintritt von Sheet, Dialog, Popover, Menü, Seite und neuer Listenzeile
--motion-settle 350 ms DAMPING = 12 (Beute: nach 350 ms in Ruhe) Ortswechsel ohne Feder: Zurückfedern, Aufklappen eines Abschnitts
--motion-moment 420 ms .honey-check (Wabe füllt sich) der eine Moment: Abhaken
--ease-out-expo cubic-bezier(0.16, 1, 0.3, 1) Bestand in globals.css alle Eintritte, der Moment
--ease-in neu cubic-bezier(0.4, 0, 1, 1) – alle Austritte (schneller weg als da)
--ease-spring neu linear(…), aus der Feder unten erzeugt SPRING_STIFFNESS 210, SPRING_DAMPING 21 (quality.ts, Dämpfungsgrad ≈ 0,72, in Ruhe nach ≈ 0,5 s) CSS-Ortswechsel mit leichtem Überschwingen, Dauer 500 ms
SPRING_FLIGHT (TS) { type: "spring", stiffness: 210, damping: 21, mass: 1 } dieselbe Feder motion: Nachrücken (layout), Loslassen nach dem Ziehen
--motion-shift neu 8 px Beute hebt ein Teil 8 mm, next-step-in 8 px Weg beim Eintritt von Inhalt (Seite 6 px bleibt)
Sheet-Weg 2,5 rem Bestand ui/sheet.tsx Sheet gleitet von seiner Kante

Grenzen:

  • Kein DOM-Übergang dauert länger als --motion-moment (420 ms).
  • Ausnahmen gibt es nur zwei:
    • die Beute-Bühne (Federflug ≈ 0,5 s, Ruhebewegung)
    • das Wegfalten der Heute-Zeile nach dem Abhaken (EXIT_DELAY_MS.full = 720). Gespeichert ist dort sofort, „Rückgängig“ steht im Toast.
  • Austritte sind nie länger als --motion-fade.

17.4 Regeln je Muster

Muster Voll Dezent Werkzeug
Seite (Routenwechsel) Eintritt: Deckkraft 0 → 1 und 6 px nach oben, --motion-enter, --ease-out-expo. Kein Austritt: Die alte Seite verschwindet sofort, die Navigation wartet nicht. nur Deckkraft, --motion-fade CSS animate-page-in (page-transition.tsx, Dauer heute 260 ms → Token)
Sheet, Dialog, Popover, Menü (öffnen, schließen) Sheet gleitet 2,5 rem von seiner Kante, Dialog und Popover blenden mit scale(0.97 → 1) ein; Eintritt --motion-enter mit --ease-out-expo, Austritt --motion-fade mit --ease-in auf demselben Weg zurück; Hintergrund nur Deckkraft. Der Fokus springt beim Öffnen sofort hinein, nicht erst nach der Animation. Beim Auf- oder Zugehen der Bildschirmtastatur bewegt sich das Sheet nicht. nur Deckkraft CSS über data-starting-style/data-ending-style von Base UI (Muster steht in ui/sheet.tsx). Dialog heute 100 ms, Sheet heute ease-in-out → Tokens.
Listenzeile hinzu (durch den Nutzer: Eintrag gespeichert, Zeile ergänzt) nur die neue Zeile: Deckkraft und --motion-shift, --motion-enter. Kein Stagger, kein Einblenden beim Laden einer Liste. Daten stehen sofort da. Deckkraft, --motion-fade CSS (animate-in aus tw-animate-css oder @starting-style)
Listenzeile weg Zeile blendet aus (--motion-fade), die übrigen rücken mit SPRING_FLIGHT nach. Das läuft über transform (layout), nicht über die Höhe. Zeile blendet aus, der Rest steht sofort am Ziel motion: AnimatePresence und m.li layout. Gilt für Heute-Liste, Aufgaben, Warteschlangen-Sheet und Sprachnotizen.
Umsortieren layout mit SPRING_FLIGHT sofort motion
Abschnitt auf/zu (Aufklapp-Karten, „Als Tabelle“) Pfeil dreht sich (--motion-settle). Der Inhalt erscheint sofort, ohne Höhen-Animation. Pfeil springt CSS motion-full:transition-transform (Bestand, bisher motion-safe:)
Erfolg, Bestätigung Abhaken ist der eine Moment: Wabe füllt sich (--motion-moment), Haken zeichnet sich, Zeile faltet weg. Speichern endet mit Toast und Navigation; kein Konfetti, kein Knopf, der seine Form ändert. Knopf-Druck: Fläche wechselt sofort, bei Voll zusätzlich scale(0.97) beim Drücken, 100 ms. Wabe und Haken blenden in 120 ms (Bestand), Knopf ohne Skalierung CSS (.honey-check, ui/button.tsx motion-full:active:scale-[0.97])
Fokuswechsel Der Fokusring erscheint sofort und wird nie animiert. Programmatischer Fokus (erstes Fehlerfeld, geöffnetes Sheet, Live-Region) springt. Scrollen dorthin ist smooth nur bei Voll und bei weniger als einer Bildschirmhöhe, sonst sofort. Auf der Beute-Bühne fliegt die Kamera mit (Beute §9.4). Scrollen sofort scrollBehaviorFor(level, distance) in src/lib/motion/tokens.ts
Laden, Skelett Das Skelett erscheint erst nach 150 ms, damit kurze Ladezeiten nicht blitzen. Pulsieren nur bei Voll (Deckkraft, 2 s). Spinner nur in Knöpfen ab 300 ms Wartezeit und immer mit Wort („Speichert …“). Skelett ruhig; der Spinner wird ein ruhendes Icon mit Wort CSS motion-full:animate-pulse, motion-full:animate-spin (bisher motion-safe:)
Läuft gerade (Aufnahme, Synchronisieren) Punkt pulsiert (Deckkraft) ruhiger Punkt; das Wort („Aufnahme läuft“) trägt den Zustand CSS
Hover (nur pointer-fine) nur Farbe und Rahmen, keine Verschiebung im DOM. Das Heben der Karten (ui/card.tsx) und das Vergrößern der Icons in der Seitenleiste entfallen. Auf der Beute-Bühne hebt sich das Teil (Beute §9.4), weil es dort zeigt, was der nächste Klick öffnet. Farbe CSS
Wabenmotiv Die Wabenzelle bewegt sich nur an zwei Stellen: beim Abhaken (.honey-check) und auf der Beute-Bühne. Waben-Standkarte, Mini-Waben, Rad, Lebenslinie, BrandMark und AreaIcon stehen still (6.8, 8.5, 9.5 bleiben). Ein Ladezeichen in Wabenform gibt es nicht. – –
Toast Sonner-Standard (gleitet), Dauer und Kurve auf die Tokens ohne Gleiten, nur Deckkraft CSS-Überschreibung an [data-sonner-toaster], falls Sonner das nicht selbst tut

17.5 Was nie animiert

  • Datenwerte, die der Imker liest:
    • Zahlen, Ampeln, Zählungen, Balken, Kurven, Zellfüllungen, der Rad-Zeiger und die Zahl im Pegel-Etikett.
    • Nichts zählt hoch, kein Balken wächst, keine Linie zeichnet sich.
    • Ein neuer Wert nach dem Sync steht sofort da.
    • Bestand, der das verletzt: stat-bar-list.tsx (transition-[width] duration-700) verliert den Übergang.
    • Auf der Beute-Bühne wogt die Oberfläche des Pegels; seine Zahl springt.
  • Fehler und Warnungen:
    • Feldfehler, role="alert", Warn- und Sperr-Hinweise (Sperrbezirk, Wartezeit, 409) erscheinen sofort.
    • Kein Schütteln, kein Einblenden, kein verzögertes Erscheinen.
  • Während der Eingabe am Stand:
    • Das Feld selbst, die Zahl im Stepper (keine rollenden Ziffern), die Umgebung des fokussierten Felds.
    • Nichts verschiebt sich, kein Toast legt sich über Feld oder Speichern-Leiste, nichts leitet von selbst weiter.
    • Eine Diktat- oder Sprachnotiz-Aufnahme bewegt nur ihren Status-Punkt.
  • Fokusring, Sonne an/aus, Farbschema, Druck und PDF.
  • Warten:
    • Kein Ziel ist während eines Eintritts gesperrt (pointer-events bleiben an).
    • Kein Klick wartet auf einen Austritt.
    • Einzige gewollte Verzögerung ist das Wegfalten der Heute-Zeile (17.3).

17.6 Leistung

  • Nur transform und opacity bewegen sich.
    • Für Zustandswechsel kommen Farbe (color, background-color, border-color, fill, stroke) und clip-path dazu (.honey-check).
    • Nie animiert werden width, height, top, left, margin, padding, grid-template-rows, box-shadow, filter und backdrop-filter.
    • Bestand, der das verletzt:
      • .today-row faltet über grid-template-rows → motion layout (17.4)
      • next-step-in animiert filter: blur(6px) → nur Deckkraft und --motion-shift
      • stat-bar-list animiert die Breite → ohne Übergang
  • Kein transition-all: Die Übergänge nennen ihre Eigenschaften (transition-colors, transition-[opacity,transform]). Heute gibt es 8 Stellen, 7 davon in ui/*.
  • Kein Layout-Thrash:
    • Keine Messung (getBoundingClientRect, offsetHeight) in einem Animationsschritt.
    • motion misst bei layout einmal zu Beginn und bewegt dann per transform.
    • Kein React-State pro Bild. DOM-Bewegung läuft über CSS oder über das WAAPI von motion.
  • will-change setzt nur motion selbst, nie dauerhaft in CSS. backdrop-blur nur auf ruhenden Flächen, in Sonne gar nicht (§4.3).
  • Ziel: 60 fps auf einem Mittelklasse-Handy. Gemessen wird mit einem Performance-Trace bei 4-facher CPU-Drosselung und 390 px. Bei Sheet auf/zu, Abhaken mit Wegfalten, Zeile entfernen und Seitenwechsel ist kein Frame länger als 32 ms, und während der Bewegung gibt es keinen Layout-Eintrag außer der einen layout-Messung.
  • Bundle:
    • motion nur über LazyMotion mit strict. Elemente kommen als m.* aus motion/react-m, die Features (domMax wegen layout) asynchron.
    • Geladen wird nur im (app)-Layout, nicht auf Login und Herkunft.
    • Budget: höchstens 30 kB gzip, im Build gemessen wie das 3D-Budget.
    • Die three.js-Szene nutzt motion nicht (eigene Federn in use-animated-node.ts).

17.7 Abnahme

  1. Wächter (src/styles/baukasten-guard.spec.ts, Regelgruppe „Bewegung“, Ausnahmen mit Grund):
    • kein motion-safe: vor translate, scale, rotate, slide-, zoom-, animate- (→ motion-full:)
    • kein transition-all
    • kein transition-[width|height] und kein duration- über 420 ms (Ausnahme components/hive/**)
    • hover: mit translate oder scale nur in components/hive/**
    • motion/react liefert nur LazyMotion, MotionConfig, AnimatePresence und Hooks; Elemente nur als m.*
    • Keyframes und Übergänge in globals.css animieren keine Eigenschaft aus der Verbotsliste in 17.6 (der Test liest die Datei)
  2. Tokens (src/lib/motion/tokens.spec.ts):
    • Die CSS-Variablen entsprechen den TS-Konstanten.
    • SPRING_FLIGHT hat dieselben Werte wie die Feder der Beute (eine Quelle).
    • Die Stützpunkte von --ease-spring weichen höchstens 0,02 von springStep ab.
  3. Komponenten:
    • AppMotion setzt reducedMotion je Stufe.
    • Bei „Aus“ ist eine entfernte Zeile sofort weg, bei „Dezent“ ohne Verschiebung.
    • scrollBehaviorFor liefert smooth nur bei Voll und kurzer Strecke.
  4. Browser (Chrome DevTools):
    • Jedes Muster aus 17.4 einmal in Voll, Dezent und Aus, dazu prefers-reduced-motion: reduce per emulate.
    • Bei Dezent und bei reduce verschiebt sich nichts, bei Aus geht alles sofort.
    • Dazu der Performance-Trace aus 17.6.
  5. Draußen-Test (13.4) bekommt Aufgabe 6: eine Aufgabe mit Handschuh abhaken. Bestanden, wenn der Haken gesehen wird und niemand auf die Liste wartet.
  6. Review: einmal review-animations bzw. fixing-motion-performance über den Diff (Skill-Routing: erst Konzept, dann CSS, dann motion, GSAP nicht).

17.8 Beute-Bühne im T5-Rahmen

  • Die Bühne ist eine eigene, immer dunkle Fläche mit eigenen Tokens (--holo-*).
    • Nur hier gibt es Glas, Leuchten, Milchglas-Elemente und Ruhebewegung.
    • §3.6 und die Verbotsliste gelten außerhalb der Bühne. Wächter-Ausnahmen gelten nur für components/hive/** und app/(app)/colonies/[id]/hive/**.
  • Bewegung auf der Bühne folgt Beute §9.8. Die Werte stammen aus denselben Tokens (17.3).
  • Fokus auf der Bühne: Der Ring aus §4.6 in --primary-ink hat auf --holo-bg weniger als 3:1. Deshalb setzt ein Rahmen .holo-stage --ring: var(--holo-edge-strong). contrast.spec.ts prüft das Paar (≥ 3) und die Etiketten --holo-label-fg auf --holo-label-bg (≥ 7).
  • Sonne:
    • --holo-* bekommt Sonne-Sätze (Bühne schwarz, Kanten reinweiß).
    • Die Szene liest die Tokens beim Wechsel der Darstellung neu ein; heute geschieht das einmalig in hive-scene/theme.ts.
    • Ohne Bloom, Kanten 2 px, Rähmchen-Arten als Schraffur, Etiketten 16 px (Beute §9.3).