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

Final design spec for the planner desktop layout overhaul:
full-bleed color tiles, CSS 3D card flip for recipe detail,
no persistent right panel, inline suggestions on empty days.
Includes interactive mockup and written component spec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 18:19:57 +02:00
parent 0596fddcd3
commit f139dce82c
2 changed files with 1221 additions and 0 deletions

View File

@@ -0,0 +1,762 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Planner — Flip 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-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;
}
/* ── Ingredient / cuisine colour palette ──────────────────────── */
/* Protein-based */
--col-haehnchen: linear-gradient(160deg,#d4923a 0%,#a85e1a 50%,#7a3d0c 100%);
--col-rind: linear-gradient(160deg,#c04545 0%,#8b2020 50%,#5a1010 100%);
--col-fisch: linear-gradient(160deg,#5b9fd4 0%,#2868a0 50%,#10406e 100%);
--col-tofu: linear-gradient(160deg,#5fa85e 0%,#2e7031 50%,#1a4a1e 100%);
--col-veg: linear-gradient(160deg,#7bc47b 0%,#3d8c3d 50%,#1e5a1e 100%);
--col-schwein: linear-gradient(160deg,#d4785a 0%,#a04535 50%,#6e2418 100%);
--col-lamm: linear-gradient(160deg,#9e6b3a 0%,#6b3f1a 50%,#3e2208 100%);
--col-ei: linear-gradient(160deg,#d4b832 0%,#a07010 50%,#6e4800 100%);
--col-linsen: linear-gradient(160deg,#8b6b3a 0%,#5e421a 50%,#3a2408 100%);
/* Cuisine-based */
--col-italienisch: linear-gradient(160deg,#c04545 0%,#7a1e1e 50%,#4a0f0f 100%);
--col-asiatisch: linear-gradient(160deg,#3a6e3a 0%,#1e4a1e 50%,#0e2e0e 100%);
--col-mexikanisch: linear-gradient(160deg,#d4923a 0%,#8b4e10 50%,#5a2e00 100%);
--col-indisch: linear-gradient(160deg,#c49010 0%,#8b5e00 50%,#5a3800 100%);
--col-mediterran: linear-gradient(160deg,#5b9fd4 0%,#1e5a8b 50%,#0a3456 100%);
*{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;}
.pg-title{font-family:var(--fd);font-size:34px;font-weight:300;margin-bottom:6px;}
.pg-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;}
.bl-hd{display:flex;align-items:baseline;gap:10px;margin-bottom:14px;}
.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-sub{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:16px;line-height:1.6;}
.note strong{color:var(--text);font-weight:500;}
/* ── Colour palette swatches ─────────────────────────────────── */
.swatch-grid{display:flex;flex-wrap:wrap;gap:8px;}
.swatch{width:88px;border-radius:var(--r-md);overflow:hidden;box-shadow:var(--sh-card);}
.swatch-color{height:52px;}
.swatch-label{font-family:var(--fs);font-size:10px;font-weight:500;color:var(--text);padding:5px 7px;background:var(--page);border-top:1px solid var(--border);}
.swatch-sub{font-family:var(--fs);font-size:9px;color:var(--muted);padding:0 7px 5px;}
/* ── 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;}
/* Main */
.main{flex:1;overflow-y:auto;padding:12px;}
.grid7{display:grid;grid-template-columns:repeat(7,1fr);gap:7px;}
/* ═══════════════════════════════════════════════
CARD FLIP SYSTEM
Each tile is a .scene > .card > .front + .back
═══════════════════════════════════════════════ */
.scene{
border-radius:var(--r-lg);
/* Perspective for 3D depth */
perspective:900px;
cursor:pointer;
}
.card{
position:relative;
width:100%;height:100%;
transform-style:preserve-3d;
transition:transform .45s cubic-bezier(.4,0,.2,1);
border-radius:var(--r-lg);
}
.card.flipped{transform:rotateY(180deg);}
/* Both faces */
.card-front,
.card-back{
position:absolute;inset:0;
border-radius:var(--r-lg);
overflow:hidden;
backface-visibility:hidden;
-webkit-backface-visibility:hidden;
}
/* ── FRONT face: full-bleed image ─── */
.card-front{
background-size:cover;
background-position:center;
}
/* Gradient: dark top (header), clear middle, dark bottom (text) */
.front-overlay{
position:absolute;inset:0;
background:
linear-gradient(to bottom,
rgba(0,0,0,.38) 0%,
rgba(0,0,0,0) 28%,
rgba(0,0,0,0) 48%,
rgba(0,0,0,.62) 100%
);
border-radius:inherit;
}
.front-head{
position:absolute;top:0;left:0;right:0;
display:flex;align-items:center;justify-content:space-between;
padding:8px 9px;z-index:2;
}
.front-abbr{font-family:var(--fs);font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:rgba(255,255,255,.85);font-weight:500;}
.front-badge{
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);
}
.fb-today{background:var(--y) !important;color:#fff !important;}
.front-info{
position:absolute;bottom:0;left:0;right:0;
padding:8px 9px 10px;z-index:2;
}
.front-name{
font-family:var(--fd);font-size:13px;font-weight:300;
color:#fff;line-height:1.3;
text-shadow:0 1px 4px rgba(0,0,0,.5);
}
.front-meta{font-family:var(--fs);font-size:10px;color:rgba(255,255,255,.78);margin-top:2px;}
.front-tags{display:flex;gap:3px;flex-wrap:wrap;margin-top:5px;}
.ftag{
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 rings via box-shadow (no layout shift) */
.card-front.st-default{box-shadow:var(--sh-card);}
.card-front.st-today{box-shadow:0 0 0 2px var(--y), var(--sh-card);}
.card-front.st-sel{box-shadow:0 0 0 2px var(--g), var(--sh-raised);}
.card-back.st-today{box-shadow:0 0 0 2px var(--y), var(--sh-raised);}
.card-back.st-sel{box-shadow:0 0 0 2px var(--g), var(--sh-raised);}
/* ── BACK face: recipe detail ─── */
.card-back{
transform:rotateY(180deg);
background:var(--page);
display:flex;flex-direction:column;
padding:0;
}
/* Thin colour strip at top of back = recipe's colour accent */
.back-strip{height:5px;flex-shrink:0;border-radius:var(--r-lg) var(--r-lg) 0 0;}
.back-inner{
display:flex;flex-direction:column;
flex:1;padding:8px 9px 9px;overflow:hidden;
}
.back-head{
display:flex;align-items:center;justify-content:space-between;
margin-bottom:6px;flex-shrink:0;
}
.back-day{font-family:var(--fs);font-size:9px;font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);}
.back-close{
width:18px;height:18px;border-radius:var(--r-full);
display:flex;align-items:center;justify-content:center;
background:var(--subtle);font-size:11px;line-height:1;
color:var(--muted);cursor:pointer;flex-shrink:0;
border:none;font-family:var(--fs);
}
.back-close:hover{background:var(--border);}
.back-name{
font-family:var(--fd);font-size:15px;font-weight:300;
line-height:1.25;color:var(--text);
margin-bottom:3px;flex-shrink:0;
}
.back-meta{font-family:var(--fs);font-size:10px;color:var(--muted);margin-bottom:8px;flex-shrink:0;}
.back-ings{display:flex;flex-wrap:wrap;gap:3px;margin-bottom:8px;flex-shrink:0;}
.bing{
font-family:var(--fs);font-size:9px;
background:var(--surface);border:1px solid var(--border);
border-radius:var(--r-full);padding:2px 6px;color:var(--text);
}
.bing-s{background:var(--subtle);border-color:var(--subtle);color:var(--muted);}
.back-actions{display:flex;flex-direction:column;gap:4px;margin-top:auto;}
.bact{
display:block;width:100%;padding:6px 8px;
border-radius:var(--r-md);border:1px solid var(--border);
background:var(--page);font-family:var(--fs);
font-size:10px;font-weight:500;letter-spacing:.04em;
text-align:center;color:var(--text);cursor:pointer;
}
.bact-pri{background:var(--gd);color:#fff;border:none;}
.bact-err{color:var(--err);border-color:var(--err);background:transparent;margin-top:2px;}
/* Tile faded (non-selected state) */
.scene-faded{opacity:.38;pointer-events:none;}
/* ── EMPTY TILE (no flip needed) ─── */
.tile-empty{
border-radius:var(--r-lg);
border:1.5px dashed var(--border);
background:var(--surface);
display:flex;flex-direction:column;
overflow:hidden;box-shadow:var(--sh-card);
cursor:pointer;
}
.te-sel{border:2px dashed var(--g);background:rgba(232,245,234,.5);}
.te-faded{opacity:.22;pointer-events:none;}
.te-head{display:flex;align-items:center;justify-content:space-between;padding:7px 8px 0;flex-shrink:0;}
.te-abbr{font-family:var(--fs);font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);}
.te-num{font-family:var(--fs);font-size:10px;font-weight:500;color:var(--muted);}
.te-cta{display:flex;flex-direction:column;align-items:center;padding:7px 6px 5px;gap:2px;flex-shrink:0;border-bottom:1px solid var(--border);}
.te-plus{font-size:17px;color:var(--border);}
.te-label{font-family:var(--fs);font-size:9px;color:var(--muted);}
.sug-list{display:flex;flex-direction:column;padding:5px 7px 5px;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 4px;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-name{font-family:var(--fd);font-size:11px;font-weight:300;color:var(--text);flex:1;line-height:1.2;}
.stag{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:4px;margin-top:auto;}
/* ── Image backgrounds ───────────────────────── */
.img-haehnchen{background:linear-gradient(160deg,#d4923a 0%,#a85e1a 50%,#7a3d0c 100%);}
.img-rind {background:linear-gradient(160deg,#c04545 0%,#8b2020 50%,#5a1010 100%);}
.img-stirfry {background:linear-gradient(160deg,#5fa85e 0%,#2e7031 50%,#1a4a1e 100%);}
.img-fisch {background:linear-gradient(160deg,#5b9fd4 0%,#2868a0 50%,#10406e 100%);}
.img-pizza {background:linear-gradient(160deg,#d4a832 0%,#a07010 50%,#6e4a00 100%);}
/* Accent strip matches image colours */
.strip-haehnchen{background:linear-gradient(90deg,#d4923a,#a85e1a);}
.strip-rind {background:linear-gradient(90deg,#c04545,#8b2020);}
.strip-stirfry {background:linear-gradient(90deg,#5fa85e,#2e7031);}
.strip-fisch {background:linear-gradient(90deg,#5b9fd4,#2868a0);}
.strip-pizza {background:linear-gradient(90deg,#d4a832,#a07010);}
/* ── Demo controls ───────────────────────────── */
.demo-hint{
font-family:var(--fs);font-size:11px;color:var(--muted);
text-align:center;margin-bottom:10px;
}
.demo-hint span{
background:var(--subtle);border-radius:var(--r-sm);
padding:2px 8px;font-weight:500;color:var(--text);
}
.specimen-row{display:flex;gap:14px;margin-bottom:8px;flex-wrap:wrap;align-items:flex-start;}
.specimen-wrap{display:flex;flex-direction:column;align-items:center;gap:6px;}
.specimen-label{font-family:var(--fs);font-size:10px;font-weight:500;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);text-align:center;}
</style>
</head>
<body>
<p class="eyebrow">Mealplan · Planer · Flip Tiles</p>
<h1 class="pg-title">Kachel-Flip + Zutaten-Farben</h1>
<p class="pg-sub">
Klick auf eine gefüllte Kachel → sie dreht sich um. Auf der Rückseite: Rezeptname, Hauptzutaten, Aktionen.
Kein Expansion-Panel mehr. Leere Kacheln bleiben unverändert mit Inline-Vorschlägen.
</p>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- SEKTION 1: FARB-PALETTE -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="block">
<div class="bl-hd">
<span class="bl-num">Palette</span>
<span class="bl-name">Farben nach Hauptzutat / Küchenstil</span>
<span class="bl-sub">Fallback wenn heroImageUrl fehlt</span>
</div>
<div class="swatch-grid">
<!-- Proteins -->
<div class="swatch"><div class="swatch-color img-haehnchen"></div><div class="swatch-label">Hähnchen</div><div class="swatch-sub">Protein</div></div>
<div class="swatch"><div class="swatch-color img-rind"></div><div class="swatch-label">Rind</div><div class="swatch-sub">Protein</div></div>
<div class="swatch"><div class="swatch-color img-fisch"></div><div class="swatch-label">Fisch</div><div class="swatch-sub">Protein</div></div>
<div class="swatch"><div class="swatch-color img-stirfry"></div><div class="swatch-label">Tofu</div><div class="swatch-sub">Protein</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#7bc47b 0%,#3d8c3d 50%,#1e5a1e 100%);"></div><div class="swatch-label">vegetarisch</div><div class="swatch-sub">Protein</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4785a 0%,#a04535 50%,#6e2418 100%);"></div><div class="swatch-label">Schwein</div><div class="swatch-sub">Protein</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#9e6b3a 0%,#6b3f1a 50%,#3e2208 100%);"></div><div class="swatch-label">Lamm</div><div class="swatch-sub">Protein</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4b832 0%,#a07010 50%,#6e4800 100%);"></div><div class="swatch-label">Ei</div><div class="swatch-sub">Protein</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#8b6b3a 0%,#5e421a 50%,#3a2408 100%);"></div><div class="swatch-label">Hülsenfrüchte</div><div class="swatch-sub">Protein</div></div>
<!-- Cuisine overrides -->
<div class="swatch"><div class="swatch-color img-pizza"></div><div class="swatch-label">Italienisch</div><div class="swatch-sub">Küche</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#3a6e3a 0%,#1e4a1e 50%,#0e2e0e 100%);"></div><div class="swatch-label">Asiatisch</div><div class="swatch-sub">Küche</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#c49010 0%,#8b5e00 50%,#5a3800 100%);"></div><div class="swatch-label">Indisch</div><div class="swatch-sub">Küche</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#c04545 0%,#7a1e1e 50%,#4a0f0f 100%);"></div><div class="swatch-label">Mexikanisch</div><div class="swatch-sub">Küche</div></div>
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#4a90b8 0%,#1e5a8b 50%,#0a3456 100%);"></div><div class="swatch-label">Mediterran</div><div class="swatch-sub">Küche</div></div>
</div>
<div class="note">
<strong>Priorität:</strong> Wenn <code>heroImageUrl</code> vorhanden → echtes Foto.
Sonst: Farbe nach erstem Protein-Tag (z.B. <code>tagType=protein</code>, <code>tagName=Hähnchen</code>).
Wenn kein Protein-Tag → Farbe nach Küchenstil-Tag (<code>tagType=cuisine</code>).
Fallback auf <code>--color-surface</code> neutral.
Die Farbwerte werden als CSS-Klassen gemappt: <code>protein-haehnchen</code>, <code>cuisine-asiatisch</code> etc.
</div>
</div>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- SEKTION 2: INTERACTIVE FLIP DEMO -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="block">
<div class="bl-hd">
<span class="bl-num">Demo</span>
<span class="bl-name">Flip-Interaktion — zum Klicken</span>
<span class="bl-sub">Echte CSS-3D-Transition</span>
</div>
<p class="demo-hint">Klicke auf eine Kachel um sie umzudrehen. <span>×</span> auf der Rückseite klappt zurück.</p>
<div class="specimen-row">
<!-- Tile 1: Hähnchen-Curry (normal) -->
<div class="specimen-wrap">
<div class="specimen-label">Standard</div>
<div class="scene" style="width:150px;height:240px;" onclick="flip(this)">
<div class="card">
<div class="card-front img-haehnchen st-default">
<div class="front-overlay"></div>
<div class="front-head">
<span class="front-abbr">Mo</span>
<span class="front-badge">7</span>
</div>
<div class="front-info">
<div class="front-name">Hähnchen-Curry</div>
<div class="front-meta">35 Min · mittel</div>
<div class="front-tags">
<span class="ftag">Hähnchen</span>
<span class="ftag">4 Port.</span>
</div>
</div>
</div>
<div class="card-back st-default">
<div class="back-strip strip-haehnchen"></div>
<div class="back-inner">
<div class="back-head">
<span class="back-day">Mo · 7. Apr</span>
<button class="back-close" onclick="unflip(event,this)">×</button>
</div>
<div class="back-name">Hähnchen-Curry</div>
<div class="back-meta">35 Min · mittel · 4 Port.</div>
<div class="back-ings">
<span class="bing">Hähnchen</span>
<span class="bing">Kokosmilch</span>
<span class="bing">Paprika</span>
<span class="bing">Spinat</span>
<span class="bing-s">Curry</span>
<span class="bing-s">Knoblauch</span>
</div>
<div class="back-actions">
<button class="bact bact-pri">Koch-Modus</button>
<button class="bact">Rezept ansehen</button>
<button class="bact">Gericht tauschen</button>
<button class="bact bact-err">Entfernen</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tile 2: Pasta Bolognese (today) -->
<div class="specimen-wrap">
<div class="specimen-label">Heute</div>
<div class="scene" style="width:150px;height:240px;" onclick="flip(this)">
<div class="card">
<div class="card-front img-rind st-today">
<div class="front-overlay"></div>
<div class="front-head">
<span class="front-abbr">Di</span>
<span class="front-badge fb-today">8</span>
</div>
<div class="front-info">
<div class="front-name">Pasta Bolognese</div>
<div class="front-meta">45 Min · mittel</div>
<div class="front-tags">
<span class="ftag" style="background:rgba(242,193,46,.35);">Rind</span>
<span class="ftag" style="background:rgba(242,193,46,.35);">Heute</span>
</div>
</div>
</div>
<div class="card-back st-today">
<div class="back-strip strip-rind"></div>
<div class="back-inner">
<div class="back-head">
<span class="back-day" style="color:var(--yx);">Di · Heute</span>
<button class="back-close" onclick="unflip(event,this)">×</button>
</div>
<div class="back-name">Pasta Bolognese</div>
<div class="back-meta">45 Min · mittel · 4 Port.</div>
<div class="back-ings">
<span class="bing">Rinderhack</span>
<span class="bing">Pasta</span>
<span class="bing">Tomaten</span>
<span class="bing">Zwiebeln</span>
<span class="bing-s">Olivenöl</span>
<span class="bing-s">Knoblauch</span>
</div>
<div class="back-actions">
<button class="bact bact-pri">Koch-Modus</button>
<button class="bact">Rezept ansehen</button>
<button class="bact">Gericht tauschen</button>
<button class="bact bact-err">Entfernen</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tile 3: Gemüse-Stir-fry (selected + flipped by default) -->
<div class="specimen-wrap">
<div class="specimen-label">Ausgewählt (bereits umgedreht)</div>
<div class="scene" style="width:150px;height:240px;" onclick="flip(this)">
<div class="card flipped">
<div class="card-front img-stirfry st-sel">
<div class="front-overlay"></div>
<div class="front-head">
<span class="front-abbr">Mi</span>
<span class="front-badge" style="background:var(--g);color:#fff;">9</span>
</div>
<div class="front-info">
<div class="front-name">Gemüse-Stir-fry</div>
<div class="front-meta">20 Min · einfach</div>
<div class="front-tags"><span class="ftag" style="background:rgba(61,140,74,.4);">Tofu</span></div>
</div>
</div>
<div class="card-back st-sel">
<div class="back-strip strip-stirfry"></div>
<div class="back-inner">
<div class="back-head">
<span class="back-day" style="color:var(--gd);">Mi · 9. Apr</span>
<button class="back-close" onclick="unflip(event,this)">×</button>
</div>
<div class="back-name">Gemüse-Stir-fry</div>
<div class="back-meta">20 Min · einfach · 2 Port.</div>
<div class="back-ings">
<span class="bing">Tofu</span>
<span class="bing">Paprika</span>
<span class="bing">Brokkoli</span>
<span class="bing">Karotten</span>
<span class="bing-s">Sesamöl</span>
<span class="bing-s">Sojasauce</span>
</div>
<div class="back-actions">
<button class="bact bact-pri">Koch-Modus</button>
<button class="bact">Rezept ansehen</button>
<button class="bact">Gericht tauschen</button>
<button class="bact bact-err">Entfernen</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Empty tile with suggestions -->
<div class="specimen-wrap">
<div class="specimen-label">Leer — kein Flip</div>
<div class="tile-empty" style="width:150px;height:240px;">
<div class="te-head">
<span class="te-abbr">Sa</span>
<span class="te-num">12</span>
</div>
<div class="te-cta">
<div class="te-plus">+</div>
<div class="te-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="stag st-g">Neues Protein</span></div>
<div class="sug-row"><span class="sug-name">Shakshuka</span><span class="stag st-g">Kein Overlap</span></div>
<div class="sug-row"><span class="sug-name">Tacos</span><span class="stag st-y">Aufwand: leicht</span></div>
<div class="sug-more">Alle Rezepte →</div>
</div>
</div>
</div>
</div>
<div class="note">
<strong>Flip-Mechanik:</strong> CSS <code>transform:rotateY(180deg)</code> auf dem <code>.card</code> wrapper,
<code>backface-visibility:hidden</code> auf beiden Faces, <code>perspective:900px</code> auf der Scene.
Transition: <code>.45s cubic-bezier(.4,0,.2,1)</code> (Material-Easing — schnell herein, weich heraus).
Der <code>×</code> Button auf der Rückseite stoppt den Klick-Event mit <code>stopPropagation()</code>
und dreht die Karte zurück. Kein zusätzlicher State nötig — die Karte ist selbst das State-Element.
<br><br>
<strong>Farbstreifen</strong> oben auf der Rückseite = 5px Gradient, identisch mit der Front-Farbe.
Gibt visuelle Kontinuität zwischen Vorder- und Rückseite.
</div>
</div>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- SEKTION 3: VOLLSTÄNDIGE SEITENANSICHT — Mi UMGEDREHT -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="block">
<div class="bl-hd">
<span class="bl-num">Seite</span>
<span class="bl-name">Vollansicht — Mittwoch umgedreht</span>
<span class="bl-sub">Kein rechtes Panel. Kacheln bis zum Rand.</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 tb-ml">Heute</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 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>
<!-- MAIN: grid fills full height, Mi is flipped -->
<div class="main">
<div class="grid7" style="height:100%;">
<!-- Mo: faded -->
<div class="scene scene-faded" style="height:100%;">
<div class="card">
<div class="card-front img-haehnchen st-default">
<div class="front-overlay"></div>
<div class="front-head"><span class="front-abbr">Mo</span><span class="front-badge">7</span></div>
<div class="front-info"><div class="front-name">Hähnchen-Curry</div><div class="front-meta">35 Min · mittel</div></div>
</div>
</div>
</div>
<!-- Di: today, faded -->
<div class="scene scene-faded" style="height:100%;">
<div class="card">
<div class="card-front img-rind st-today">
<div class="front-overlay"></div>
<div class="front-head"><span class="front-abbr">Di</span><span class="front-badge fb-today">8</span></div>
<div class="front-info"><div class="front-name">Pasta Bolognese</div><div class="front-meta">45 Min · mittel</div></div>
</div>
</div>
</div>
<!-- Mi: SELECTED + FLIPPED -->
<div class="scene" style="height:100%;" onclick="flip(this)">
<div class="card flipped">
<div class="card-front img-stirfry st-sel">
<div class="front-overlay"></div>
<div class="front-head">
<span class="front-abbr">Mi</span>
<span class="front-badge" style="background:var(--g);color:#fff;">9</span>
</div>
<div class="front-info">
<div class="front-name">Gemüse-Stir-fry</div>
<div class="front-meta">20 Min · einfach</div>
<div class="front-tags"><span class="ftag" style="background:rgba(61,140,74,.4);">Tofu</span></div>
</div>
</div>
<div class="card-back st-sel">
<div class="back-strip strip-stirfry"></div>
<div class="back-inner">
<div class="back-head">
<span class="back-day" style="color:var(--gd);">Mi · 9. Apr</span>
<button class="back-close" onclick="unflip(event,this)">×</button>
</div>
<div class="back-name">Gemüse-Stir-fry</div>
<div class="back-meta">20 Min · einfach · 2 Port.</div>
<div class="back-ings">
<span class="bing">Tofu</span>
<span class="bing">Paprika</span>
<span class="bing">Brokkoli</span>
<span class="bing">Karotten</span>
<span class="bing">Ingwer</span>
<span class="bing-s">Sesamöl</span>
<span class="bing-s">Sojasauce</span>
</div>
<div class="back-actions">
<button class="bact bact-pri">Koch-Modus</button>
<button class="bact">Rezept ansehen</button>
<button class="bact">Gericht tauschen</button>
<button class="bact bact-err">Entfernen</button>
</div>
</div>
</div>
</div>
</div>
<!-- Do: faded -->
<div class="scene scene-faded" style="height:100%;">
<div class="card">
<div class="card-front img-fisch st-default">
<div class="front-overlay"></div>
<div class="front-head"><span class="front-abbr">Do</span><span class="front-badge">10</span></div>
<div class="front-info"><div class="front-name">Lachs mit Kartoffeln</div><div class="front-meta">30 Min · einfach</div></div>
</div>
</div>
</div>
<!-- Fr: faded -->
<div class="scene scene-faded" style="height:100%;">
<div class="card">
<div class="card-front img-pizza st-default">
<div class="front-overlay"></div>
<div class="front-head"><span class="front-abbr">Fr</span><span class="front-badge">11</span></div>
<div class="front-info"><div class="front-name">Pizza Margherita</div><div class="front-meta">50 Min · aufwändig</div></div>
</div>
</div>
</div>
<!-- Sa: empty with suggestions -->
<div class="tile-empty te-faded" style="height:100%;">
<div class="te-head"><span class="te-abbr">Sa</span><span class="te-num">12</span></div>
<div class="te-cta"><div class="te-plus">+</div><div class="te-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="stag st-g">Neues Protein</span></div>
<div class="sug-row"><span class="sug-name">Shakshuka</span><span class="stag st-g">Kein Overlap</span></div>
<div class="sug-more">Alle Rezepte →</div>
</div>
</div>
<!-- So: empty with suggestions -->
<div class="tile-empty te-faded" style="height:100%;">
<div class="te-head"><span class="te-abbr">So</span><span class="te-num">13</span></div>
<div class="te-cta"><div class="te-plus">+</div><div class="te-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">Grünes Thai-Curry</span><span class="stag st-g">Neues Protein</span></div>
<div class="sug-row"><span class="sug-name">Tacos</span><span class="stag st-y">Aufwand: leicht</span></div>
<div class="sug-more">Alle Rezepte →</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="note">
<strong>Layout:</strong> Linke Sidebar (Variety-Score) bleibt. Kein rechtes Panel mehr.
Die Kacheln füllen den gesamten verbleibenden Platz (<code>flex:1</code>) — 7 gleich breite Spalten,
volle Höhe (<code>height:100%</code> auf Grid und Kacheln). Kein Layout-Shift, kein After-Scroll.
<br><br>
<strong>Dimm-Effekt:</strong> Beim Flip werden alle anderen Kacheln auf 38% gedimmt.
Kein neuer API-Aufruf nötig — reine CSS-Klasse per JS.
<br><br>
<strong>„Gericht tauschen":</strong> Öffnet den Rezept-Picker als Slide-in-Drawer von rechts
(kein persistentes Panel). Drawer schließt sich nach Auswahl oder Abbruch.
<br><br>
<strong>Leere Kacheln:</strong> Zeigen Inline-Vorschläge auch im gedimmten Zustand (wenn
eine andere Kachel geflippt ist). Kein Flip auf leeren Kacheln.
</div>
</div>
<script>
function flip(scene) {
const card = scene.querySelector('.card');
const isFlipped = card.classList.toggle('flipped');
// Dim all other scenes in the same grid
const grid = scene.closest('.grid7');
if (!grid) return;
grid.querySelectorAll('.scene, .tile-empty').forEach(el => {
if (el === scene) return;
if (isFlipped) {
el.style.opacity = '0.38';
el.style.pointerEvents = 'none';
} else {
el.style.opacity = '';
el.style.pointerEvents = '';
}
});
}
function unflip(event, btn) {
event.stopPropagation();
const scene = btn.closest('.scene');
const card = scene.querySelector('.card');
card.classList.remove('flipped');
// Un-dim everything
const grid = scene.closest('.grid7');
if (!grid) return;
grid.querySelectorAll('.scene, .tile-empty').forEach(el => {
el.style.opacity = '';
el.style.pointerEvents = '';
});
}
</script>
</body>
</html>

View File

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