Files
mealprep/specs/planner-fullbleed-tiles.html
Marcel Raddatz 520dae5adf 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>
2026-04-09 20:23:28 +02:00

774 lines
38 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Planner — Full-Bleed 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; --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 6px 18px rgba(28,28,24,.14),0 2px 6px rgba(28,28,24,.08);
--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:56px;}
.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;}
/* ── Desktop 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);overflow:hidden;margin-top:6px;}
.pb-y{background:var(--yl);} .pb-t{background:var(--border);}
.pb-fill{height:100%;border-radius:var(--r-full);}
.pb-fg-y{background:var(--y);} .pb-fg-g{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-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:12px;}
/* ════════════════════════════════════════════
FULL-BLEED TILE SYSTEM
════════════════════════════════════════════ */
.grid7{display:grid;grid-template-columns:repeat(7,1fr);gap:7px;}
/* The tile is a positioned container — image is the background */
.tile{
position:relative;
border-radius:var(--r-lg);
overflow:hidden;
cursor:pointer;
box-shadow:var(--sh-card);
transition:box-shadow .15s, transform .1s;
/* Image fills entire tile */
background-size:cover;
background-position:center;
}
.tile:hover{box-shadow:var(--sh-raised);transform:translateY(-1px);}
/* State borders — use box-shadow to avoid layout shift with border:2px */
.tile-default{}
.tile-today{box-shadow:0 0 0 2px var(--y), var(--sh-card);}
.tile-today:hover{box-shadow:0 0 0 2px var(--y), var(--sh-raised);}
.tile-sel{box-shadow:0 0 0 2px var(--g), var(--sh-raised);}
.tile-faded{opacity:.42;pointer-events:none;}
/* Full-height gradient overlay: dark at bottom for text, subtle at top for day label */
.tile-overlay{
position:absolute;inset:0;
background:
linear-gradient(to bottom,
rgba(0,0,0,.32) 0%,
rgba(0,0,0,0) 30%,
rgba(0,0,0,0) 45%,
rgba(0,0,0,.55) 100%
);
border-radius:inherit;
}
/* Day header — top of tile, over image */
.tile-head{
position:absolute;top:0;left:0;right:0;
display:flex;align-items:center;justify-content:space-between;
padding:7px 8px;
z-index:2;
}
.tile-day-abbr{
font-family:var(--fs);font-size:9px;text-transform:uppercase;
letter-spacing:.06em;color:rgba(255,255,255,.85);font-weight:500;
}
.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:rgba(255,255,255,.9);
background:rgba(255,255,255,.22);
}
.dn-today{background:var(--y) !important;color:#fff !important;}
.dn-sel{background:var(--g) !important;color:#fff !important;}
/* Recipe info — bottom of tile, over image */
.tile-info{
position:absolute;bottom:0;left:0;right:0;
padding:8px 9px 9px;
z-index:2;
}
.tile-name{
font-family:var(--fd);font-size:13px;font-weight:300;
color:#fff;line-height:1.3;
text-shadow:0 1px 3px rgba(0,0,0,.4);
}
.tile-meta{
font-family:var(--fs);font-size:10px;
color:rgba(255,255,255,.75);
margin-top:2px;
}
.tile-tags{display:flex;gap:3px;flex-wrap:wrap;margin-top:5px;}
.tag{
font-family:var(--fs);font-size:8px;font-weight:500;
padding:2px 5px;border-radius:2px;
background:rgba(255,255,255,.2);
color:rgba(255,255,255,.92);
backdrop-filter:blur(2px);
}
/* State-specific tag tints */
.tag-today{background:rgba(242,193,46,.35);}
.tag-sel{background:rgba(61,140,74,.45);}
/* ── EMPTY TILE ────────────────────────────── */
/* Empty tile: no image, dashed border, suggestion list fills height */
.tile-empty{
border-radius:var(--r-lg);
border:1.5px dashed var(--border);
background:var(--surface);
cursor:pointer;
display:flex;flex-direction:column;
overflow:hidden;
box-shadow:var(--sh-card);
}
.tile-empty-sel{
border:2px dashed var(--g);
background:rgba(232,245,234,.5);
}
.tile-empty-faded{opacity:.3;pointer-events:none;}
.empty-head{
display:flex;align-items:center;justify-content:space-between;
padding:7px 8px 0;flex-shrink:0;
}
.empty-day-abbr{font-family:var(--fs);font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);}
.empty-day-num{font-family:var(--fs);font-size:10px;font-weight:500;color:var(--muted);}
.empty-cta{
display:flex;flex-direction:column;align-items:center;justify-content:center;
padding:8px 6px 6px;
gap:2px;flex-shrink:0;
border-bottom:1px solid var(--border);
}
.empty-plus{font-size:18px;color:var(--border);}
.empty-label{font-family:var(--fs);font-size:9px;color:var(--muted);}
.empty-sel-label{color:var(--gd);}
.empty-arr{font-size:9px;color:var(--gd);margin-top:1px;}
/* Inline suggestion list */
.sug-list{
display:flex;flex-direction:column;
padding:5px 7px 6px;flex:1;overflow:hidden;
}
.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:2px;
}
.sug-row{
display:flex;align-items:center;gap:4px;
padding:5px 0;border-bottom:1px solid var(--subtle);
cursor:pointer;
}
.sug-row:last-of-type{border-bottom:none;}
.sug-row:hover .sug-name{color:var(--gd);}
.sug-name{font-family:var(--fd);font-size:11px;font-weight:300;color:var(--text);flex:1;line-height:1.2;}
.sug-tag{
font-family:var(--fs);font-size:8px;font-weight:500;
padding:1px 4px;border-radius:2px;white-space:nowrap;flex-shrink:0;
}
.st-g{background:var(--gt);color:var(--gd);}
.st-y{background:var(--yt);color:var(--yx);}
.sug-more{
font-family:var(--fs);font-size:9px;font-weight:500;
color:var(--yx);text-align:center;padding-top:5px;margin-top:auto;
}
/* ── EXPANSION ─────────────────────────────── */
.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{width:12px;height:12px;border-left:2px solid var(--g);border-top:2px solid var(--g);transform:rotate(45deg);}
.arr-fill{background:var(--gt);}
.arr-empty{background:rgba(232,245,234,.5);}
.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-bdgs{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;}
.ebdg{font-family:var(--fs);font-size:10px;font-weight:500;padding:3px 7px;border-radius:2px;}
.ebdg-e{background:var(--gt);color:var(--gd);}
.ebdg-p{background:var(--pt);color:var(--p);}
.ebdg-sc{background:var(--gt);color:var(--gd);}
.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 today card */
.today-card{background:var(--yt);border:1px solid var(--yl);border-radius:var(--r-md);padding:10px;margin-bottom:10px;}
.tc-lbl{font-family:var(--fs);font-size:9px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--yx);margin-bottom:4px;}
.tc-name{font-family:var(--fd);font-size:14px;font-weight:300;line-height:1.3;}
.tc-meta{font-family:var(--fs);font-size:10px;color:var(--muted);margin-top:2px;}
/* ── CLOSE-UP SPECIMENS ─────────────────────── */
.specimen-row{display:flex;gap:14px;margin-bottom:32px;flex-wrap:wrap;}
.specimen{flex-shrink:0;}
.specimen-label{font-family:var(--fs);font-size:10px;font-weight:500;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;text-align:center;}
/* Image colour palettes for each recipe (placeholder for heroImageUrl) */
.img-curry{background:linear-gradient(160deg,#d4923a 0%,#a85e1a 40%,#7a3d0c 100%);}
.img-pasta{background:linear-gradient(160deg,#c04545 0%,#8b2020 40%,#5a1010 100%);}
.img-stirfry{background:linear-gradient(160deg,#5fa85e 0%,#2e7031 40%,#1a4a1e 100%);}
.img-lachs{background:linear-gradient(160deg,#5b9fd4 0%,#2868a0 40%,#10406e 100%);}
.img-pizza{background:linear-gradient(160deg,#d4a832 0%,#a07010 40%,#6e4a00 100%);}
</style>
</head>
<body>
<p class="eyebrow">Mealplan · Planer · Full-Bleed Tiles</p>
<h1 class="title">Vollflächige Kacheln</h1>
<p class="sub">
Das Bild füllt die gesamte Kachelhöhe. Text und Tags werden über einen Gradienten am unteren Rand eingeblendet.
Kein leerer Bereich mehr — jeder Pixel der Kachel ist Bild.
Leere Kacheln behalten die Vorschlagsliste von innen.
</p>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- CLOSE-UP: Tile variants -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="block">
<div class="block-label">
<span class="bl-num">Nahaufnahme</span>
<span class="bl-name">Kachel-Varianten</span>
</div>
<div class="specimen-row">
<!-- Filled: default -->
<div class="specimen">
<div class="specimen-label">Gefüllt — Standard</div>
<div class="tile img-curry" style="width:140px;height:220px;">
<div class="tile-overlay"></div>
<div class="tile-head">
<span class="tile-day-abbr">Mo</span>
<span class="tile-day-num">7</span>
</div>
<div class="tile-info">
<div class="tile-name">Hähnchen-Curry</div>
<div class="tile-meta">35 Min · mittel</div>
<div class="tile-tags">
<span class="tag">Hähnchen</span>
<span class="tag">4 Port.</span>
</div>
</div>
</div>
</div>
<!-- Filled: today -->
<div class="specimen">
<div class="specimen-label">Gefüllt — Heute</div>
<div class="tile tile-today img-pasta" style="width:140px;height:220px;">
<div class="tile-overlay"></div>
<div class="tile-head">
<span class="tile-day-abbr">Di</span>
<span class="tile-day-num dn-today">8</span>
</div>
<div class="tile-info">
<div class="tile-name">Pasta Bolognese</div>
<div class="tile-meta">45 Min · mittel</div>
<div class="tile-tags">
<span class="tag tag-today">Rind</span>
<span class="tag tag-today">4 Port.</span>
</div>
</div>
</div>
</div>
<!-- Filled: selected -->
<div class="specimen">
<div class="specimen-label">Gefüllt — Ausgewählt</div>
<div class="tile tile-sel img-stirfry" style="width:140px;height:220px;">
<div class="tile-overlay"></div>
<div class="tile-head">
<span class="tile-day-abbr">Mi</span>
<span class="tile-day-num dn-sel">9</span>
</div>
<div class="tile-info">
<div class="tile-name">Gemüse-Stir-fry</div>
<div class="tile-meta">20 Min · einfach</div>
<div class="tile-tags">
<span class="tag tag-sel">Tofu</span>
<span class="tag tag-sel">2 Port.</span>
</div>
<div style="text-align:center;font-size:9px;color:rgba(255,255,255,.7);margin-top:4px;"></div>
</div>
</div>
</div>
<!-- Filled: faded -->
<div class="specimen">
<div class="specimen-label">Gefüllt — Gedimmt</div>
<div class="tile tile-faded img-lachs" style="width:140px;height:220px;pointer-events:auto;">
<div class="tile-overlay"></div>
<div class="tile-head">
<span class="tile-day-abbr">Do</span>
<span class="tile-day-num">10</span>
</div>
<div class="tile-info">
<div class="tile-name">Lachs mit Kartoffeln</div>
<div class="tile-meta">30 Min · einfach</div>
<div class="tile-tags"><span class="tag">Fisch</span></div>
</div>
</div>
</div>
<!-- Empty with suggestions -->
<div class="specimen">
<div class="specimen-label">Leer — Vorschläge</div>
<div class="tile-empty" style="width:140px;height:220px;">
<div class="empty-head">
<span class="empty-day-abbr">Sa</span>
<span class="empty-day-num">12</span>
</div>
<div class="empty-cta">
<div class="empty-plus">+</div>
<div class="empty-label">Gericht wählen</div>
</div>
<div class="sug-list">
<div class="sug-hd">Vorschläge</div>
<div class="sug-row">
<span class="sug-name">Ramen mit Ei</span>
<span class="sug-tag st-g">Neu</span>
</div>
<div class="sug-row">
<span class="sug-name">Shakshuka</span>
<span class="sug-tag st-g">Kein Overlap</span>
</div>
<div class="sug-row">
<span class="sug-name">Tacos</span>
<span class="sug-tag st-y">Leicht</span>
</div>
<div class="sug-more">Alle →</div>
</div>
</div>
</div>
<!-- Empty selected -->
<div class="specimen">
<div class="specimen-label">Leer — Ausgewählt</div>
<div class="tile-empty tile-empty-sel" style="width:140px;height:220px;">
<div class="empty-head">
<span class="empty-day-abbr" style="color:var(--gd);">Sa</span>
<span class="empty-day-num" style="color:var(--gd);">12</span>
</div>
<div class="empty-cta" style="border-bottom-color:var(--gl);">
<div class="empty-plus" style="color:var(--gl);">+</div>
<div class="empty-label empty-sel-label">Gericht wählen</div>
<div class="empty-arr"></div>
</div>
<div class="sug-list">
<div class="sug-hd">Vorschläge</div>
<div class="sug-row">
<span class="sug-name">Ramen mit Ei</span>
<span class="sug-tag st-g">Neu</span>
</div>
<div class="sug-row">
<span class="sug-name">Shakshuka</span>
<span class="sug-tag st-g">Kein Overlap</span>
</div>
<div class="sug-row">
<span class="sug-name">Tacos</span>
<span class="sug-tag st-y">Leicht</span>
</div>
<div class="sug-more">Alle →</div>
</div>
</div>
</div>
</div>
<div class="note">
<strong>Gradient-Overlay:</strong> Dunkel oben (30% → 0%) für den Tages-Header, dunkel unten (0% → 55%) für den Text.
Der mittlere Bereich ist transparent — das Bild ist klar zu sehen.
Text-Shadow auf dem Rezeptnamen sichert Lesbarkeit auch bei hellen Bildern.
Tags nutzen <code>rgba(255,255,255,.2)</code> als Glasmorphismus-Hintergrund.
Zustands-Borders werden per <code>box-shadow</code> umgesetzt (kein Layout-Shift durch border:2px).
</div>
</div>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- ZUSTAND 1 — VOLLSTÄNDIGE SEITENANSICHT -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="block">
<div class="block-label">
<span class="bl-num">01</span>
<span class="bl-name">Kein Tag ausgewählt — volle Seite</span>
<span class="bl-when">Kacheln füllen die Viewport-Höhe komplett</span>
</div>
<div class="frame" style="height:560px;">
<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 pb-y"><div class="pb-fill pb-fg-y" 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" style="padding:12px;">
<!-- Grid fills full height of main -->
<div class="grid7" style="height:100%;">
<div class="tile img-curry" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Mo</span><span class="tile-day-num">7</span></div>
<div class="tile-info">
<div class="tile-name">Hähnchen-Curry</div>
<div class="tile-meta">35 Min · mittel</div>
<div class="tile-tags"><span class="tag">Hähnchen</span><span class="tag">4 Port.</span></div>
</div>
</div>
<div class="tile tile-today img-pasta" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Di</span><span class="tile-day-num dn-today">8</span></div>
<div class="tile-info">
<div class="tile-name">Pasta Bolognese</div>
<div class="tile-meta">45 Min · mittel</div>
<div class="tile-tags"><span class="tag tag-today">Rind</span><span class="tag tag-today">Heute</span></div>
</div>
</div>
<div class="tile img-stirfry" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Mi</span><span class="tile-day-num">9</span></div>
<div class="tile-info">
<div class="tile-name">Gemüse-Stir-fry</div>
<div class="tile-meta">20 Min · einfach</div>
<div class="tile-tags"><span class="tag">Tofu</span><span class="tag">2 Port.</span></div>
</div>
</div>
<div class="tile img-lachs" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Do</span><span class="tile-day-num">10</span></div>
<div class="tile-info">
<div class="tile-name">Lachs mit Kartoffeln</div>
<div class="tile-meta">30 Min · einfach</div>
<div class="tile-tags"><span class="tag">Fisch</span><span class="tag">2 Port.</span></div>
</div>
</div>
<div class="tile img-pizza" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Fr</span><span class="tile-day-num">11</span></div>
<div class="tile-info">
<div class="tile-name">Pizza Margherita</div>
<div class="tile-meta">50 Min · aufwändig</div>
<div class="tile-tags"><span class="tag">vegetarisch</span><span class="tag">4 Port.</span></div>
</div>
</div>
<div class="tile-empty" style="height:100%;">
<div class="empty-head"><span class="empty-day-abbr">Sa</span><span class="empty-day-num">12</span></div>
<div class="empty-cta">
<div class="empty-plus">+</div>
<div class="empty-label">Gericht wählen</div>
</div>
<div class="sug-list">
<div class="sug-hd">Vorschläge</div>
<div class="sug-row"><span class="sug-name">Ramen mit Ei</span><span class="sug-tag st-g">Neues Protein</span></div>
<div class="sug-row"><span class="sug-name">Shakshuka</span><span class="sug-tag st-g">Kein Overlap</span></div>
<div class="sug-row"><span class="sug-name">Tacos</span><span class="sug-tag st-y">Aufwand: leicht</span></div>
<div class="sug-more">Alle Rezepte →</div>
</div>
</div>
<div class="tile-empty" style="height:100%;">
<div class="empty-head"><span class="empty-day-abbr">So</span><span class="empty-day-num">13</span></div>
<div class="empty-cta">
<div class="empty-plus">+</div>
<div class="empty-label">Gericht wählen</div>
</div>
<div class="sug-list">
<div class="sug-hd">Vorschläge</div>
<div class="sug-row"><span class="sug-name">Pho Bo</span><span class="sug-tag st-g">Neues Protein</span></div>
<div class="sug-row"><span class="sug-name">Avocado-Bowl</span><span class="sug-tag st-g">Kein Overlap</span></div>
<div class="sug-row"><span class="sug-name">Kürbissuppe</span><span class="sug-tag st-g">Kein Overlap</span></div>
<div class="sug-more">Alle Rezepte →</div>
</div>
</div>
</div>
</div>
<div class="rp">
<div class="today-card">
<div class="tc-lbl">Heute Abend</div>
<div class="tc-name">Pasta Bolognese</div>
<div class="tc-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 leerer Bereich.</strong> Das Grid nimmt die volle Höhe ein (<code>height:100%</code> auf Kacheln und Grid).
Gefüllte Kacheln: Bild von oben bis unten, Text per Overlay am unteren Rand.
Leere Kacheln: dieselbe Höhe, oben "+ Gericht wählen", darunter die Vorschlagsliste.
Da MoFr alle Bilder haben, entsteht ein visuell abwechslungsreicher Kalender ohne Blank Space.
</div>
</div>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- ZUSTAND 2 — REZEPT AUSGEWÄHLT (Mi), EXPANSION UNTEN -->
<!-- ══════════════════════════════════════════════════════════════ -->
<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">Mi — Expansion öffnet sich unter dem Grid</span>
</div>
<div class="frame" style="height:680px;">
<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 pb-y"><div class="pb-fill pb-fg-y" 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>
</div>
<div class="main">
<!-- Tiles: shorter when expansion open -->
<div class="grid7" style="height:290px;">
<div class="tile tile-faded img-curry" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Mo</span><span class="tile-day-num">7</span></div>
<div class="tile-info"><div class="tile-name">Hähnchen-Curry</div><div class="tile-meta">35 Min</div></div>
</div>
<div class="tile tile-today tile-faded img-pasta" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Di</span><span class="tile-day-num dn-today">8</span></div>
<div class="tile-info"><div class="tile-name">Pasta Bolognese</div><div class="tile-meta">45 Min</div></div>
</div>
<!-- Mi selected -->
<div class="tile tile-sel img-stirfry" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr" style="color:rgba(255,255,255,.9);">Mi</span><span class="tile-day-num dn-sel">9</span></div>
<div class="tile-info">
<div class="tile-name">Gemüse-Stir-fry</div>
<div class="tile-meta">20 Min · einfach</div>
<div class="tile-tags"><span class="tag tag-sel">Tofu</span></div>
<div style="text-align:center;font-size:9px;color:rgba(255,255,255,.7);margin-top:4px;"></div>
</div>
</div>
<div class="tile tile-faded img-lachs" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Do</span><span class="tile-day-num">10</span></div>
<div class="tile-info"><div class="tile-name">Lachs mit Kartoffeln</div><div class="tile-meta">30 Min</div></div>
</div>
<div class="tile tile-faded img-pizza" style="height:100%;">
<div class="tile-overlay"></div>
<div class="tile-head"><span class="tile-day-abbr">Fr</span><span class="tile-day-num">11</span></div>
<div class="tile-info"><div class="tile-name">Pizza Margherita</div><div class="tile-meta">50 Min</div></div>
</div>
<div class="tile-empty tile-empty-faded" style="height:100%;">
<div class="empty-head"><span class="empty-day-abbr">Sa</span><span class="empty-day-num">12</span></div>
<div class="empty-cta"><div class="empty-plus">+</div></div>
</div>
<div class="tile-empty tile-empty-faded" style="height:100%;">
<div class="empty-head"><span class="empty-day-abbr">So</span><span class="empty-day-num">13</span></div>
<div class="empty-cta"><div class="empty-plus">+</div></div>
</div>
</div>
<!-- Expansion -->
<div class="expand-wrap">
<div class="exp-arrows">
<div></div><div></div>
<div class="exp-arr"><div class="arr arr-fill"></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>
</div>
<div class="exp-bdgs">
<span class="ebdg ebdg-e">einfach</span>
<span class="ebdg ebdg-p">Protein: Tofu</span>
<span class="ebdg ebdg-sc">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>
<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?</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 pb-t" style="margin-bottom:12px;"><div class="pb-fill pb-fg-g" style="width:78%;"></div></div>
<div class="hr"></div>
<div style="display:flex;flex-direction:column;gap:6px;">
<div style="font-family:var(--fs);font-size:11px;color:var(--gd);">✓ Kein Protein-Overlap</div>
<div style="font-family:var(--fs);font-size:11px;color:var(--gd);">✓ Neue Zutaten</div>
<div style="font-family:var(--fs);font-size:11px;color:var(--yx);">~ Tofu zum 2. Mal</div>
</div>
</div>
</div>
</div>
<div class="note">
<strong>Beim Klick auf eine gefüllte Kachel</strong> werden alle anderen auf 42% Deckkraft gedimmt
(gefüllte <em>und</em> leere). Die Tiles bleiben in ihrer Höhe, die Expansion wächst darunter.
Da das Bild jetzt die volle Kachelhöhe einnimmt, wirkt das Dimmen als echter Fokus-Effekt —
wie eine Lupe auf das ausgewählte Gericht.
</div>
</div>
</body>
</html>