Flip Tiles · Final Spec · Route: /planner
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.
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░ │ └──────────┴──────────────────────────────────────────────────┘
display: grid; grid-template-columns: repeat(7, 1fr); gap: 7px; height: 100%. Kacheln füllen die gesamte verbleibende Breite und Höhe.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.
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.
box-shadow: 0 0 0 2px var(--yellow), var(--sh-card).
Datumsziffer-Badge in --yellow. Tag-Label „Heute" zusätzlich als frosted Tag.
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.
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 gesetzt, nicht via border,
um Layout-Shift zu vermeiden. Die Kacheln behalten identische Außenmaße in allen Zuständen.
Wenn heroImageUrl vorhanden ist, wird das echte Foto als background-image gesetzt.
Fehlt es, greift die folgende Prioritätskette:
tagType = "protein" finden → Protein-FarbetagType = "cuisine" finden → Küchenstil-Farbebackground: var(--color-surface) (neutral)
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.
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)
background-image: url(heroImageUrl) mit background-size: cover::after-Pseudo-Element:linear-gradient(to bottom, rgba(0,0,0,.38) 0%, transparent 28%, transparent 48%, rgba(0,0,0,.62) 100%).ingredient, Vorrats-Zutaten (Staples) gedimmt als .ingredient--staple| Aktion | Stil | Verhalten |
|---|---|---|
| Koch-Modus starten | Primary (grün ausgefüllt) | Navigiert zu /planner/cook/[slotId] |
| Rezept ansehen | Secondary (Rahmen) | Navigiert zu /recipes/[recipeId] |
| Gericht tauschen | Secondary (Rahmen) | Öffnet Rezept-Picker-Drawer (§06) |
| Entfernen | Danger (roter Text, transparenter BG) | Löscht den Slot, Kachel wird leer |
.scene → .card.classList.toggle('flipped')opacity: 0.38; pointer-events: noneevent.stopPropagation(), classList.remove('flipped'), Geschwister-Opacity zurücksetzenslotMap-State vorhanden. Der Flip ist eine rein visuelle Operation.
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 → │ └────────────────────────────────┘
Anstelle numerischer Score-Deltas (die für leere Slots immer positiv sind und daher keine Information tragen) werden Begründungs-Tags angezeigt:
| Tag | Farbe | Bedeutung |
|---|---|---|
| Neues Protein | Grün | Proteinquelle kommt diese Woche noch nicht vor |
| Kein Overlap | Grün | Keine Zutaten-Überschneidung mit anderen Tagen |
| Aufwand: leicht | Gelb | Kochzeit < 30 Min oder Aufwand = einfach |
| Aufwand: mittel | Neutral | Mittlerer Aufwand |
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.
Der Rezept-Picker öffnet sich als Slide-in-Drawer von rechts — ausschließlich auf explizite Anfrage. Er hat keinen persistenten Platz im Layout mehr.
min(480px, 90vw)RecipePicker-Komponente (aktuell im rechten Panel) wird in einen
generischen Drawer gewrappt. Der Drawer-Wrapper ist neu; der Picker selbst bleibt unverändert.
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.
.protein-haehnchen, .cuisine-asiatisch, …).
.scene: role="button", tabindex="0", aria-expanded="false|true", aria-label="[Rezeptname] — Details anzeigen".card-back: aria-hidden="true" solange nicht geflipptaria-label="Schließen", type="button"Enter / Space flippt, Escape dreht zurückaria-hidden="true" wenn eine andere geflippt ist