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]>
103 KiB
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-themesfliegt 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,RatingScalemit 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 allem01-app/02-guides/preventing-flash-before-hydration.md,progressive-web-apps.mdund01-app/03-api-reference/03-file-conventions/01-metadata/manifest.mdsowieapp-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
- 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.
- 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.
- 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?
- 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.
- 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.
- 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-primaryals Schriftfarbering-ring/50undoutline-noneohne 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::root(hell).dark(dunkel):root[data-contrast="sun"](Sonne hell).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 Variantensun,pointer-fineundpointer-coarse. -
ui/*undcomponents/*enthalten keinedark:-Klassen mehr. Ein Modus ist nur ein anderer Tokensatz. Einzige Ausnahme: Leaflet-Kacheln (7.4). -
Entfernt:
--gradient-honey,--gradient-sage,--gradient-sky. Die KomponentePageHeader/EntityCardverliert den Propgradient. -
@media printsetzt 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.
- Zellen, Ränder, Ringe und Rad-Segmente bekommen
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-inktext-warning→text-warning-inktext-condition-x-foregroundin Abzeichen →text-condition-x-ink(COLONY_CONDITION_TONE_CLASS,StatusLightBadge)border-primarybei 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önungenbg-*/15werden zubg-cardmit 2-px-Rahmen in der jeweiligen Tinte (Hilfsklasse.tintstatt/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-smauftext-baseum:Field-LabelChoiceGroup-OptionCheckboxField-Label- Button-Größe
touch(heutetext-[0.95rem]) Input/Textarea(md:text-smentfällt; 16 px verhindert auch das Zoomen in iOS)- Zeilentitel in Listen,
DetailHeader-Untertitel EmptyState-Beschreibung
- Größen:
size="default"bei Button undInputhat 44 px Höhe. 32 bzw. 36 px gibt es nur mitpointer-fine:stattsm:.size="touch"bleibt 44 px. Inui/button.tsx,ui/input.tsx,ui/select.tsx,ui/textarea.tsxundui/tabs.tsxwirdsm:h-*zupointer-fine:h-*. - Ziele: Icon-Knöpfe ohne Text (
icon,icon-sm) bekommen beipointer-coarseeine Trefferfläche von 44 px über ein::after-Element, ohne optisch zu wachsen.
4.6 Fokusstil
In @layer base:
:root { --focus-width: 2px; --focus-offset: 2px; }
:where(:focus-visible) { outline: var(--focus-width) solid var(--ring); outline-offset: var(--focus-offset); }
- Alle
outline-none,focus-visible:ring-3 focus-visible:ring-ring/50undhas-[:focus-visible]:outline-*inui/*,components/*undapp/*entfallen (18 Stellenring-ring/50in 17 Dateien, 22outline-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-paddingbleibt.
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
- Vor dem ersten Bild:
src/components/appearance-script.tsxrendert im<head>vonapp/layout.tsxein Inline-Skript nach dem MusterInlineScriptder Next-Anleitung „Preventing Flash“.- Es liest die drei Schlüssel in
try/catchund setzt Klasse und Attribute. - Es setzt den Inhalt von
<meta name="theme-color">auf die Seitenfarbe des aktiven Satzes. <html>bekommtsuppressHydrationWarning.- 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.
- Es liest die drei Schlüssel in
- Zur Laufzeit:
useAppearance()(src/lib/appearance/appearance.ts) liefert die Werte und Setter.- Ein Setter schreibt
localStorageund das Attribut. - Er hört auf
matchMedia('(prefers-color-scheme: dark)'), solange „Wie Gerät“ gewählt ist, und aufprefers-contrast, solange nichts gewählt ist. - Er hört auf das
storage-Ereignis, damit ein zweiter Tab mitzieht. motion-preference.tsbehält seine API;applyMotionLevelwird vom Skript übernommen, deruseEffectinproviders.tsxentfällt.
- Ein Setter schreibt
- Toaster:
components/ui/sonner.tsxliestuseAppearance().resolvedThemestattnext-themes.next-themeswird auspackage.jsonentfernt. - Metadaten:
viewport.themeColorinapp/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.tsxstattmotion-section.tsx). DreiChoiceGroup-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
ColonyOverviewausGET /apiaries/:id/colony-overview, Query-Key["apiaries", apiaryId, "colony-overview"](wie T3 Task 14). Offline greiftwithPackFallback(T3 Task 15). - Je Zeile:
code,status, Bedingungen,lastInspectionDate,openWarnings,openCritical. Ab T4 zusätzlichinspectionDueOn,segment,nextStepTitle. - Zustand der Zelle =
colonyStatusLight(row, { today, inspectionsVisible }), unverändert aus T3/T4. - Auf der Bienenstände-Liste lädt
useQueriesdie Ü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 beipointer-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
nZellen.n = floor((Breite_Container − (b + 4)/2) / (b + 4)), mindestens 3, höchstens 10. - Jede zweite Reihe ist um
(b + 4)/2nach rechts versetzt und um ein Viertel der Höhe nach oben verzahnt. Abstand 4 px. nmisst einResizeObserver(useContainerWidth).- Reihenfolge nach Volkscode, natürlich sortiert, wie Liste und Runde (Offene Frage 2).
- Reihen mit
-
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,XCirclewieStatusLightBadge) - 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).
- oben das Zustands-Icon (16 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--inputund keine Aussage zur Durchsicht. - Die 3-Tage-Grenze ist dieselbe wie „SOON“ in der T4-Rangfolge.
- Die Funktion
rimStepOf(row, today, inspectionsVisible)liegt insrc/lib/honeycomb/cell-state.ts.
- Ohne
-
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-inkhervor, 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 einaria-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, RandCanvasText. 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:
ApiaryCombmit 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, mituseMemo.
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] })ersetztL.iconmit den PNG-Markern. Die Dateien inpublic/leaflet/werden gelöscht.miniCombSvg(summary)(src/lib/honeycomb/mini-comb-svg.ts, reine Funktion, liefert einen SVG-String):- Hintergrund-Sechseck (Radius 24) in
--cardmit 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,
--cardmit Tinten-Rand). - Farben über
var(--condition-*). Weil dasdivIconim DOM liegt, ziehen Theme und Sonne ohne Neuaufbau mit.
- Hintergrund-Sechseck (Radius 24) in
- 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-tipmit--popover/--popover-foregroundund Tinten-Rand. Den Schließknopf.leaflet-popup-close-buttonvergrößert ein Pseudo-Element auf 44 px.
7.3 Barrierefreiheit und Rückfall
L.marker(..., { keyboard: true, title }). Nachaddsetzt der Codearia-labelam 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:
colonyStrengthzä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.
- Die Konstante
- 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) undqueens: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):- „Volk B3 seit 12.03.2025.“
- „Stärke zuletzt 9 Waben am 02.10., höchstens 14 Waben im Juli.“
- „Milben zuletzt 2,1 pro Tag am 28.09. – Gut.“
- „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 demselbenLifelineModelwie 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).
buildLifelineund 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;swarmSeasonKirsche bis Linde;SEASON_AUTOPILOT.winterVarroaWindow11-15 bis 12-20). - Ab T4 kommen sie aus dem Server, damit Änderungen in „Verwaltung > Automatisierung“ auch im Rad ankommen.
- Vor T4 aus
-
Additive Erweiterung von
GET /autopilot/today(T5-Task am Backend, Rechtcolonies:readwie bisher, keine Geheimnisse):interface AutopilotToday { // … unverändert aus T4 Task 15 … seasons: { apiaryId: string; apiaryName: string; segment: SeasonSegmentKey; phase: PhenologyPhaseKey | null; phaseSource: PhaseSourceKey | null; segmentSince: string; // neu, aus SeasonState reportedOn: string | null; // neu note: SeasonNote }[]; // neu: 'NO_COORDINATES' | 'WEATHER_GAPS' | 'WEATHER_STALE' | null seasonCalendar: { // neu, aus readRule(PHENOLOGY) und readRule(SEASON_AUTOPILOT) calendarFallback: Record<PhenologyPhaseKey, string>; // 'MM-DD' swarmSeason: { from: PhenologyPhaseKey; until: PhenologyPhaseKey }; winterVarroaWindow: { from: string; to: string }; }; }- Gebaut in
NextStepsService.todayausSeasonService.statesFor(hatsegmentSinceschon) undreadRule. - Fehlen die Felder (älteres Backend), nutzt das Frontend
season-defaults.ts.
- Gebaut in
-
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)insrc/lib/season/year-wheel.ts(reine Funktion):interface YearWheelModel { year: number; today: string; segments: { key: SeasonSegmentKey; from: string; to: string; source: 'REPORT' | 'GTS' | 'CALENDAR'; current: boolean; estimated: boolean }[]; windows: { key: 'TRACHT' | 'VARROA_SUMMER' | 'EINWINTERUNG' | 'VARROA_WINTER'; from: string; to: string }[]; taskWeeks: { weekStart: string; count: number }[]; overdueCount: number; reference: { apiaryId: string; apiaryName: string } | null; deviations: { apiaryName: string; segment: SeasonSegmentKey }[]; }-
Fenster:
- Tracht = Salweide bis Trachtende
- Varroa Sommer = Trachtende bis Herbst
- Einwinterung = Herbst bis Winter
- Varroa Winter =
winterVarroaWindow
Die Fenster liegen nacheinander und überlappen nicht.
-
Der aktuelle Abschnitt beginnt bei
segmentSince(gemeldet, geschätzt oder Kalender). -
Vergangene und künftige Grenzen sind Kalenderwerte mit
estimated = trueund 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.
- Segmente mit 2 px Lücke in
-
Innenring:
- Tracht in
--chart-3(voll) - Varroa in
--chart-4mit 45°-Schraffur (zwei Fenster) - Einwinterung in
--chart-5(voll)
Muster plus Legende, damit die Fenster auch ohne Farbsehen unterscheidbar sind.
- Tracht in
-
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-criticalmit 2 px Tinten-Ring, 4° vor dem Zeiger.
- Radius 3 px (1 Aufgabe), 4,5 px (2–3) bzw. 6 px (4 und mehr), in
-
Zentrum:
- Abschnitt (22 px, Space Grotesk halbfett)
- „seit {Datum} · {Quelle}“ (14 px; Quelle aus
PHASE_SOURCE_LABELS: „gemeldet am 18.04.“, „geschätzt“, „Kalender“; beinotezusätzlich „Wetterdaten lückenhaft“ bzw. „ohne Koordinaten“) - das laufende Arbeitsfenster mit Ende, oder die Abweichung eines Stands
-
Zeiger: Linie in
--foregroundmit 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:
- Kopf: Gruß als
h1in Tinte auf dem Seitenhintergrund und die Datumszeile, ohne Honigbanner und ohne Weiß auf Honig. Die Honigfarbe gehört allein dem Hauptknopf. - „Als Nächstes“ (T4 aus
/autopilot/today, offline aus Aufgaben): einziger Honigknopf der Seite. - „Außerdem heute“.
- T4-Karte „Durchsicht fällig“ je Stand.
- „Im Blick behalten“ (Meldungen).
- „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_STOCKin „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-foregroundhalbfett, 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-pressedundaria-labelam bestehendenButton(Proppressed). 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 UICheckbox):- 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.
- Kästchen 20 px, Rahmen 2 px
CheckboxFieldmitlabel,description?,checked,onCheckedChange,disabled?,appearance:"row"(Standard): ganze Zeile klickbar, mindestens 44 px (in Sheets 56 px übersize="sheet"), Text 16 px"tile": umrandete Kachel wieToggleChip; gewählt mit Honig-Tönung und 2-px-Tinten-Rand; Sonne:--cardmit Tinten-Rand"task": die Abhak-Zelle der Heute-Liste; diehoney-check-Animation bleibt als einzige „Moment“-Bewegung, jetzt auf dem Base-UI-Primitiv statt eines eigenenrole="checkbox"
ToggleChipwird nach der Umstellung gelöscht.
10.4 Tabs
ui/tabs.tsxmit einer Variantesegment(Standard):- Leiste in
--muted, Innenabstand 4 px. - Reiter 44 px (
pointer-fine: 36 px), Text 16 px. - Aktiv: Fläche
--card, Text--foregroundhalbfett, unten ein 3-px-Strich in--primary-ink. Der Zustand zeigt sich also über Fläche, Gewicht und Strich, nicht nur über Farbe.
- Leiste in
- Base UI liefert
role="tab"/tabpanel,aria-controlsund Pfeiltasten. - Die Variante
lineentfällt; die Statistik nutztsegment. ActiveHistoryTabswird eine Vorlage aufTabs(Propsvalue,onValueChange,activeCount,historyCount,childrenje Panel). Die 9 Listen legen ihre Liste in das Panel.TabSwitcherwird gelöscht; die beiden Einstellungsseiten nutzenTabsdirekt.
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 nutzenInput 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
- Besetzte Wabengassen in der Einwinterung (
- 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.
- Tokens additiv:
- Neue Tokens und Varianten in
globals.cssergänzen (-ink,area-*,queen-*,--destructive-foreground, Sonne-Sätze,pointer-*,sun). Bestehende Werte bleiben noch. contrast.spec.tsprüft schon die neuen Paare.
- Neue Tokens und Varianten in
- Darstellung:
- Appearance-Modul und Inline-Skript.
- Profil-Karte „Darstellung“ und Schnellschalter.
next-themesraus, 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).
- 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-primarybei Auswahl →border-foreground- Die Honig-Fläche bleibt
bg-primary.
- Tokenwerte umstellen:
- Werte aus 4.2 einsetzen, Verläufe entfernen.
ui/buttonStandard ohne Verlauf,dark:-Klassen ausui/*entfernen.- Der Wächter
contrast.spec.tswird ab hier hart (alle vier Sätze). - Das Entwicklungs-Flag aus Schritt 2 entfällt.
- Fokus und Größen:
- Globale Fokusregel.
- Entfernen der Komponenten-Fokusklassen (Codemod),
sm:h-*→pointer-fine:h-*, Schriftgrößen aus 4.5.
AREASundAreaIcon:nav-config.ts,quick-actions.ts,PageHeader,DetailHeader,EntityCard(Propareastattgradient),BrandMark, Icon-Abweichungen aus 2.2.- Ist T4 Task 17 gemergt:
INTENT_ICONSausAREAS.
- Auswahlgruppe: neue Optik,
appearance="cards", die drei Umstellungen. - Checkbox:
Checkboxumbauen,CheckboxFieldneu; dann je Datei umstellen (Formulare zuerst, dann Einstellungen, zuletzthoney-check).ToggleChiplöschen. - Tabs:
ui/tabsumbauen,ActiveHistoryTabsals Vorlage, die 9 Listen und 2 Einstellungsseiten umstellen.TabSwitcherlöschen. - Plus/Minus: Felder aus 10.5.
- Wächter:
baukasten-guard.spec.tswird hart. - Manifest und Icons (12), Service-Worker-Version.
- T5b (Abschnitte 6–9):
- Wabe auf der Stand-Seite, dann Bienenstände-Liste
- Marker
- Lebenslinie
- Backend-Zusatz
/autopilot/today, dann Rad und Dashboard-Umbau
Schritte 1–5 berühren viele Dateien mechanisch. Sie laufen in einem kurzen Fenster, in dem keine anderen Frontend-Tasks offen sind (15).
11. Icon und Farbe je Bereich
Quelle ist src/lib/areas.ts:
export type AreaTone = 'betrieb' | 'erfassung' | 'arbeit' | 'ernte' | 'neutral';
export interface Area { key: AreaKey; label: string; icon: LucideIcon; tone: AreaTone; href?: string }
export const AREAS: Readonly<Record<AreaKey, Area>>;
AreaIcon({ area, size })zeichnet das Icon in einer Wabenzelle (Sechseck, 40 px; in Listen 32 px).- Die Zelle ist mit dem Bereichston in 14 % getönt und hat 1 px Rand im Ton; das Icon steht im Ton. Sonne: Fläche
--card, Rand 2 px. - Die Navigation bleibt neutral (Icons in
--muted-foreground, aktiv Honig). Bunte Navigationslisten wären Unruhe. - Der Ton erscheint nur in:
AreaIcon(Seitenkopf, Detailkopf, Karten)- im „+ Neu“-Sheet
- bei den Ereignis-Icons der Lebenslinie
- bei den Absichts-Icons der T4-Vorschläge
Regel: Ein Bereichston ist nie eine Zustandsfarbe. Rot, Grün und Gelb der Ampel sowie --destructive und --warning sind für Zustände reserviert.
| Bereich | Schlüssel | Icon (lucide) | Ton | Hinweis |
|---|---|---|---|---|
| Start | dashboard |
LayoutDashboard |
neutral | |
| Suche | search |
Search |
neutral | |
| Bienenstände | apiaries |
MapPin |
betrieb | auch Kartenansicht |
| Völker | colonies |
Layers |
betrieb | Beute = gestapelte Zargen; das Sechseck ist der Marke vorbehalten |
| Königinnen | queens |
Crown |
betrieb | bisher Salbei-Verlauf von Hand |
| Überwinterung | winter |
Snowflake |
betrieb | |
| Pflanzenentwicklung (T4b) | phenology |
Flower2 |
betrieb | |
| Durchsicht | inspections |
ClipboardCheck |
erfassung | |
| Varroa | varroa |
Bug |
erfassung | |
| Schwarmkontrolle | swarm-control |
Sprout |
erfassung | bleibt, weil schon gelernt (T3) |
| Behandlung | treatments |
Syringe |
erfassung | |
| Fütterung | feedings |
Utensils |
erfassung | bleibt, weil schon gelernt (T3) |
| Aufgaben | tasks |
ListTodo |
arbeit | |
| Kalender | calendar |
CalendarDays |
arbeit | bisher Himmel-Verlauf von Hand |
| Vorschläge (T4) | suggestions |
Lightbulb |
arbeit | wie T4 Task 17 |
| Meldungen | notifications |
Bell |
arbeit | |
| Ernten | harvests |
Scissors |
ernte | Detailseite bisher Boxes |
| Schleuderungen | extractions |
FlaskConical |
ernte | Neu-Seite bisher Droplet |
| Honigchargen | honey-batches |
Droplet |
ernte | |
| Abfüllungen | bottling |
Package |
ernte | |
| Produkte | products |
ShoppingBag |
ernte | |
| Rohstoffe | raw-materials |
Boxes |
ernte | |
| Herstellungschargen | production-batches |
Factory |
ernte | |
| Lager | storage |
Warehouse |
ernte | bisher zusätzlich Package |
| Inventar | equipment |
Wrench |
ernte | bisher Salbei oder Himmel |
| Statistik | statistics |
BarChart3 |
neutral | |
| Benutzer & Rollen | users-roles |
Users |
neutral | |
| Automatisierung | automation |
Zap |
neutral | |
| Etikettenvorlagen | label-templates |
Tag |
neutral | bisher Himmel |
| Backup | backup |
Database |
neutral | |
| Audit-Log | audit-log |
History |
neutral | bisher Salbei |
| Profil | profile |
UserRound |
neutral | bisher Salbei |
- Eine Seite, die einen anderen Bereich nur erwähnt (z. B. die Schleuderung in der Honigcharge), darf dessen Icon tragen. Ein Icon bedeutet immer seinen Bereich.
- Die öffentliche Herkunftsseite behält ihre eigenen Icons.
12. Manifest und Icons
12.1 Markenzeichen
- Form: eine verdeckelte Wabenzelle.
- Sechseck mit spitzer Oberseite in Honig (#f0b13f) mit 6 % Rand in Tinte.
- Darin ein kleineres Sechseck in hellem Wachs (
oklch(0.88 0.11 85)#f9d280) als Deckel, leicht nach oben versetzt. - Es ist bei 16 px als Honig-Sechseck lesbar.
- Master:
public/brand/apicore-mark.svg. - App-Icon: Hintergrund in dunkler Tinte
oklch(0.24 0.035 55)#2c1b0f, Zelle mittig. Honig auf Tinte hat 8,7:1. Das ist eine Weiterentwicklung des bisherigen Schwarz-Gelb, kein Bruch. BrandMark-Komponente: dieselbe Zelle als SVG (24/32 px) neben dem Schriftzug „ApiCore“ (Space Grotesk). Sie ersetztHexagonauf 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.mjsaus dem Master-SVG (sharpals devDependency; heute nur transitiv über Next vorhanden). Das Skript ist idempotent. Die Ausgabe wird committet, damit das Docker-Image nicht vom Skript abhängt.
12.3 app/manifest.ts
{
id: "/", name: "ApiCore – Imkereiverwaltung", short_name: "ApiCore",
description: "Digitale Imkereiverwaltung für Wabenhain", lang: "de", dir: "ltr",
start_url: "/", scope: "/", display: "standalone",
background_color: "#f9f4ea", // = --background hell
theme_color: "#f9f4ea", // Statusleiste; dunkel über viewport.themeColor (5.2)
categories: ["productivity", "business"],
icons: [ /* any 192/512, maskable 192/512, monochrome 512, icon.svg any */ ],
shortcuts: [
{ name: "Durchsicht erfassen", short_name: "Durchsicht", url: "/inspections/new", icons: [{ src: "/icons/shortcut-inspection-96.png", sizes: "96x96" }] },
{ name: "Varroa messen", short_name: "Varroa", url: "/varroa/new", icons: [ … ] },
{ name: "Heute", url: "/dashboard", icons: [ … ] },
],
}
- Die Shortcuts sind Routen der Service-Worker-Shell (T3 Task 15). Sie öffnen also auch offline.
- Ein langer Druck auf das App-Icon führt direkt zur Durchsicht.
proxy.ts: Dermatchernimmt/icons/undapple-icon.pngaus, wie heuteicon.svg.public/sw.js:SHELL_ROUTESbleiben.- 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(ersetztsrc/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-*undarea-*(≥ 3) und allequeen-*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').
- Liest die vier Sätze aus
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) perreadFileSyncund vergleicht dieMM-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_colorundtheme_colorentsprechen dem hellen--background.- Die Shortcut-URLs stehen in
SHELL_ROUTES.
13.2 Unit
appearance.spec.ts:- Standard „Hell“
- „Wie Gerät“ folgt
matchMediaund reagiert auf Wechsel prefers-contrast: moreohne 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 setzttheme-color
areas.spec.ts:- Jeder
NAV_GROUPS-Eintrag und jedeQUICK_ACTIONhat einenAREAS-Schlüssel und dasselbe Icon - Kein Ton ist eine Zustandsfarbe
- Jeder
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_SINCEnicht 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 vorvarroaLight(T3)- Königinnen-Amtszeiten aus
queenHistory, Farbe ausmarkingColor - unbekannte Farbe → gestrichelt
- Behandlungsspannen
- Zukunft: T4-Schritte, sonst Aufgaben, höchstens 3
- fehlende Spur bei 403
<desc>-Text
- Werte vor
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
- gewählte Option hat
CheckboxField:- ganze Zeile schaltet
- Name aus dem Label
- Zeile mindestens 44 px (Klasse
min-h-11), im Sheetmin-h-14 tileundtaskhabenrole="checkbox"vom Primitiv
Tabs: Pfeiltasten wechseln, Panel isttabpanelmitaria-labelledby,ActiveHistoryTabszeigt Zähler.AppearanceSection: Wahl setzt Klasse und Attribute aufdocument.documentElementund speichert. Schnellschalter im Benutzermenü ist einmenuitemcheckbox.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 mitaria-label, Popup mit „Stand öffnen“, Liste unter der Karte, Offline-Hinweis. Leaflet wird gemockt.ColonyLifeline:figuremit 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) gegennpm run build && npm run startmit 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: activeundprefers-reduced-motion: reduceje einmal auf Start, Stand und Volk. - Die Bilder liegen als Anhang am PR, nicht im Repo.
- Werkzeug: Chrome DevTools (MCP
-
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:
- den Hauptknopf auf Start finden und tippen
- auf 50 cm Abstand sagen, welches Volk an einem Stand Kritisch ist und welches lange nicht durchgesehen wurde
- Brutwaben mit dem Stepper von 0 auf 6 bringen
- den Sonnenmodus einschalten
- 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:
- 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.
- Checkboxen und Tabs liegen an 29 Stellen, nicht an einer Handvoll.
- Der Fokus ist an 44 Stellen von Hand gebaut.
- 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
- 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).
- 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). - 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.
- Die Shell-Version steigt (
- 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.
- Erste Woche:
- Draußen-Test (13.4) mit dem Inhaber.
- Tokenwerte dürfen danach nur zusammen mit
contrast.spec.tsgeändert werden; der Test hält die Grenzen.
- 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:
AREASals 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.mdper/impeccable documentaus dem fertigen Stand erzeugen, damit spätere Seiten den Baukasten finden.
- ARCHITECTURE bekommt neue Annahmen unter der dann nächsten freien Nummer (T3 und T4 vergeben 22–27 und folgende):
16. Offene Fragen an den Inhaber
-
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.
-
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.standSlotmit 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.
-
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.
-
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:
- Herkunft zeigen: Ein Sheet kommt von seiner Kante, ein geöffnetes Teil fliegt aus seinem Stapel. Man sieht, wo man ist.
- Veränderung zeigen: Eine Zeile geht weg, die anderen rücken nach. Man sieht, was sich geändert hat.
- 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()aussrc/lib/motion/motion-preference.ts. Den Hook nutzt die Beute schon (hive-canvas.tsx,hive-visual-slot.tsx). Er verbindet die gewählte Stufe mitprefers-reduced-motionund reagiert sofort auf beides. Ab §5 setzt das Inline-Skriptdata-motionvor dem ersten Bild.useAppearance().setMotionlöst dabei weiterMOTION_EVENTaus, damit Szene und DOM sofort mitziehen. - Eine Quelle für CSS: die Variante
motion-full:(gibt es schon inglobals.css). Jede Verschiebung, Skalierung oder Drehung steht hintermotion-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): EinAppMotion-Rahmen im(app)-Layout setztMotionConfigje Stufe. BeifullstehtreducedMotionauf"never", weil der Hook das System schon einrechnet. Beisubtlesteht es auf"always": Dann laufen nur Deckkraft und Farbe. Beioffgilttransition={{ 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.
- Feldfehler,
- 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-eventsbleiben an). - Kein Klick wartet auf einen Austritt.
- Einzige gewollte Verzögerung ist das Wegfalten der Heute-Zeile (17.3).
- Kein Ziel ist während eines Eintritts gesperrt (
17.6 Leistung
- Nur
transformundopacitybewegen sich.- Für Zustandswechsel kommen Farbe (
color,background-color,border-color,fill,stroke) undclip-pathdazu (.honey-check). - Nie animiert werden
width,height,top,left,margin,padding,grid-template-rows,box-shadow,filterundbackdrop-filter. - Bestand, der das verletzt:
.today-rowfaltet übergrid-template-rows→motionlayout(17.4)next-step-inanimiertfilter: blur(6px)→ nur Deckkraft und--motion-shiftstat-bar-listanimiert die Breite → ohne Übergang
- Für Zustandswechsel kommen Farbe (
- Kein
transition-all: Die Übergänge nennen ihre Eigenschaften (transition-colors,transition-[opacity,transform]). Heute gibt es 8 Stellen, 7 davon inui/*. - Kein Layout-Thrash:
- Keine Messung (
getBoundingClientRect,offsetHeight) in einem Animationsschritt. motionmisst beilayouteinmal zu Beginn und bewegt dann pertransform.- Kein React-State pro Bild. DOM-Bewegung läuft über CSS oder über das WAAPI von
motion.
- Keine Messung (
will-changesetzt nurmotionselbst, nie dauerhaft in CSS.backdrop-blurnur 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:
motionnur überLazyMotionmitstrict. Elemente kommen alsm.*ausmotion/react-m, die Features (domMaxwegenlayout) 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
motionnicht (eigene Federn inuse-animated-node.ts).
17.7 Abnahme
- Wächter (
src/styles/baukasten-guard.spec.ts, Regelgruppe „Bewegung“, Ausnahmen mit Grund):- kein
motion-safe:vortranslate,scale,rotate,slide-,zoom-,animate-(→motion-full:) - kein
transition-all - kein
transition-[width|height]und keinduration-über 420 ms (Ausnahmecomponents/hive/**) hover:mittranslateoderscalenur incomponents/hive/**motion/reactliefert nurLazyMotion,MotionConfig,AnimatePresenceund Hooks; Elemente nur alsm.*- Keyframes und Übergänge in
globals.cssanimieren keine Eigenschaft aus der Verbotsliste in 17.6 (der Test liest die Datei)
- kein
- Tokens (
src/lib/motion/tokens.spec.ts):- Die CSS-Variablen entsprechen den TS-Konstanten.
SPRING_FLIGHThat dieselben Werte wie die Feder der Beute (eine Quelle).- Die Stützpunkte von
--ease-springweichen höchstens 0,02 vonspringStepab.
- Komponenten:
AppMotionsetztreducedMotionje Stufe.- Bei „Aus“ ist eine entfernte Zeile sofort weg, bei „Dezent“ ohne Verschiebung.
scrollBehaviorForliefertsmoothnur bei Voll und kurzer Strecke.
- Browser (Chrome DevTools):
- Jedes Muster aus 17.4 einmal in Voll, Dezent und Aus, dazu
prefers-reduced-motion: reduceperemulate. - Bei Dezent und bei reduce verschiebt sich nichts, bei Aus geht alles sofort.
- Dazu der Performance-Trace aus 17.6.
- Jedes Muster aus 17.4 einmal in Voll, Dezent und Aus, dazu
- 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.
- Review: einmal
review-animationsbzw.fixing-motion-performanceüber den Diff (Skill-Routing: erst Konzept, dann CSS, dannmotion, 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/**undapp/(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-inkhat auf--holo-bgweniger als 3:1. Deshalb setzt ein Rahmen.holo-stage--ring: var(--holo-edge-strong).contrast.spec.tsprüft das Paar (≥ 3) und die Etiketten--holo-label-fgauf--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).