From f139dce82c2b36270c139137ddd75a86587e8bdb Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Thu, 9 Apr 2026 18:19:57 +0200 Subject: [PATCH] =?UTF-8?q?docs(specs):=20add=20planner=20desktop=20redesi?= =?UTF-8?q?gn=20spec=20=E2=80=94=20flip=20tiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final design spec for the planner desktop layout overhaul: full-bleed color tiles, CSS 3D card flip for recipe detail, no persistent right panel, inline suggestions on empty days. Includes interactive mockup and written component spec. Co-Authored-By: Claude Sonnet 4.6 --- specs/planner-flip-tiles.html | 762 +++++++++++++++++++++++++ specs/planner-redesign-flip-tiles.html | 459 +++++++++++++++ 2 files changed, 1221 insertions(+) create mode 100644 specs/planner-flip-tiles.html create mode 100644 specs/planner-redesign-flip-tiles.html diff --git a/specs/planner-flip-tiles.html b/specs/planner-flip-tiles.html new file mode 100644 index 0000000..c6b8105 --- /dev/null +++ b/specs/planner-flip-tiles.html @@ -0,0 +1,762 @@ + + + + + +Planner — Flip Tiles + + + + + + +

Mealplan · Planer · Flip Tiles

+

Kachel-Flip + Zutaten-Farben

+

+ Klick auf eine gefüllte Kachel → sie dreht sich um. Auf der Rückseite: Rezeptname, Hauptzutaten, Aktionen. + Kein Expansion-Panel mehr. Leere Kacheln bleiben unverändert mit Inline-Vorschlägen. +

+ + + + + +
+
+ Palette + Farben nach Hauptzutat / Küchenstil + Fallback wenn heroImageUrl fehlt +
+ +
+ +
Hähnchen
Protein
+
Rind
Protein
+
Fisch
Protein
+
Tofu
Protein
+
vegetarisch
Protein
+
Schwein
Protein
+
Lamm
Protein
+
Ei
Protein
+
Hülsenfrüchte
Protein
+ +
Italienisch
Küche
+
Asiatisch
Küche
+
Indisch
Küche
+
Mexikanisch
Küche
+
Mediterran
Küche
+
+ +
+ Priorität: Wenn heroImageUrl vorhanden → echtes Foto. + Sonst: Farbe nach erstem Protein-Tag (z.B. tagType=protein, tagName=Hähnchen). + Wenn kein Protein-Tag → Farbe nach Küchenstil-Tag (tagType=cuisine). + Fallback auf --color-surface neutral. + Die Farbwerte werden als CSS-Klassen gemappt: protein-haehnchen, cuisine-asiatisch etc. +
+
+ + + + + +
+
+ Demo + Flip-Interaktion — zum Klicken + Echte CSS-3D-Transition +
+ +

Klicke auf eine Kachel um sie umzudrehen. × auf der Rückseite klappt zurück.

+ +
+ + +
+
Standard
+
+
+
+
+
+ Mo + 7 +
+
+
Hähnchen-Curry
+
35 Min · mittel
+
+ Hähnchen + 4 Port. +
+
+
+
+
+
+
+ Mo · 7. Apr + +
+
Hähnchen-Curry
+
35 Min · mittel · 4 Port.
+
+ Hähnchen + Kokosmilch + Paprika + Spinat + Curry + Knoblauch +
+
+ + + + +
+
+
+
+
+
+ + +
+
Heute
+
+
+
+
+
+ Di + 8 +
+
+
Pasta Bolognese
+
45 Min · mittel
+
+ Rind + Heute +
+
+
+
+
+
+
+ Di · Heute + +
+
Pasta Bolognese
+
45 Min · mittel · 4 Port.
+
+ Rinderhack + Pasta + Tomaten + Zwiebeln + Olivenöl + Knoblauch +
+
+ + + + +
+
+
+
+
+
+ + +
+
Ausgewählt (bereits umgedreht)
+
+
+
+
+
+ Mi + 9 +
+
+
Gemüse-Stir-fry
+
20 Min · einfach
+
Tofu
+
+
+
+
+
+
+ Mi · 9. Apr + +
+
Gemüse-Stir-fry
+
20 Min · einfach · 2 Port.
+
+ Tofu + Paprika + Brokkoli + Karotten + Sesamöl + Sojasauce +
+
+ + + + +
+
+
+
+
+
+ + +
+
Leer — kein Flip
+
+
+ Sa + 12 +
+
+
+
+
Gericht wählen
+
+
+
Vorschläge
+
Ramen mit EiNeues Protein
+
ShakshukaKein Overlap
+
TacosAufwand: leicht
+
Alle Rezepte →
+
+
+
+ +
+ +
+ Flip-Mechanik: CSS transform:rotateY(180deg) auf dem .card wrapper, + backface-visibility:hidden auf beiden Faces, perspective:900px auf der Scene. + Transition: .45s cubic-bezier(.4,0,.2,1) (Material-Easing — schnell herein, weich heraus). + Der × Button auf der Rückseite stoppt den Klick-Event mit stopPropagation() + und dreht die Karte zurück. Kein zusätzlicher State nötig — die Karte ist selbst das State-Element. +

