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>
460 lines
27 KiB
HTML
460 lines
27 KiB
HTML
<!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 & 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ülsenfrü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 < 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=&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>
|