# T5 Optik mit eigener Handschrift — Design Stand: 2026-10-14 · 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 `` setzt vor dem ersten Bild `class="dark"`, `data-contrast` und `data-motion` auf `` (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 | Keine neue Animation. Rad, Lebenslinie und Waben erscheinen sofort. Bewegung gibt es nur, wo sie heute einen Zustand zeigt (Abhaken, nächster Schritt). | | 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 | | §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. **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 ## 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 `` 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`: ```css :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 `` | 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 `` 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 `` auf die Seitenfarbe des aktiven Satzes. - `` 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: `