docs(specs): add planner desktop redesign spec — flip tiles

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 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 18:19:57 +02:00
parent 0596fddcd3
commit f139dce82c
2 changed files with 1221 additions and 0 deletions

View File

@@ -0,0 +1,459 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Planner Redesign — Flip Tiles · Final Spec</title>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
<!--
spec:agent
document: Planner Desktop Redesign — Flip Tiles
version: 1.0
route: /planner (desktop)
screens: Planner main area — tile grid, sidebar, recipe picker drawer
key-decisions:
- Full-bleed color/image tiles (no blank body space)
- CSS 3D card flip replaces expansion panel
- No persistent right panel — tiles fill full remaining width
- Ingredient/cuisine color palette as heroImageUrl fallback
- Inline suggestions on empty tiles (reasoning tags, no delta numbers)
- No "Gericht hinzufügen" toolbar button (empty tile CTA handles it)
- Recipe picker opens as slide-in drawer (on demand only)
last-updated: 2026-04
reference-mockups:
- specs/planner-flip-tiles.html (interactive demo, color palette)
-->
<style>
:root {
--color-page: #FAFAF7;
--color-surface: #F5F4EE;
--color-subtle: #EDECEA;
--color-border: #D8D7D0;
--color-text-muted: #6B6A63;
--color-text: #1C1C18;
--green-tint: #E8F5EA;
--green-light: #AEDCB0;
--green: #3D8C4A;
--green-dark: #2E6E39;
--yellow-tint: #FDF6D8;
--yellow-light: #F9E08A;
--yellow: #F2C12E;
--yellow-text: #8A6800;
--color-error: #DC4C3E;
--font-display: 'Fraunces', Georgia, serif;
--font-sans: 'DM Sans', system-ui, sans-serif;
--font-mono: 'DM Mono', monospace;
--radius-sm: 4px; --radius-md: 6px; --radius-lg: 10px; --radius-full: 9999px;
--shadow-card: 0 1px 3px rgba(28,28,24,.06), 0 1px 2px rgba(28,28,24,.04);
--shadow-raised: 0 4px 12px rgba(28,28,24,.10), 0 2px 4px rgba(28,28,24,.06);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: var(--font-sans); background: var(--color-page); color: var(--color-text); font-size: 14px; line-height: 1.6; }
.doc { max-width: 900px; margin: 0 auto; padding: 48px 40px 96px; }
.doc-header { display: flex; justify-content: space-between; align-items: flex-end; padding-bottom: 28px; border-bottom: 1px solid var(--color-border); margin-bottom: 48px; }
.doc-header h1 { font-family: var(--font-display); font-size: 26px; font-weight: 500; letter-spacing: -0.02em; margin-bottom: 4px; }
.doc-header p { font-size: 13px; color: var(--color-text-muted); }
.doc-meta { font-family: var(--font-mono); font-size: 11px; color: var(--color-text-muted); text-align: right; line-height: 1.9; }
.intro { font-size: 14px; line-height: 1.75; color: var(--color-text); max-width: 700px; margin-bottom: 48px; }
.intro p + p { margin-top: 12px; }
.section { margin-bottom: 56px; }
.section-label { font-size: 10px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-text-muted); padding-bottom: 10px; border-bottom: 1px solid var(--color-border); margin-bottom: 28px; }
h2 { font-family: var(--font-display); font-size: 20px; font-weight: 400; margin-bottom: 14px; }
h3 { font-size: 13px; font-weight: 600; margin-bottom: 8px; color: var(--color-text); }
p { margin-bottom: 10px; font-size: 14px; line-height: 1.7; }
ul { padding-left: 20px; margin-bottom: 12px; }
li { font-size: 14px; line-height: 1.65; margin-bottom: 4px; }
code { font-family: var(--font-mono); font-size: 12px; background: var(--color-subtle); border-radius: 3px; padding: 1px 5px; }
pre { font-family: var(--font-mono); font-size: 12px; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: 14px 16px; margin: 12px 0; overflow-x: auto; line-height: 1.6; }
.callout { background: var(--color-surface); border-left: 3px solid var(--color-border); border-radius: 0 var(--radius-md) var(--radius-md) 0; padding: 12px 16px; margin: 16px 0; font-size: 13px; line-height: 1.65; }
.callout.green { border-color: var(--green); background: var(--green-tint); }
.callout.yellow { border-color: var(--yellow); background: var(--yellow-tint); }
.callout strong { font-weight: 600; }
table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 16px 0; }
th { font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-muted); padding: 8px 12px; text-align: left; border-bottom: 2px solid var(--color-border); }
td { padding: 9px 12px; border-bottom: 1px solid var(--color-subtle); vertical-align: top; }
tr:last-child td { border-bottom: none; }
.swatch-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0; }
.swatch { width: 80px; border-radius: var(--radius-md); overflow: hidden; box-shadow: var(--shadow-card); }
.swatch-color { height: 44px; }
.swatch-name { font-size: 10px; font-weight: 500; padding: 4px 6px; background: var(--color-page); border-top: 1px solid var(--color-border); }
.swatch-sub { font-size: 9px; color: var(--color-text-muted); padding: 0 6px 4px; }
.state-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin: 16px 0; }
.state-card { border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 14px 16px; }
.state-name { font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-text-muted); margin-bottom: 6px; }
.state-desc { font-size: 13px; line-height: 1.6; }
.component-row { display: flex; gap: 8px; align-items: baseline; padding: 10px 0; border-bottom: 1px solid var(--color-subtle); }
.component-row:last-child { border-bottom: none; }
.comp-file { font-family: var(--font-mono); font-size: 12px; color: var(--color-text); flex: 0 0 auto; min-width: 280px; }
.comp-action { font-size: 13px; color: var(--color-text-muted); }
.badge { display: inline-block; font-size: 10px; font-weight: 600; padding: 2px 7px; border-radius: var(--radius-full); }
.badge-new { background: var(--green-tint); color: var(--green-dark); }
.badge-mod { background: var(--yellow-tint); color: var(--yellow-text); }
.badge-del { background: #fde8e8; color: var(--color-error); }
</style>
</head>
<body>
<div class="doc">
<div class="doc-header">
<div>
<h1>Planner Desktop Redesign</h1>
<p>Flip Tiles · Final Spec · Route: <code>/planner</code></p>
</div>
<div class="doc-meta">
Version 1.0<br>
2026-04<br>
Mockup: <code>specs/planner-flip-tiles.html</code>
</div>
</div>
<div class="intro">
<p>
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.
</p>
<p>
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.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">01 · Layout</div>
<h2>Seitenstruktur</h2>
<p>Desktop-Layout: 2 Spalten. Kein persistentes rechtes Panel mehr.</p>
<pre>┌─────────────────────────────────────────────────────────────┐
│ 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░ │
└──────────┴──────────────────────────────────────────────────┘</pre>
<ul>
<li><strong>Sidebar (184 px, flex-shrink: 0):</strong> Variety-Score-Card, Sub-Scores, Überschneidungs-Warnungen, Link zur Variety-Analyse. Unverändert.</li>
<li><strong>Main (flex: 1):</strong> <code>display: grid; grid-template-columns: repeat(7, 1fr); gap: 7px; height: 100%</code>. Kacheln füllen die gesamte verbleibende Breite und Höhe.</li>
<li><strong>Toolbar:</strong> Nur Navigation — Wochenbezeichnung, Zurück/Vor-Pfeile, Heute-Button. Kein „+ Gericht hinzufügen" mehr.</li>
</ul>
<div class="callout yellow">
<strong>Entfernt:</strong> Das rechte Panel (<code>width: 228px</code>) mit der „Heute Abend"-Karte und dem Leerlauf-Hinweis entfällt vollständig. Koch-Modus ist auf der Kachel-Rückseite zugänglich.
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">02 · Kachel-Zustände</div>
<h2>Tile States</h2>
<div class="state-grid">
<div class="state-card">
<div class="state-name">Standard (gefüllt)</div>
<div class="state-desc">
Vollbild-Farbhintergrund (Gradient nach Zutat/Küche) oder <code>heroImageUrl</code>.
Dual-Gradient-Overlay (oben + unten dunkel, Mitte klar).
Oben: Tageskürzel + Datumsziffer. Unten: Rezeptname, Kochzeit, Tags.
<br><br>
<code>box-shadow: var(--sh-card)</code> — kein sichtbarer Ring.
</div>
</div>
<div class="state-card">
<div class="state-name">Heute (gefüllt)</div>
<div class="state-desc">
Identisch wie Standard, aber mit gelbem Ring via
<code>box-shadow: 0 0 0 2px var(--yellow), var(--sh-card)</code>.
Datumsziffer-Badge in <code>--yellow</code>. Tag-Label „Heute" zusätzlich als frosted Tag.
</div>
</div>
<div class="state-card">
<div class="state-name">Ausgewählt / Geflippt</div>
<div class="state-desc">
Grüner Ring: <code>box-shadow: 0 0 0 2px var(--green), var(--sh-raised)</code>.
Karte dreht sich 180° (CSS 3D, siehe §04). Alle anderen Kacheln werden auf 38 % Deckkraft
gedimmt und sind nicht klickbar.
</div>
</div>
<div class="state-card">
<div class="state-name">Leer</div>
<div class="state-desc">
Kein Flip. Gestrichelter Rahmen (<code>border: 1.5px dashed var(--color-border)</code>),
<code>background: var(--color-surface)</code>. Oben: Tageskürzel + Datum.
Darunter: <code>+</code> Icon + „Gericht wählen". Rest der Kachel: Inline-Vorschläge (§05).
</div>
</div>
</div>
<div class="callout">
<strong>box-shadow statt border:</strong> Statusringe werden via <code>box-shadow</code> gesetzt, nicht via <code>border</code>,
um Layout-Shift zu vermeiden. Die Kacheln behalten identische Außenmaße in allen Zuständen.
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">03 · Farb-Palette</div>
<h2>Ingredient &amp; Cuisine Colors</h2>
<p>
Wenn <code>heroImageUrl</code> vorhanden ist, wird das echte Foto als <code>background-image</code> gesetzt.
Fehlt es, greift die folgende Prioritätskette:
</p>
<ol style="padding-left:20px;margin-bottom:16px;">
<li>Ersten Tag mit <code>tagType = "protein"</code> finden → Protein-Farbe</li>
<li>Ersten Tag mit <code>tagType = "cuisine"</code> finden → Küchenstil-Farbe</li>
<li>Fallback: <code>background: var(--color-surface)</code> (neutral)</li>
</ol>
<h3>Protein-Farben</h3>
<div class="swatch-row">
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4923a,#a85e1a,#7a3d0c)"></div><div class="swatch-name">Hähnchen</div><div class="swatch-sub">protein-haehnchen</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#c04545,#8b2020,#5a1010)"></div><div class="swatch-name">Rind</div><div class="swatch-sub">protein-rind</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#5b9fd4,#2868a0,#10406e)"></div><div class="swatch-name">Fisch</div><div class="swatch-sub">protein-fisch</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#5fa85e,#2e7031,#1a4a1e)"></div><div class="swatch-name">Tofu</div><div class="swatch-sub">protein-tofu</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#7bc47b,#3d8c3d,#1e5a1e)"></div><div class="swatch-name">Vegetarisch</div><div class="swatch-sub">protein-veg</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4785a,#a04535,#6e2418)"></div><div class="swatch-name">Schwein</div><div class="swatch-sub">protein-schwein</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#9e6b3a,#6b3f1a,#3e2208)"></div><div class="swatch-name">Lamm</div><div class="swatch-sub">protein-lamm</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4b832,#a07010,#6e4800)"></div><div class="swatch-name">Ei</div><div class="swatch-sub">protein-ei</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#8b6b3a,#5e421a,#3a2408)"></div><div class="swatch-name">Hülsen­früchte</div><div class="swatch-sub">protein-huelsenfruechte</div></div>
</div>
<h3>Küchenstil-Farben</h3>
<div class="swatch-row">
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#c04545,#7a1e1e,#4a0f0f)"></div><div class="swatch-name">Italienisch</div><div class="swatch-sub">cuisine-italienisch</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#3a6e3a,#1e4a1e,#0e2e0e)"></div><div class="swatch-name">Asiatisch</div><div class="swatch-sub">cuisine-asiatisch</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#c49010,#8b5e00,#5a3800)"></div><div class="swatch-name">Indisch</div><div class="swatch-sub">cuisine-indisch</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4923a,#8b4e10,#5a2e00)"></div><div class="swatch-name">Mexikanisch</div><div class="swatch-sub">cuisine-mexikanisch</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#4a90b8,#1e5a8b,#0a3456)"></div><div class="swatch-name">Mediterran</div><div class="swatch-sub">cuisine-mediterran</div></div>
</div>
<p>
Die CSS-Klassen (<code>protein-haehnchen</code>, <code>cuisine-asiatisch</code>, …) werden
serverseitig aus den Rezept-Tags abgeleitet und als Svelte-Prop übergeben, z.B.
<code>colorClass="protein-haehnchen"</code>. Das Component setzt die Klasse auf dem Kachel-Wrapper.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">04 · Flip-Mechanik</div>
<h2>CSS 3D Card Flip</h2>
<p>Jede gefüllte Kachel besteht aus drei verschachtelten Elementen:</p>
<pre>.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)</pre>
<h3>Vorderseite</h3>
<ul>
<li>Vollbild-Farbe oder <code>background-image: url(heroImageUrl)</code> mit <code>background-size: cover</code></li>
<li>Dual-Gradient-Overlay als absolutes <code>::after</code>-Pseudo-Element:<br>
<code>linear-gradient(to bottom, rgba(0,0,0,.38) 0%, transparent 28%, transparent 48%, rgba(0,0,0,.62) 100%)</code></li>
<li>Oben links: Tageskürzel (9px uppercase). Oben rechts: Datums-Badge (Kreis)</li>
<li>Unten: Rezeptname (Fraunces 13px), Meta-Zeile (Kochzeit · Aufwand), Tag-Chips</li>
</ul>
<h3>Rückseite</h3>
<ul>
<li><strong>Farbstreifen (5 px)</strong> oben — identischer Gradient wie die Vorderseite. Gibt visuelle Kontinuität.</li>
<li>Tageskürzel + Datum (links) · × Schließen-Button (rechts)</li>
<li>Rezeptname (Fraunces 15px)</li>
<li>Meta: Kochzeit · Aufwand · Portionen</li>
<li>Zutaten-Pills: normale Zutaten als <code>.ingredient</code>, Vorrats-Zutaten (Staples) gedimmt als <code>.ingredient--staple</code></li>
<li>Aktionen (gestapelt, volle Breite):</li>
</ul>
<table>
<thead><tr><th>Aktion</th><th>Stil</th><th>Verhalten</th></tr></thead>
<tbody>
<tr><td>Koch-Modus starten</td><td>Primary (grün ausgefüllt)</td><td>Navigiert zu <code>/planner/cook/[slotId]</code></td></tr>
<tr><td>Rezept ansehen</td><td>Secondary (Rahmen)</td><td>Navigiert zu <code>/recipes/[recipeId]</code></td></tr>
<tr><td>Gericht tauschen</td><td>Secondary (Rahmen)</td><td>Öffnet Rezept-Picker-Drawer (§06)</td></tr>
<tr><td>Entfernen</td><td>Danger (roter Text, transparenter BG)</td><td>Löscht den Slot, Kachel wird leer</td></tr>
</tbody>
</table>
<h3>Interaction Flow</h3>
<ul>
<li>Klick auf <code>.scene</code><code>.card.classList.toggle('flipped')</code></li>
<li>Alle Geschwister-Kacheln im Grid → <code>opacity: 0.38; pointer-events: none</code></li>
<li>× Button auf Rückseite → <code>event.stopPropagation()</code>, <code>classList.remove('flipped')</code>, Geschwister-Opacity zurücksetzen</li>
<li>Escape-Taste → aktive Kachel zurückdrehen</li>
</ul>
<div class="callout green">
<strong>Kein API-Aufruf beim Flip.</strong> Alle dargestellten Daten (Name, Zutaten, Aktionen) sind bereits
im vorhandenen <code>slotMap</code>-State vorhanden. Der Flip ist eine rein visuelle Operation.
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">05 · Leere Kacheln</div>
<h2>Empty Tile — Inline Suggestions</h2>
<p>Leere Kacheln haben denselben <code>height: 100%</code> wie gefüllte Kacheln. Kein Flip.</p>
<pre>┌─────────────────┐
│ 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 → │
└────────────────────────────────┘</pre>
<h3>Vorschlag-Tags (Reasoning)</h3>
<p>Anstelle numerischer Score-Deltas (die für leere Slots immer positiv sind und daher keine Information tragen)
werden Begründungs-Tags angezeigt:</p>
<table>
<thead><tr><th>Tag</th><th>Farbe</th><th>Bedeutung</th></tr></thead>
<tbody>
<tr><td>Neues Protein</td><td>Grün</td><td>Proteinquelle kommt diese Woche noch nicht vor</td></tr>
<tr><td>Kein Overlap</td><td>Grün</td><td>Keine Zutaten-Überschneidung mit anderen Tagen</td></tr>
<tr><td>Aufwand: leicht</td><td>Gelb</td><td>Kochzeit &lt; 30 Min oder Aufwand = einfach</td></tr>
<tr><td>Aufwand: mittel</td><td>Neutral</td><td>Mittlerer Aufwand</td></tr>
</tbody>
</table>
<div class="callout">
<strong>Datenquelle:</strong> Die vorhandene <code>GET /api/suggestions?weekId=&amp;dayOfWeek=</code> API liefert
<code>SuggestionItem { recipe, scoreDelta, hasConflict }</code>. Die Reasoning-Tags werden frontend-seitig
aus den Rezept-Tags und dem vorhandenen <code>slotMap</code> abgeleitet, kein Backend-Änderungsbedarf.
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">06 · Rezept-Picker</div>
<h2>Recipe Picker Drawer</h2>
<p>
Der Rezept-Picker öffnet sich als Slide-in-Drawer von rechts — ausschließlich auf explizite Anfrage.
Er hat keinen persistenten Platz im Layout mehr.
</p>
<h3>Trigger</h3>
<ul>
<li>Klick auf <strong>„Gericht tauschen"</strong> auf der Kachel-Rückseite</li>
<li>Klick auf <strong>„Gericht wählen"</strong> CTA oder Vorschlag-Zeile auf einer leeren Kachel</li>
</ul>
<h3>Drawer-Verhalten</h3>
<ul>
<li>Slide-in von rechts, überlagert den Inhalt (kein Layout-Shift)</li>
<li>Breite: <code>min(480px, 90vw)</code></li>
<li>Backdrop (halbtransparent) schließt den Drawer bei Klick</li>
<li>Nach Auswahl: Drawer schließt sich, Slot wird aktualisiert, Kachel zeigt neues Rezept</li>
</ul>
<div class="callout">
Der bestehende <code>RecipePicker</code>-Komponente (aktuell im rechten Panel) wird in einen
generischen Drawer gewrappt. Der Drawer-Wrapper ist neu; der Picker selbst bleibt unverändert.
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">07 · Mobile</div>
<h2>Mobile — Out of Scope</h2>
<p>
Dieses Spec betrifft ausschließlich die Desktop-Ansicht (<code>≥ 768px</code>).
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.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">08 · Komponenten</div>
<h2>Komponenten-Übersicht</h2>
<div class="component-row">
<span class="comp-file">src/routes/(app)/planner/+page.svelte</span>
<span class="badge badge-mod">Ändern</span>
<span class="comp-action">Rechtes Panel entfernen. Layout auf 2-spaltig (sidebar + main) umstellen. Toolbar-Button entfernen. Grid-Höhe auf 100% setzen.</span>
</div>
<div class="component-row">
<span class="comp-file">src/lib/planner/DayMealCard.svelte</span>
<span class="badge badge-mod">Ersetzen / umbenennen</span>
<span class="comp-action">Zur Flip-Kachel umbauen: .scene → .card → .card-front + .card-back. Farb-Klassen-Prop, Gradient-Overlay, Back-Face mit Aktionen.</span>
</div>
<div class="component-row">
<span class="comp-file">src/lib/planner/EmptyDayTile.svelte</span>
<span class="badge badge-new">Neu</span>
<span class="comp-action">Leere Kachel: + CTA + Inline-Suggestion-Liste mit Reasoning-Tags. Ersetzt den bisherigen leeren Slot-Platzhalter.</span>
</div>
<div class="component-row">
<span class="comp-file">src/lib/planner/RecipePickerDrawer.svelte</span>
<span class="badge badge-new">Neu</span>
<span class="comp-action">Drawer-Wrapper um den bestehenden RecipePicker. Slide-in von rechts, Backdrop, Schließ-Logik.</span>
</div>
<div class="component-row">
<span class="comp-file">src/lib/planner/RecipePicker.svelte</span>
<span class="badge badge-mod">Ändern</span>
<span class="comp-action">Aus dem rechten Panel lösen. Bekommt slotId als Prop. Keine Änderung an der Such-/Auswahl-Logik nötig.</span>
</div>
<div class="component-row">
<span class="comp-file">src/app.css</span>
<span class="badge badge-mod">Ergänzen</span>
<span class="comp-action">14 Farb-Klassen für Protein- und Küchenstil-Gradients hinzufügen (<code>.protein-haehnchen</code>, <code>.cuisine-asiatisch</code>, …).</span>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-label">09 · Accessibility</div>
<h2>A11y-Anforderungen</h2>
<ul>
<li><code>.scene</code>: <code>role="button"</code>, <code>tabindex="0"</code>, <code>aria-expanded="false|true"</code>, <code>aria-label="[Rezeptname] — Details anzeigen"</code></li>
<li><code>.card-back</code>: <code>aria-hidden="true"</code> solange nicht geflippt</li>
<li>× Schließen-Button: <code>aria-label="Schließen"</code>, <code>type="button"</code></li>
<li>Keyboard: <code>Enter</code> / <code>Space</code> flippt, <code>Escape</code> dreht zurück</li>
<li>Dimming: gedimmte Kacheln bekommen <code>aria-hidden="true"</code> wenn eine andere geflippt ist</li>
</ul>
</div>
</div>
</body>
</html>