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]>
1325 lines
103 KiB
Markdown
1325 lines
103 KiB
Markdown
# 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`:
|
||
|
||
```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 `<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):
|
||
|
||
```ts
|
||
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):
|
||
|
||
```ts
|
||
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`:
|
||
|
||
```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`
|
||
|
||
```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).
|