+ Farbstreifen oben auf der Rückseite = 5px Gradient, identisch mit der Front-Farbe. + Gibt visuelle Kontinuität zwischen Vorder- und Rückseite. +
+
+ + + + + +
+
+ Seite + Vollansicht — Mittwoch umgedreht + Kein rechtes Panel. Kacheln bis zum Rand. +
+ +
+
+ Wochenplaner + 7.–13. Apr +
+ +
+ +
+ +
+
+
Abwechslungs-Score
+
7.8/10
+
+
Protein
8.0
+
Zutaten
7.2
+
Aufwand
8.2
+ Variety-Analyse → +
+
+
Überschneidungen
+
⚠ Hähnchen an Mo + Do
+
⚠ Tomaten an Di + Do
+
+
+
Geplant
+
5/ 7 Tage
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+
Mo7
+
Hähnchen-Curry
35 Min · mittel
+
+
+
+ + +
+
+
+
+
Di8
+
Pasta Bolognese
45 Min · mittel
+
+
+
+ + +
+
+
+
+
+ Mi + 9 +
+
+
Gemüse-Stir-fry
+
20 Min · einfach
+
Tofu
+
+
+
+
+
+
+ Mi · 9. Apr + +
+
Gemüse-Stir-fry
+
20 Min · einfach · 2 Port.
+
+ Tofu + Paprika + Brokkoli + Karotten + Ingwer + Sesamöl + Sojasauce +
+
+ + + + +
+
+
+
+
+ + +
+
+
+
+
Do10
+
Lachs mit Kartoffeln
30 Min · einfach
+
+
+
+ + +
+
+
+
+
Fr11
+
Pizza Margherita
50 Min · aufwändig
+
+
+
+ + +
+
Sa12
+
+
Gericht wählen
+
+
Vorschläge
+
Ramen mit EiNeues Protein
+
ShakshukaKein Overlap
+
Alle Rezepte →
+
+
+ + +
+
So13
+
+
Gericht wählen
+
+
Vorschläge
+
Grünes Thai-CurryNeues Protein
+
TacosAufwand: leicht
+
Alle Rezepte →
+
+
+ +
+
+
+
+ +
+ Layout: Linke Sidebar (Variety-Score) bleibt. Kein rechtes Panel mehr. + Die Kacheln füllen den gesamten verbleibenden Platz (flex:1) — 7 gleich breite Spalten, + volle Höhe (height:100% auf Grid und Kacheln). Kein Layout-Shift, kein After-Scroll. +

+ Dimm-Effekt: Beim Flip werden alle anderen Kacheln auf 38% gedimmt. + Kein neuer API-Aufruf nötig — reine CSS-Klasse per JS. +

+ „Gericht tauschen": Öffnet den Rezept-Picker als Slide-in-Drawer von rechts + (kein persistentes Panel). Drawer schließt sich nach Auswahl oder Abbruch. +

+ Leere Kacheln: Zeigen Inline-Vorschläge auch im gedimmten Zustand (wenn + eine andere Kachel geflippt ist). Kein Flip auf leeren Kacheln. +
+
+ + + + + diff --git a/specs/planner-redesign-flip-tiles.html b/specs/planner-redesign-flip-tiles.html new file mode 100644 index 0000000..740d6b4 --- /dev/null +++ b/specs/planner-redesign-flip-tiles.html @@ -0,0 +1,459 @@ + + + + + + Planner Redesign — Flip Tiles · Final Spec + + + + + +
+ +
+
+

Planner Desktop Redesign

+

Flip Tiles · Final Spec · Route: /planner

+
+
+ Version 1.0
+ 2026-04
+ Mockup: specs/planner-flip-tiles.html +
+
+ +
+

+ Der Wochenplaner hat auf Desktop aktuell ~80 % vertikalen Leerraum unterhalb des 7-Spalten-Kalenders. + Zusätzlich ist das rechte Panel im Leerlauf nicht genutzt. Dieses Spec beschreibt ein vollständiges + Redesign der Desktop-Hauptfläche: Die Kacheln füllen die volle Höhe und Breite, Rezeptdetails werden + über einen CSS-3D-Flip direkt in der Kachel angezeigt, und leere Tage zeigen Inline-Vorschläge. +

