feat(recipes): add image upload, fix save 500, seed HelloFresh data

- Store hero image as base64 data URI in text column (V023 migration)
- Add file upload UI to RecipeForm with FileReader preview
- Remove isChildFriendly from RecipeCreateRequest (no form field)
- Fix 500 on save: effort values now lowercase, serves/cookTimeMin changed
  from primitive short to nullable Integer to survive omitted fields
- Fix empty categories panel: removed stale tagType=category filter
- Group category tags by type with German headings in recipe form
- Split SuggestionResponse.SuggestionRecipe (no image) from SlotRecipe
- Seed 11 HelloFresh recipes with ingredients, steps and tags (V101)
- Add frontend e2e scaffold, specs and dev yml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 20:23:28 +02:00
parent 116e400a91
commit 520dae5adf
34 changed files with 9862 additions and 84 deletions

View File

@@ -0,0 +1,847 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Planner — Tall Tiles</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@300;400&family=DM+Sans:wght@400;500;600&family=DM+Mono&display=swap" rel="stylesheet">
<style>
:root {
--page: #fafaf7;
--surface: #f5f4ee;
--subtle: #edecea;
--border: #d8d7d0;
--text: #1c1c18;
--muted: #6b6a63;
--gt: #e8f5ea; --gl: #aedcb0; --g: #3d8c4a; --gd: #2e6e39;
--yt: #fdf6d8; --yl: #f9e08a; --y: #f2c12e; --yx: #8a6800;
--pt: #eeedfe; --p: #534ab7;
--ot: #fef0e6; --o: #e8862a; --od: #b46820;
--err: #dc4c3e;
--r-sm: 4px; --r-md: 6px; --r-lg: 10px; --r-xl: 16px; --r-full: 9999px;
--sh-card: 0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);
--sh-raised: 0 4px 12px rgba(28,28,24,.10),0 2px 4px rgba(28,28,24,.06);
--fd: 'Fraunces', Georgia, serif;
--fs: 'DM Sans', system-ui, sans-serif;
--fm: 'DM Mono', monospace;
}
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:var(--fs);background:#dddcd7;color:var(--text);padding:40px 24px 80px;line-height:1.4;}
.eyebrow{font-family:var(--fs);font-size:11px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;}
.title{font-family:var(--fd);font-size:34px;font-weight:300;margin-bottom:6px;}
.sub{font-family:var(--fs);font-size:14px;color:var(--muted);max-width:700px;line-height:1.65;margin-bottom:44px;}
.block{margin-bottom:60px;}
.block-label{display:flex;align-items:baseline;gap:10px;margin-bottom:12px;}
.bl-num{font-family:var(--fm);font-size:11px;background:var(--subtle);color:var(--muted);padding:3px 8px;border-radius:var(--r-sm);}
.bl-name{font-family:var(--fd);font-size:22px;font-weight:300;}
.bl-when{font-family:var(--fs);font-size:12px;color:var(--muted);margin-left:auto;}
.note{font-family:var(--fs);font-size:12px;color:var(--muted);border-left:3px solid var(--border);padding:10px 14px;margin-top:14px;line-height:1.6;}
.note strong{color:var(--text);font-weight:500;}
/* Frame */
.frame{display:flex;flex-direction:column;background:var(--page);border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh-raised);}
.tb{display:flex;align-items:center;gap:7px;padding:11px 18px;border-bottom:1px solid var(--border);background:var(--page);flex-shrink:0;}
.tb-h1{font-family:var(--fd);font-size:17px;font-weight:300;}
.tb-range{font-family:var(--fs);font-size:11px;color:var(--muted);}
.tb-arr{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:var(--r-md);font-size:13px;color:var(--muted);}
.tb-btn{height:28px;padding:0 10px;border:1px solid var(--border);border-radius:var(--r-md);font-family:var(--fs);font-size:11px;font-weight:500;letter-spacing:.04em;color:var(--text);background:var(--page);}
.tb-ml{margin-left:auto;}
.tb-pri{background:var(--gd);color:#fff;border:none;}
.body{display:flex;flex:1;overflow:hidden;}
/* Sidebar */
.sb{width:184px;flex-shrink:0;border-right:1px solid var(--border);background:var(--surface);padding:13px;display:flex;flex-direction:column;gap:13px;}
.sb-lbl{font-family:var(--fs);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:5px;}
.score-box{background:var(--yt);border:1px solid var(--yl);border-radius:var(--r-md);padding:10px;}
.sc-big{font-family:var(--fd);font-size:27px;font-weight:300;line-height:1;}
.sc-den{font-family:var(--fs);font-size:11px;color:var(--muted);}
.pbar{height:4px;border-radius:var(--r-full);background:var(--yl);overflow:hidden;margin-top:6px;}
.pb-fill{height:100%;border-radius:var(--r-full);background:var(--y);}
.pbg{background:var(--g);}
.sr{display:flex;align-items:center;gap:6px;margin-top:6px;}
.sr-l{font-family:var(--fs);font-size:10px;color:var(--muted);width:68px;flex-shrink:0;}
.sr-b{flex:1;height:3px;border-radius:var(--r-full);background:var(--border);overflow:hidden;}
.sr-f{height:100%;border-radius:var(--r-full);}
.sr-v{font-family:var(--fm);font-size:9px;color:var(--muted);width:18px;text-align:right;}
.w-item{font-family:var(--fs);font-size:10px;color:var(--yx);margin-top:4px;line-height:1.4;}
.dp{display:flex;gap:2px;margin-top:5px;}
.dp-s{flex:1;height:4px;border-radius:var(--r-full);}
.sb-link{font-family:var(--fs);font-size:10px;font-weight:500;color:var(--yx);display:block;margin-top:8px;}
/* Right panel */
.rp{width:228px;flex-shrink:0;border-left:1px solid var(--border);background:var(--page);padding:13px;display:flex;flex-direction:column;overflow-y:auto;}
.rp-lbl{font-family:var(--fs);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:7px;}
.rp-name{font-family:var(--fd);font-size:16px;font-weight:300;line-height:1.35;}
.rp-meta{font-family:var(--fs);font-size:11px;color:var(--muted);margin-top:3px;}
.rp-btn{display:block;width:100%;padding:7px;border-radius:var(--r-md);border:1px solid var(--border);background:var(--page);font-family:var(--fs);font-size:11px;font-weight:500;letter-spacing:.04em;text-align:center;color:var(--text);margin-top:5px;}
.rp-pri{background:var(--gd);color:#fff;border:none;}
.rp-err{color:var(--err);border-color:var(--err);background:transparent;}
.hr{height:1px;background:var(--border);margin:10px 0;}
.picker-search{display:flex;align-items:center;gap:6px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);padding:6px 9px;margin-bottom:9px;}
.pick-item{display:flex;align-items:center;gap:7px;padding:7px 0;border-bottom:1px solid var(--subtle);cursor:pointer;}
.pick-item:last-child{border-bottom:none;}
.pick-name{font-family:var(--fd);font-size:12px;font-weight:300;flex:1;line-height:1.3;}
.pick-meta{font-family:var(--fs);font-size:10px;color:var(--muted);}
.pick-top{font-family:var(--fm);font-size:9px;background:var(--gt);color:var(--gd);padding:2px 5px;border-radius:2px;}
/* Main */
.main{flex:1;overflow-y:auto;padding:13px;}
/* ── TALL TILE SYSTEM ────────────────────────── */
.grid7{display:grid;grid-template-columns:repeat(7,1fr);gap:7px;}
/* Filled tile */
.tile{
border-radius:var(--r-lg);
border:1px solid var(--border);
background:var(--surface);
cursor:pointer;
overflow:hidden;
box-shadow:var(--sh-card);
display:flex;
flex-direction:column;
transition:box-shadow .12s, border-color .12s;
}
.tile:hover{box-shadow:var(--sh-raised);}
.tile-today{border:2px solid var(--y);}
.tile-sel{border:2px solid var(--g);}
.tile-faded{opacity:.45;}
/* Image area (top of tile) */
.tile-img{
width:100%;
flex-shrink:0;
border-radius:0;
position:relative;
overflow:hidden;
}
/* Overlay gradient so text on image is readable if needed */
.tile-img::after{
content:'';
position:absolute;
inset:0;
background:linear-gradient(to bottom, transparent 40%, rgba(28,28,24,.18) 100%);
}
/* Day header (inside tile, above image or overlaid) */
.tile-head{
display:flex;
align-items:center;
justify-content:space-between;
padding:7px 8px 0;
flex-shrink:0;
}
.tile-day-abbr{font-family:var(--fs);font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);}
.tile-day-num{
width:20px;height:20px;border-radius:var(--r-full);
display:flex;align-items:center;justify-content:center;
font-family:var(--fs);font-size:10px;font-weight:500;
color:var(--text);
}
.dn-today{background:var(--y);color:#fff;}
.dn-sel{background:var(--gd);color:#fff;}
/* Tile body text */
.tile-body{padding:7px 8px 8px;display:flex;flex-direction:column;flex:1;}
.tile-name{font-family:var(--fd);font-size:13px;font-weight:300;line-height:1.35;color:var(--text);}
.tile-meta{font-family:var(--fs);font-size:10px;color:var(--muted);margin-top:3px;}
.tile-tags{display:flex;gap:3px;flex-wrap:wrap;margin-top:6px;}
.tag{font-family:var(--fs);font-size:8px;font-weight:500;padding:2px 5px;border-radius:2px;}
.tag-e{background:var(--gt);color:var(--gd);}
.tag-m{background:var(--yt);color:var(--yx);}
.tag-h{background:var(--ot);color:var(--od);}
.tag-p{background:var(--pt);color:var(--p);}
.tile-serves{font-family:var(--fs);font-size:9px;color:var(--muted);margin-top:auto;padding-top:5px;}
/* Empty tile */
.tile-empty{
border-radius:var(--r-lg);
border:1px dashed var(--border);
background:transparent;
cursor:pointer;
display:flex;
flex-direction:column;
overflow:hidden;
}
.tile-empty-sel{border:2px dashed var(--g);background:rgba(232,245,234,.35);}
.tile-empty-head{
display:flex;align-items:center;justify-content:space-between;
padding:7px 8px 0;flex-shrink:0;
}
.tile-empty-body{
display:flex;flex-direction:column;
align-items:center;justify-content:center;
padding:8px 6px 4px;
gap:3px;
flex-shrink:0;
border-bottom:1px solid var(--border);
margin-bottom:0;
}
.tile-empty-plus{font-size:20px;color:var(--border);}
.tile-empty-label{font-family:var(--fs);font-size:9px;color:var(--muted);}
/* Inline suggestions inside empty tile */
.tile-sug-list{
display:flex;flex-direction:column;
padding:6px 7px;
gap:0;
flex:1;
overflow:hidden;
}
.tile-sug-hd{
font-family:var(--fs);font-size:8px;font-weight:500;
letter-spacing:.07em;text-transform:uppercase;
color:var(--muted);
padding:3px 0 5px;
border-bottom:1px solid var(--subtle);
margin-bottom:3px;
}
.tile-sug-item{
display:flex;align-items:center;gap:5px;
padding:5px 0;
border-bottom:1px solid var(--subtle);
cursor:pointer;
}
.tile-sug-item:last-child{border-bottom:none;}
.tile-sug-item:hover .tile-sug-name{color:var(--gd);}
.tile-sug-name{font-family:var(--fd);font-size:11px;font-weight:300;color:var(--text);flex:1;line-height:1.2;}
.tile-sug-tag{font-family:var(--fs);font-size:8px;font-weight:500;padding:1px 4px;border-radius:2px;white-space:nowrap;flex-shrink:0;}
.ts-green{background:var(--gt);color:var(--gd);}
.ts-yellow{background:var(--yt);color:var(--yx);}
.tile-sug-more{
font-family:var(--fs);font-size:9px;font-weight:500;
color:var(--yx);text-align:center;
padding:5px 0 2px;
margin-top:auto;
}
/* Expansion below grid */
.expand-wrap{position:relative;margin-top:3px;}
.exp-arrows{
display:grid;grid-template-columns:repeat(7,1fr);gap:7px;
position:absolute;top:-8px;left:0;right:0;pointer-events:none;
}
.exp-arr{display:flex;justify-content:center;}
.arr-shape{width:12px;height:12px;border-left:2px solid var(--g);border-top:2px solid var(--g);transform:rotate(45deg);}
.arr-bg-filled{background:var(--gt);}
.arr-bg-empty{background:rgba(232,245,234,.35);}
.expand{
border:2px solid var(--g);border-radius:var(--r-lg);
background:var(--gt);padding:14px;
display:flex;gap:14px;
}
.exp-left{flex:1;}
.exp-ctx{font-family:var(--fs);font-size:10px;font-weight:500;letter-spacing:.07em;text-transform:uppercase;color:var(--gd);margin-bottom:4px;}
.exp-name{font-family:var(--fd);font-size:22px;font-weight:300;line-height:1.25;}
.exp-meta{font-family:var(--fs);font-size:12px;color:var(--muted);margin-top:4px;}
.ing-wrap{display:flex;flex-wrap:wrap;gap:4px;margin-top:10px;}
.ing{font-family:var(--fs);font-size:10px;background:#fff;border:1px solid var(--border);border-radius:var(--r-full);padding:2px 8px;color:var(--text);}
.ing-s{background:var(--subtle);border-color:var(--subtle);color:var(--muted);}
.exp-badges{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;}
.exp-right{display:flex;flex-direction:column;gap:5px;width:116px;flex-shrink:0;}
.exp-btn{padding:7px;border-radius:var(--r-md);border:1px solid var(--border);background:#fff;font-family:var(--fs);font-size:11px;font-weight:500;text-align:center;cursor:pointer;letter-spacing:.04em;}
.exp-pri{background:var(--gd);color:#fff;border:none;}
.exp-err{color:var(--err);border-color:var(--err);background:transparent;}
/* Right panel idle */
.rp-today{background:var(--yt);border:1px solid var(--yl);border-radius:var(--r-md);padding:10px;margin-bottom:10px;}
.rtc-lbl{font-family:var(--fs);font-size:9px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--yx);margin-bottom:4px;}
.rtc-name{font-family:var(--fd);font-size:14px;font-weight:300;line-height:1.3;}
.rtc-meta{font-family:var(--fs);font-size:10px;color:var(--muted);margin-top:2px;}
</style>
</head>
<body>
<p class="eyebrow">Mealplan · Planer · Tall Tiles</p>
<h1 class="title">Hohe Kacheln — drei Zustände</h1>
<p class="sub">
Kein separater Agenda-Bereich. Die Kacheln selbst sind die Informationsschicht:
Bild-Placeholder oben, Rezeptname, Metadaten, Tags. Leere Kacheln nutzen die Höhe
für Vorschläge direkt inline. Unter dem Grid erscheint nur noch die Expansion beim Klick.
</p>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- ZUSTAND 1 — KEIN TAG AUSGEWÄHLT -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="block">
<div class="block-label">
<span class="bl-num">01</span>
<span class="bl-name">Kein Tag ausgewählt</span>
<span class="bl-when">Standard beim Laden der Seite</span>
</div>
<div class="frame" style="height:580px;">
<div class="tb">
<span class="tb-h1">Wochenplaner</span>
<span class="tb-range">7.13. Apr</span>
<div class="tb-arr"></div><div class="tb-arr"></div>
<button class="tb-btn">Heute</button>
<button class="tb-btn tb-ml tb-pri">+ Gericht hinzufügen</button>
</div>
<div class="body">
<!-- Sidebar -->
<div class="sb">
<div class="score-box">
<div class="sb-lbl">Abwechslungs-Score</div>
<div style="display:flex;align-items:baseline;gap:4px;"><span class="sc-big">7.8</span><span class="sc-den">/10</span></div>
<div class="pbar"><div class="pb-fill" style="width:78%;"></div></div>
<div class="sr"><span class="sr-l">Protein</span><div class="sr-b"><div class="sr-f" style="width:80%;background:var(--g);"></div></div><span class="sr-v">8.0</span></div>
<div class="sr"><span class="sr-l">Zutaten</span><div class="sr-b"><div class="sr-f" style="width:72%;background:var(--y);"></div></div><span class="sr-v">7.2</span></div>
<div class="sr"><span class="sr-l">Aufwand</span><div class="sr-b"><div class="sr-f" style="width:82%;background:var(--g);"></div></div><span class="sr-v">8.2</span></div>
<a class="sb-link">Variety-Analyse →</a>
</div>
<div>
<div class="sb-lbl">Überschneidungen</div>
<div class="w-item">⚠ Hähnchen an Mo + Do</div>
<div class="w-item">⚠ Tomaten an Di + Do</div>
</div>
<div>
<div class="sb-lbl">Geplant</div>
<div style="display:flex;align-items:baseline;gap:3px;"><span style="font-family:var(--fd);font-size:20px;font-weight:300;">5</span><span style="font-family:var(--fs);font-size:10px;color:var(--muted);">/ 7 Tage</span></div>
<div class="dp" style="margin-top:5px;">
<div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--border);"></div><div class="dp-s" style="background:var(--border);"></div>
</div>
</div>
</div>
<!-- MAIN -->
<div class="main" style="padding:13px;">
<div class="grid7" style="height:calc(100% - 0px);">
<!-- Mo: Hähnchen-Curry — warm amber image -->
<div class="tile" style="height:100%;">
<div class="tile-head">
<span class="tile-day-abbr">Mo</span>
<span class="tile-day-num">7</span>
</div>
<div class="tile-img" style="height:90px;background:linear-gradient(135deg,#e8c88a 0%,#c9a05a 100%);flex-shrink:0;">
<!-- hero image placeholder — warm/spice tones for curry -->
<div style="position:absolute;inset:0;display:flex;align-items:flex-end;padding:5px 7px;">
</div>
</div>
<div class="tile-body">
<div class="tile-name">Hähnchen-Curry</div>
<div class="tile-meta">35 Min</div>
<div class="tile-tags">
<span class="tag tag-m">mittel</span>
<span class="tag tag-p">Hähnchen</span>
</div>
<div class="tile-serves">4 Portionen</div>
</div>
</div>
<!-- Di: Pasta Bolognese — rich red (TODAY) -->
<div class="tile tile-today" style="height:100%;">
<div class="tile-head">
<span class="tile-day-abbr" style="color:var(--yx);">Di</span>
<span class="tile-day-num dn-today">8</span>
</div>
<div class="tile-img" style="height:90px;background:linear-gradient(135deg,#c96060 0%,#8b2828 100%);flex-shrink:0;">
</div>
<div class="tile-body" style="background:var(--yt);">
<div class="tile-name">Pasta Bolognese</div>
<div class="tile-meta">45 Min</div>
<div class="tile-tags">
<span class="tag tag-m">mittel</span>
<span class="tag tag-p">Rind</span>
</div>
<div class="tile-serves">4 Portionen</div>
</div>
</div>
<!-- Mi: Gemüse-Stir-fry — fresh green -->
<div class="tile" style="height:100%;">
<div class="tile-head">
<span class="tile-day-abbr">Mi</span>
<span class="tile-day-num">9</span>
</div>
<div class="tile-img" style="height:90px;background:linear-gradient(135deg,#7bbf7e 0%,#3d7a42 100%);flex-shrink:0;">
</div>
<div class="tile-body">
<div class="tile-name">Gemüse-Stir-fry</div>
<div class="tile-meta">20 Min</div>
<div class="tile-tags">
<span class="tag tag-e">einfach</span>
<span class="tag tag-p">Tofu</span>
</div>
<div class="tile-serves">2 Portionen</div>
</div>
</div>
<!-- Do: Lachs — blue-teal -->
<div class="tile" style="height:100%;">
<div class="tile-head">
<span class="tile-day-abbr">Do</span>
<span class="tile-day-num">10</span>
</div>
<div class="tile-img" style="height:90px;background:linear-gradient(135deg,#6baed6 0%,#2171b5 100%);flex-shrink:0;">
</div>
<div class="tile-body">
<div class="tile-name">Lachs mit Kartoffeln</div>
<div class="tile-meta">30 Min</div>
<div class="tile-tags">
<span class="tag tag-e">einfach</span>
<span class="tag tag-p">Fisch</span>
</div>
<div class="tile-serves">2 Portionen</div>
</div>
</div>
<!-- Fr: Pizza — warm cream/yellow -->
<div class="tile" style="height:100%;">
<div class="tile-head">
<span class="tile-day-abbr">Fr</span>
<span class="tile-day-num">11</span>
</div>
<div class="tile-img" style="height:90px;background:linear-gradient(135deg,#f0d080 0%,#c8960a 100%);flex-shrink:0;">
</div>
<div class="tile-body">
<div class="tile-name">Pizza Margherita</div>
<div class="tile-meta">50 Min</div>
<div class="tile-tags">
<span class="tag tag-h">aufwändig</span>
<span class="tag tag-p">vegetarisch</span>
</div>
<div class="tile-serves">4 Portionen</div>
</div>
</div>
<!-- Sa: EMPTY — inline suggestions -->
<div class="tile-empty" style="height:100%;">
<div class="tile-empty-head">
<span class="tile-day-abbr">Sa</span>
<span class="tile-day-num" style="color:var(--muted);">12</span>
</div>
<div class="tile-empty-body">
<div class="tile-empty-plus">+</div>
<div class="tile-empty-label">Gericht wählen</div>
</div>
<div class="tile-sug-list">
<div class="tile-sug-hd">Vorschläge</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Ramen mit Ei</span>
<span class="tile-sug-tag ts-green">Neues Protein</span>
</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Shakshuka</span>
<span class="tile-sug-tag ts-green">Kein Overlap</span>
</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Tacos</span>
<span class="tile-sug-tag ts-yellow">Aufwand: leicht</span>
</div>
<div class="tile-sug-more">Alle Rezepte →</div>
</div>
</div>
<!-- So: EMPTY — inline suggestions -->
<div class="tile-empty" style="height:100%;">
<div class="tile-empty-head">
<span class="tile-day-abbr">So</span>
<span class="tile-day-num" style="color:var(--muted);">13</span>
</div>
<div class="tile-empty-body">
<div class="tile-empty-plus">+</div>
<div class="tile-empty-label">Gericht wählen</div>
</div>
<div class="tile-sug-list">
<div class="tile-sug-hd">Vorschläge</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Pho Bo</span>
<span class="tile-sug-tag ts-green">Neues Protein</span>
</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Avocado-Bowl</span>
<span class="tile-sug-tag ts-green">Kein Overlap</span>
</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Kürbissuppe</span>
<span class="tile-sug-tag ts-green">Kein Overlap</span>
</div>
<div class="tile-sug-more">Alle Rezepte →</div>
</div>
</div>
</div><!-- /grid7 -->
</div><!-- /main -->
<!-- Right panel: idle + heute -->
<div class="rp">
<div class="rp-today">
<div class="rtc-lbl">Heute Abend</div>
<div class="rtc-name">Pasta Bolognese</div>
<div class="rtc-meta">Dienstag · 45 Min · mittel</div>
<button class="rp-btn rp-pri" style="margin-top:8px;padding:6px;font-size:11px;">Koch-Modus starten</button>
</div>
<div class="hr"></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:6px;padding:16px;">
<div style="font-family:var(--fs);font-size:12px;color:var(--text);">Tag auswählen</div>
<div style="font-family:var(--fs);font-size:11px;color:var(--muted);max-width:152px;line-height:1.5;">Klicke eine Kachel um Details zu sehen oder ein Gericht zu planen</div>
</div>
</div>
</div>
</div>
<div class="note">
<strong>Kein Agenda-Bereich.</strong> Die Kacheln füllen die volle Höhe des Hauptbereichs.
Geplante Tage zeigen: farbiges Bild-Placeholder (wird durch <code>heroImageUrl</code> ersetzt),
Rezeptname, Kochzeit, Effort-Badge, Protein-Tag, Portionenanzahl.
Leere Kacheln (Sa, So) zeigen direkt 3 Vorschläge mit Begründungs-Tags — kein Scrollen nötig.
Klick auf einen Vorschlag-Eintrag → Rezept wird direkt eingetragen.
Klick auf die Kachel selbst → öffnet Expansion unten (Zustand 3).
</div>
</div>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- ZUSTAND 2 — TAG MIT REZEPT ANGEKLICKT (Mi) -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="block">
<div class="block-label">
<span class="bl-num">02</span>
<span class="bl-name">Tag mit Rezept angeklickt</span>
<span class="bl-when">Klick auf Mi — Gemüse-Stir-fry</span>
</div>
<div class="frame" style="height:700px;">
<div class="tb">
<span class="tb-h1">Wochenplaner</span>
<span class="tb-range">7.13. Apr</span>
<div class="tb-arr"></div><div class="tb-arr"></div>
<button class="tb-btn">Heute</button>
<button class="tb-btn tb-ml tb-pri">+ Gericht hinzufügen</button>
</div>
<div class="body">
<div class="sb">
<div class="score-box">
<div class="sb-lbl">Abwechslungs-Score</div>
<div style="display:flex;align-items:baseline;gap:4px;"><span class="sc-big">7.8</span><span class="sc-den">/10</span></div>
<div class="pbar"><div class="pb-fill" style="width:78%;"></div></div>
<div class="sr"><span class="sr-l">Protein</span><div class="sr-b"><div class="sr-f" style="width:80%;background:var(--g);"></div></div><span class="sr-v">8.0</span></div>
<div class="sr"><span class="sr-l">Zutaten</span><div class="sr-b"><div class="sr-f" style="width:72%;background:var(--y);"></div></div><span class="sr-v">7.2</span></div>
<div class="sr"><span class="sr-l">Aufwand</span><div class="sr-b"><div class="sr-f" style="width:82%;background:var(--g);"></div></div><span class="sr-v">8.2</span></div>
<a class="sb-link">Variety-Analyse →</a>
</div>
<div>
<div class="sb-lbl">Überschneidungen</div>
<div class="w-item">⚠ Hähnchen an Mo + Do</div>
<div class="w-item">⚠ Tomaten an Di + Do</div>
</div>
<div>
<div class="sb-lbl">Geplant</div>
<div style="display:flex;align-items:baseline;gap:3px;"><span style="font-family:var(--fd);font-size:20px;font-weight:300;">5</span><span style="font-family:var(--fs);font-size:10px;color:var(--muted);">/ 7 Tage</span></div>
<div class="dp" style="margin-top:5px;">
<div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--g);"></div><div class="dp-s" style="background:var(--border);"></div><div class="dp-s" style="background:var(--border);"></div>
</div>
</div>
</div>
<div class="main">
<!-- Tiles: Mi selected, others faded -->
<div class="grid7" style="height:320px;">
<div class="tile tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr">Mo</span><span class="tile-day-num">7</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#e8c88a,#c9a05a);flex-shrink:0;"></div>
<div class="tile-body"><div class="tile-name">Hähnchen-Curry</div><div class="tile-meta">35 Min</div></div>
</div>
<div class="tile tile-today tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr" style="color:var(--yx);">Di</span><span class="tile-day-num dn-today">8</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#c96060,#8b2828);flex-shrink:0;"></div>
<div class="tile-body" style="background:var(--yt);"><div class="tile-name">Pasta Bolognese</div><div class="tile-meta">45 Min</div></div>
</div>
<!-- Mi: SELECTED — full opacity, green border -->
<div class="tile tile-sel" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr" style="color:var(--gd);">Mi</span><span class="tile-day-num dn-sel">9</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#7bbf7e,#3d7a42);flex-shrink:0;"></div>
<div class="tile-body" style="background:var(--gt);">
<div class="tile-name">Gemüse-Stir-fry</div>
<div class="tile-meta">20 Min</div>
<div class="tile-tags"><span class="tag tag-e">einfach</span><span class="tag tag-p">Tofu</span></div>
<div style="text-align:center;font-size:9px;color:var(--gd);margin-top:auto;padding-top:4px;"></div>
</div>
</div>
<div class="tile tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr">Do</span><span class="tile-day-num">10</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#6baed6,#2171b5);flex-shrink:0;"></div>
<div class="tile-body"><div class="tile-name">Lachs mit Kartoffeln</div><div class="tile-meta">30 Min</div></div>
</div>
<div class="tile tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr">Fr</span><span class="tile-day-num">11</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#f0d080,#c8960a);flex-shrink:0;"></div>
<div class="tile-body"><div class="tile-name">Pizza Margherita</div><div class="tile-meta">50 Min</div></div>
</div>
<div class="tile-empty tile-faded" style="height:100%;opacity:.3;">
<div class="tile-empty-head"><span class="tile-day-abbr">Sa</span><span class="tile-day-num">12</span></div>
<div class="tile-empty-body"><div class="tile-empty-plus">+</div></div>
</div>
<div class="tile-empty tile-faded" style="height:100%;opacity:.3;">
<div class="tile-empty-head"><span class="tile-day-abbr">So</span><span class="tile-day-num">13</span></div>
<div class="tile-empty-body"><div class="tile-empty-plus">+</div></div>
</div>
</div>
<!-- EXPANSION below grid -->
<div class="expand-wrap">
<div class="exp-arrows">
<div></div><div></div>
<div class="exp-arr"><div class="arr-shape arr-bg-filled"></div></div>
<div></div><div></div><div></div><div></div>
</div>
<div class="expand">
<div class="exp-left">
<div class="exp-ctx">Mittwoch, 9. Apr · Abendessen</div>
<div class="exp-name">Gemüse-Stir-fry</div>
<div class="exp-meta">20 Min · einfach · 2 Portionen</div>
<div class="ing-wrap">
<span class="ing">Tofu</span>
<span class="ing">Paprika</span>
<span class="ing">Brokkoli</span>
<span class="ing">Karotten</span>
<span class="ing">Zucchini</span>
<span class="ing">Ingwer</span>
<span class="ing-s">Sesamöl</span>
<span class="ing-s">Sojasauce</span>
<span class="ing-s">Knoblauch</span>
<span class="ing-s">Salz, Pfeffer</span>
</div>
<div class="exp-badges">
<span class="tag tag-e" style="font-size:10px;padding:3px 7px;">einfach</span>
<span style="font-family:var(--fs);font-size:10px;font-weight:500;padding:3px 7px;border-radius:2px;background:var(--pt);color:var(--p);">Protein: Tofu</span>
<span style="font-family:var(--fs);font-size:10px;font-weight:500;padding:3px 7px;border-radius:2px;background:var(--gt);color:var(--gd);">Score ▲ +0.4</span>
</div>
</div>
<div class="exp-right">
<button class="exp-btn exp-pri">Koch-Modus</button>
<button class="exp-btn">Rezept ansehen</button>
<button class="exp-btn">Gericht tauschen</button>
<button class="exp-btn exp-err">Entfernen</button>
</div>
</div>
</div>
</div>
<!-- Right: score context -->
<div class="rp">
<div class="rp-lbl">Mittwoch, 9. Apr</div>
<div style="font-family:var(--fs);font-size:11px;color:var(--muted);margin-bottom:10px;">Wie wirkt dieses Gericht auf die Woche?</div>
<div style="display:flex;align-items:baseline;gap:4px;margin-bottom:4px;">
<span style="font-family:var(--fd);font-size:22px;font-weight:300;">7.8</span>
<span style="font-family:var(--fs);font-size:11px;color:var(--muted);">/10</span>
<span style="font-family:var(--fs);font-size:11px;color:var(--gd);font-weight:500;margin-left:6px;">▲ +0.4</span>
</div>
<div class="pbar" style="background:var(--border);margin-bottom:12px;"><div class="pb-fill pbg" style="width:78%;"></div></div>
<div class="hr"></div>
<div style="font-family:var(--fs);font-size:10px;font-weight:500;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);margin-bottom:8px;">Bewertung</div>
<div style="display:flex;flex-direction:column;gap:6px;">
<div style="display:flex;align-items:center;gap:6px;font-family:var(--fs);font-size:11px;color:var(--gd);">✓ Kein Protein-Overlap</div>
<div style="display:flex;align-items:center;gap:6px;font-family:var(--fs);font-size:11px;color:var(--gd);">✓ Neue Zutaten</div>
<div style="display:flex;align-items:center;gap:6px;font-family:var(--fs);font-size:11px;color:var(--yx);">~ Tofu zum 2. Mal</div>
</div>
</div>
</div>
</div>
<div class="note">
<strong>Nach Klick auf Mittwoch:</strong> Nicht-ausgewählte Kacheln werden auf 45% Deckkraft gedimmt.
Die Expansion erscheint direkt unter dem Grid (Pfeil zeigt zu Mi).
Die Kacheln bleiben auf ihrer Höhe — der Expansion-Bereich wächst <em>zusätzlich</em> darunter.
Zutaten zeigen normale Zutaten als Pills; Grundzutaten (Sesamöl, Sojasauce…) gedimmt.
</div>
</div>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- ZUSTAND 3 — LEERER TAG ANGEKLICKT (Sa) -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="block">
<div class="block-label">
<span class="bl-num">03</span>
<span class="bl-name">Leerer Tag angeklickt</span>
<span class="bl-when">Klick auf Sa — kein Gericht geplant</span>
</div>
<div class="frame" style="height:700px;">
<div class="tb">
<span class="tb-h1">Wochenplaner</span>
<span class="tb-range">7.13. Apr</span>
<div class="tb-arr"></div><div class="tb-arr"></div>
<button class="tb-btn">Heute</button>
<button class="tb-btn tb-ml tb-pri">+ Gericht hinzufügen</button>
</div>
<div class="body">
<div class="sb">
<div class="score-box">
<div class="sb-lbl">Abwechslungs-Score</div>
<div style="display:flex;align-items:baseline;gap:4px;"><span class="sc-big">7.8</span><span class="sc-den">/10</span></div>
<div class="pbar"><div class="pb-fill" style="width:78%;"></div></div>
<div class="sr"><span class="sr-l">Protein</span><div class="sr-b"><div class="sr-f" style="width:80%;background:var(--g);"></div></div><span class="sr-v">8.0</span></div>
<div class="sr"><span class="sr-l">Zutaten</span><div class="sr-b"><div class="sr-f" style="width:72%;background:var(--y);"></div></div><span class="sr-v">7.2</span></div>
<div class="sr"><span class="sr-l">Aufwand</span><div class="sr-b"><div class="sr-f" style="width:82%;background:var(--g);"></div></div><span class="sr-v">8.2</span></div>
<a class="sb-link">Variety-Analyse →</a>
</div>
<div>
<div class="sb-lbl">Überschneidungen</div>
<div class="w-item">⚠ Hähnchen an Mo + Do</div>
<div class="w-item">⚠ Tomaten an Di + Do</div>
</div>
</div>
<div class="main">
<div class="grid7" style="height:320px;">
<div class="tile tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr">Mo</span><span class="tile-day-num">7</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#e8c88a,#c9a05a);flex-shrink:0;"></div>
<div class="tile-body"><div class="tile-name">Hähnchen-Curry</div><div class="tile-meta">35 Min</div></div>
</div>
<div class="tile tile-today tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr" style="color:var(--yx);">Di</span><span class="tile-day-num dn-today">8</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#c96060,#8b2828);flex-shrink:0;"></div>
<div class="tile-body" style="background:var(--yt);"><div class="tile-name">Pasta Bolognese</div><div class="tile-meta">45 Min</div></div>
</div>
<div class="tile tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr">Mi</span><span class="tile-day-num">9</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#7bbf7e,#3d7a42);flex-shrink:0;"></div>
<div class="tile-body"><div class="tile-name">Gemüse-Stir-fry</div><div class="tile-meta">20 Min</div></div>
</div>
<div class="tile tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr">Do</span><span class="tile-day-num">10</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#6baed6,#2171b5);flex-shrink:0;"></div>
<div class="tile-body"><div class="tile-name">Lachs mit Kartoffeln</div><div class="tile-meta">30 Min</div></div>
</div>
<div class="tile tile-faded" style="height:100%;">
<div class="tile-head"><span class="tile-day-abbr">Fr</span><span class="tile-day-num">11</span></div>
<div class="tile-img" style="height:80px;background:linear-gradient(135deg,#f0d080,#c8960a);flex-shrink:0;"></div>
<div class="tile-body"><div class="tile-name">Pizza Margherita</div><div class="tile-meta">50 Min</div></div>
</div>
<!-- Sa: SELECTED EMPTY -->
<div class="tile-empty tile-empty-sel" style="height:100%;">
<div class="tile-empty-head">
<span class="tile-day-abbr" style="color:var(--gd);">Sa</span>
<span class="tile-day-num dn-sel">12</span>
</div>
<div class="tile-empty-body" style="border-bottom-color:var(--gl);">
<div class="tile-empty-plus" style="color:var(--gl);">+</div>
<div class="tile-empty-label" style="color:var(--gd);">Gericht wählen</div>
<div style="text-align:center;font-size:9px;color:var(--gd);margin-top:2px;"></div>
</div>
<div class="tile-sug-list">
<div class="tile-sug-hd">Vorschläge</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Ramen mit Ei</span>
<span class="tile-sug-tag ts-green">Neues Protein</span>
</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Shakshuka</span>
<span class="tile-sug-tag ts-green">Kein Overlap</span>
</div>
<div class="tile-sug-item">
<span class="tile-sug-name">Tacos</span>
<span class="tile-sug-tag ts-yellow">Aufwand: leicht</span>
</div>
<div class="tile-sug-more">Alle Rezepte →</div>
</div>
</div>
<div class="tile-empty tile-faded" style="height:100%;opacity:.3;">
<div class="tile-empty-head"><span class="tile-day-abbr">So</span><span class="tile-day-num">13</span></div>
<div class="tile-empty-body"><div class="tile-empty-plus">+</div></div>
</div>
</div>
<!-- EXPANSION: suggestions full width -->
<div class="expand-wrap">
<div class="exp-arrows">
<div></div><div></div><div></div><div></div><div></div>
<div class="exp-arr"><div class="arr-shape arr-bg-empty"></div></div>
<div></div>
</div>
<div class="expand" style="flex-direction:column;gap:10px;">
<div style="font-family:var(--fs);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--gd);">Samstag, 12. Apr — Alle Vorschläge</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:7px;">
<div style="border:1px solid var(--gl);border-radius:var(--r-md);background:#fff;padding:10px;cursor:pointer;">
<div style="font-family:var(--fd);font-size:13px;font-weight:300;line-height:1.3;">Ramen mit Ei</div>
<div style="font-family:var(--fs);font-size:10px;color:var(--muted);margin-top:3px;">40 Min · mittel</div>
<span style="font-family:var(--fs);font-size:9px;font-weight:500;padding:2px 6px;border-radius:2px;background:var(--gt);color:var(--gd);margin-top:6px;display:inline-block;">Neues Protein</span>
</div>
<div style="border:1px solid var(--gl);border-radius:var(--r-md);background:#fff;padding:10px;cursor:pointer;">
<div style="font-family:var(--fd);font-size:13px;font-weight:300;line-height:1.3;">Shakshuka</div>
<div style="font-family:var(--fs);font-size:10px;color:var(--muted);margin-top:3px;">25 Min · einfach</div>
<span style="font-family:var(--fs);font-size:9px;font-weight:500;padding:2px 6px;border-radius:2px;background:var(--gt);color:var(--gd);margin-top:6px;display:inline-block;">Kein Overlap</span>
</div>
<div style="border:1px solid var(--gl);border-radius:var(--r-md);background:#fff;padding:10px;cursor:pointer;">
<div style="font-family:var(--fd);font-size:13px;font-weight:300;line-height:1.3;">Rindfleisch-Tacos</div>
<div style="font-family:var(--fs);font-size:10px;color:var(--muted);margin-top:3px;">30 Min · einfach</div>
<span style="font-family:var(--fs);font-size:9px;font-weight:500;padding:2px 6px;border-radius:2px;background:var(--yt);color:var(--yx);margin-top:6px;display:inline-block;">Gleiche Zutaten</span>
</div>
<div style="border:1px solid var(--gl);border-radius:var(--r-md);background:#fff;padding:10px;cursor:pointer;">
<div style="font-family:var(--fd);font-size:13px;font-weight:300;line-height:1.3;">Kürbissuppe</div>
<div style="font-family:var(--fs);font-size:10px;color:var(--muted);margin-top:3px;">35 Min · einfach</div>
<span style="font-family:var(--fs);font-size:9px;font-weight:500;padding:2px 6px;border-radius:2px;background:var(--gt);color:var(--gd);margin-top:6px;display:inline-block;">Kein Overlap</span>
</div>
</div>
</div>
</div>
</div>
<!-- Right: full recipe picker -->
<div class="rp">
<div class="rp-lbl">Samstag, 12. Apr</div>
<div class="picker-search">
<span style="font-size:12px;color:var(--muted);"></span>
<span style="font-family:var(--fs);font-size:11px;color:var(--muted);">Rezept suchen…</span>
</div>
<div style="overflow-y:auto;flex:1;">
<div class="pick-item"><div style="flex:1;"><div class="pick-name">Ramen mit Ei</div><div class="pick-meta">40 Min · mittel</div></div><span class="pick-top">Top</span></div>
<div class="pick-item"><div style="flex:1;"><div class="pick-name">Shakshuka</div><div class="pick-meta">25 Min · einfach</div></div><span class="pick-top">Top</span></div>
<div class="pick-item"><div style="flex:1;"><div class="pick-name">Kürbissuppe</div><div class="pick-meta">35 Min · einfach</div></div></div>
<div class="pick-item"><div style="flex:1;"><div class="pick-name">Tofu-Teriyaki</div><div class="pick-meta">30 Min · einfach</div></div></div>
<div class="pick-item"><div style="flex:1;"><div class="pick-name">Gemüse-Curry</div><div class="pick-meta">40 Min · mittel</div></div></div>
<div class="pick-item"><div style="flex:1;"><div class="pick-name">Linseneintopf</div><div class="pick-meta">50 Min · einfach</div></div></div>
<div class="pick-item"><div style="flex:1;"><div class="pick-name">Ofen-Lachs</div><div class="pick-meta">35 Min · einfach</div></div></div>
</div>
</div>
</div>
</div>
<div class="note">
<strong>Nach Klick auf leeren Samstag:</strong> Die Kachel selbst zeigt schon die 3 Inline-Vorschläge (sichtbar seit Zustand 1).
Der Pfeil-Indikator erscheint, die Expansion zeigt alle 4 Vorschläge nebeneinander als klickbare Karten.
Das rechte Panel öffnet gleichzeitig den vollständigen Rezept-Picker mit Suche — für alle anderen Optionen.
Klick auf eine Karte (main) oder Picker-Eintrag (rechts) trägt das Rezept ein und schließt die Expansion.
</div>
</div>
</body>
</html>