+

+ Das rechte Panel entfällt dauerhaft. Der Rezept-Picker öffnet sich als Slide-in-Drawer ausschließlich + auf Anfrage (Aktion „Gericht tauschen" auf der Kachel-Rückseite). Der Toolbar-Button + „Gericht hinzufügen" entfällt, da jede leere Kachel eine eigene CTA hat. +

+
+ + + +
+ +

Seitenstruktur

+ +

Desktop-Layout: 2 Spalten. Kein persistentes rechtes Panel mehr.

+ +
┌─────────────────────────────────────────────────────────────┐
+│  Toolbar (Wochenplaner · 7.–13. Apr  ‹ ›  Heute)           │
+├──────────┬──────────────────────────────────────────────────┤
+│ Sidebar  │  7-Spalten-Kachelgrid (flex: 1, height: 100%)   │
+│ 184 px   │                                                  │
+│ Variety  │  Mo    Di    Mi    Do    Fr    Sa    So          │
+│ Score    │  ████  ████  ████  ████  ████  ░░░░  ░░░░       │
+│          │  ████  ████  ████  ████  ████  ░+░░  ░+░░       │
+│          │  ████  ████  ████  ████  ████  ░Vor░  ░Vor░      │
+└──────────┴──────────────────────────────────────────────────┘
+ +
    +
  • Sidebar (184 px, flex-shrink: 0): Variety-Score-Card, Sub-Scores, Überschneidungs-Warnungen, Link zur Variety-Analyse. Unverändert.
  • +
  • Main (flex: 1): display: grid; grid-template-columns: repeat(7, 1fr); gap: 7px; height: 100%. Kacheln füllen die gesamte verbleibende Breite und Höhe.
  • +
  • Toolbar: Nur Navigation — Wochenbezeichnung, Zurück/Vor-Pfeile, Heute-Button. Kein „+ Gericht hinzufügen" mehr.
  • +
+ +
+ Entfernt: Das rechte Panel (width: 228px) mit der „Heute Abend"-Karte und dem Leerlauf-Hinweis entfällt vollständig. Koch-Modus ist auf der Kachel-Rückseite zugänglich. +
+
+ + + +
+ +

Tile States

+ +
+
+
Standard (gefüllt)
+
+ Vollbild-Farbhintergrund (Gradient nach Zutat/Küche) oder heroImageUrl. + Dual-Gradient-Overlay (oben + unten dunkel, Mitte klar). + Oben: Tageskürzel + Datumsziffer. Unten: Rezeptname, Kochzeit, Tags. +

+ box-shadow: var(--sh-card) — kein sichtbarer Ring. +
+
+
+
Heute (gefüllt)
+
+ Identisch wie Standard, aber mit gelbem Ring via + box-shadow: 0 0 0 2px var(--yellow), var(--sh-card). + Datumsziffer-Badge in --yellow. Tag-Label „Heute" zusätzlich als frosted Tag. +
+
+
+
Ausgewählt / Geflippt
+
+ Grüner Ring: box-shadow: 0 0 0 2px var(--green), var(--sh-raised). + Karte dreht sich 180° (CSS 3D, siehe §04). Alle anderen Kacheln werden auf 38 % Deckkraft + gedimmt und sind nicht klickbar. +
+
+
+
Leer
+
+ Kein Flip. Gestrichelter Rahmen (border: 1.5px dashed var(--color-border)), + background: var(--color-surface). Oben: Tageskürzel + Datum. + Darunter: + Icon + „Gericht wählen". Rest der Kachel: Inline-Vorschläge (§05). +
+
+
+ +
+ box-shadow statt border: Statusringe werden via box-shadow gesetzt, nicht via border, + um Layout-Shift zu vermeiden. Die Kacheln behalten identische Außenmaße in allen Zuständen. +
+
+ + + +
+ +

Ingredient & Cuisine Colors

+ +

+ Wenn heroImageUrl vorhanden ist, wird das echte Foto als background-image gesetzt. + Fehlt es, greift die folgende Prioritätskette: +

+
    +
  1. Ersten Tag mit tagType = "protein" finden → Protein-Farbe
  2. +
  3. Ersten Tag mit tagType = "cuisine" finden → Küchenstil-Farbe
  4. +
  5. Fallback: background: var(--color-surface) (neutral)
  6. +
+ +

Protein-Farben

+
+
Hähnchen
protein-haehnchen
+
Rind
protein-rind
+
Fisch
protein-fisch
+
Tofu
protein-tofu
+
Vegetarisch
protein-veg
+
Schwein
protein-schwein
+
Lamm
protein-lamm
+
Ei
protein-ei
+
Hülsen­früchte
protein-huelsenfruechte
+
+ +

Küchenstil-Farben

+
+
Italienisch
cuisine-italienisch
+
Asiatisch
cuisine-asiatisch
+
Indisch
cuisine-indisch
+
Mexikanisch
cuisine-mexikanisch
+
Mediterran
cuisine-mediterran
+
+ +

+ Die CSS-Klassen (protein-haehnchen, cuisine-asiatisch, …) werden + serverseitig aus den Rezept-Tags abgeleitet und als Svelte-Prop übergeben, z.B. + colorClass="protein-haehnchen". Das Component setzt die Klasse auf dem Kachel-Wrapper. +

+
+ + + +
+ +

CSS 3D Card Flip

+ +

Jede gefüllte Kachel besteht aus drei verschachtelten Elementen:

+
.scene   → perspective: 900px; border-radius: var(--radius-lg); cursor: pointer
+  .card  → position: relative; transform-style: preserve-3d
+           transition: transform .45s cubic-bezier(.4,0,.2,1)
+           .card.flipped → transform: rotateY(180deg)
+    .card-front → backface-visibility: hidden; position: absolute; inset: 0
+    .card-back  → backface-visibility: hidden; transform: rotateY(180deg)
+                   position: absolute; inset: 0; background: var(--color-page)
+ +

Vorderseite

+
    +
  • Vollbild-Farbe oder background-image: url(heroImageUrl) mit background-size: cover
  • +
  • Dual-Gradient-Overlay als absolutes ::after-Pseudo-Element:
    + linear-gradient(to bottom, rgba(0,0,0,.38) 0%, transparent 28%, transparent 48%, rgba(0,0,0,.62) 100%)
  • +
  • Oben links: Tageskürzel (9px uppercase). Oben rechts: Datums-Badge (Kreis)
  • +
  • Unten: Rezeptname (Fraunces 13px), Meta-Zeile (Kochzeit · Aufwand), Tag-Chips
  • +
+ +

Rückseite

+
    +
  • Farbstreifen (5 px) oben — identischer Gradient wie die Vorderseite. Gibt visuelle Kontinuität.
  • +
  • Tageskürzel + Datum (links) · × Schließen-Button (rechts)
  • +
  • Rezeptname (Fraunces 15px)
  • +
  • Meta: Kochzeit · Aufwand · Portionen
  • +
  • Zutaten-Pills: normale Zutaten als .ingredient, Vorrats-Zutaten (Staples) gedimmt als .ingredient--staple
  • +
  • Aktionen (gestapelt, volle Breite):
  • +
+ + + + + + + + + +
AktionStilVerhalten
Koch-Modus startenPrimary (grün ausgefüllt)Navigiert zu /planner/cook/[slotId]
Rezept ansehenSecondary (Rahmen)Navigiert zu /recipes/[recipeId]
Gericht tauschenSecondary (Rahmen)Öffnet Rezept-Picker-Drawer (§06)
EntfernenDanger (roter Text, transparenter BG)Löscht den Slot, Kachel wird leer
+ +

Interaction Flow

+
    +
  • Klick auf .scene.card.classList.toggle('flipped')
  • +
  • Alle Geschwister-Kacheln im Grid → opacity: 0.38; pointer-events: none
  • +
  • × Button auf Rückseite → event.stopPropagation(), classList.remove('flipped'), Geschwister-Opacity zurücksetzen
  • +
  • Escape-Taste → aktive Kachel zurückdrehen
  • +
+ +
+ Kein API-Aufruf beim Flip. Alle dargestellten Daten (Name, Zutaten, Aktionen) sind bereits + im vorhandenen slotMap-State vorhanden. Der Flip ist eine rein visuelle Operation. +
+
+ + + +
+ +

Empty Tile — Inline Suggestions

+ +

Leere Kacheln haben denselben height: 100% wie gefüllte Kacheln. Kein Flip.

+ +
┌─────────────────┐
+│ Sa     12       │  ← Tageskürzel + Datum
+│─────────────────│
+│       +         │
+│  Gericht wählen │  ← Klick öffnet Rezept-Picker-Drawer
+│─────────────────│
+│ VORSCHLÄGE      │
+│ Ramen mit Ei  [Neues Protein]  │
+│ Shakshuka     [Kein Overlap]   │
+│ Tacos         [Aufwand: leicht]│
+│                                │
+│     Alle Rezepte →             │
+└────────────────────────────────┘
+ +

Vorschlag-Tags (Reasoning)

+

Anstelle numerischer Score-Deltas (die für leere Slots immer positiv sind und daher keine Information tragen) + werden Begründungs-Tags angezeigt:

+ + + + + + + + + +
TagFarbeBedeutung
Neues ProteinGrünProteinquelle kommt diese Woche noch nicht vor
Kein OverlapGrünKeine Zutaten-Überschneidung mit anderen Tagen
Aufwand: leichtGelbKochzeit < 30 Min oder Aufwand = einfach
Aufwand: mittelNeutralMittlerer Aufwand
+ +
+ Datenquelle: Die vorhandene GET /api/suggestions?weekId=&dayOfWeek= API liefert + SuggestionItem { recipe, scoreDelta, hasConflict }. Die Reasoning-Tags werden frontend-seitig + aus den Rezept-Tags und dem vorhandenen slotMap abgeleitet, kein Backend-Änderungsbedarf. +
+
+ + + +
+ +

Recipe Picker Drawer

+ +

+ Der Rezept-Picker öffnet sich als Slide-in-Drawer von rechts — ausschließlich auf explizite Anfrage. + Er hat keinen persistenten Platz im Layout mehr. +

+ +

Trigger

+
    +
  • Klick auf „Gericht tauschen" auf der Kachel-Rückseite
  • +
  • Klick auf „Gericht wählen" CTA oder Vorschlag-Zeile auf einer leeren Kachel
  • +
+ +

Drawer-Verhalten

+
    +
  • Slide-in von rechts, überlagert den Inhalt (kein Layout-Shift)
  • +
  • Breite: min(480px, 90vw)
  • +
  • Backdrop (halbtransparent) schließt den Drawer bei Klick
  • +
  • Nach Auswahl: Drawer schließt sich, Slot wird aktualisiert, Kachel zeigt neues Rezept
  • +
+ +
+ Der bestehende RecipePicker-Komponente (aktuell im rechten Panel) wird in einen + generischen Drawer gewrappt. Der Drawer-Wrapper ist neu; der Picker selbst bleibt unverändert. +
+
+ + + +
+ +

Mobile — Out of Scope

+ +

+ Dieses Spec betrifft ausschließlich die Desktop-Ansicht (≥ 768px). + Das mobile Layout (vertikaler Stack, DayMealCard, ActionSheet) bleibt unverändert. + CSS-3D-Flips auf Touch-Geräten haben bekannte Rendering-Unterschiede auf älteren Android-Browsern — + ein separates Issue sollte die mobile Interaktion (ggf. Slide-up Sheet statt Flip) spezifizieren. +

+
+ + + +
+ +

Komponenten-Übersicht

+ +
+ src/routes/(app)/planner/+page.svelte + Ändern + Rechtes Panel entfernen. Layout auf 2-spaltig (sidebar + main) umstellen. Toolbar-Button entfernen. Grid-Höhe auf 100% setzen. +
+
+ src/lib/planner/DayMealCard.svelte + Ersetzen / umbenennen + Zur Flip-Kachel umbauen: .scene → .card → .card-front + .card-back. Farb-Klassen-Prop, Gradient-Overlay, Back-Face mit Aktionen. +
+
+ src/lib/planner/EmptyDayTile.svelte + Neu + Leere Kachel: + CTA + Inline-Suggestion-Liste mit Reasoning-Tags. Ersetzt den bisherigen leeren Slot-Platzhalter. +
+
+ src/lib/planner/RecipePickerDrawer.svelte + Neu + Drawer-Wrapper um den bestehenden RecipePicker. Slide-in von rechts, Backdrop, Schließ-Logik. +
+
+ src/lib/planner/RecipePicker.svelte + Ändern + Aus dem rechten Panel lösen. Bekommt slotId als Prop. Keine Änderung an der Such-/Auswahl-Logik nötig. +
+
+ src/app.css + Ergänzen + 14 Farb-Klassen für Protein- und Küchenstil-Gradients hinzufügen (.protein-haehnchen, .cuisine-asiatisch, …). +
+
+ + + +
+ +

A11y-Anforderungen

+ +
    +
  • .scene: role="button", tabindex="0", aria-expanded="false|true", aria-label="[Rezeptname] — Details anzeigen"
  • +
  • .card-back: aria-hidden="true" solange nicht geflippt
  • +
  • × Schließen-Button: aria-label="Schließen", type="button"
  • +
  • Keyboard: Enter / Space flippt, Escape dreht zurück
  • +
  • Dimming: gedimmte Kacheln bekommen aria-hidden="true" wenn eine andere geflippt ist
  • +
+
+ +
+ +