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:
762
specs/planner-flip-tiles.html
Normal file
762
specs/planner-flip-tiles.html
Normal 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>
|
||||
459
specs/planner-redesign-flip-tiles.html
Normal file
459
specs/planner-redesign-flip-tiles.html
Normal 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 & Cuisine Colors</h2>
|
||||
|
||||
<p>
|
||||
Wenn <code>heroImageUrl</code> vorhanden ist, wird das echte Foto als <code>background-image</code> gesetzt.
|
||||
Fehlt es, greift die folgende Prioritätskette:
|
||||
</p>
|
||||
<ol style="padding-left:20px;margin-bottom:16px;">
|
||||
<li>Ersten Tag mit <code>tagType = "protein"</code> finden → Protein-Farbe</li>
|
||||
<li>Ersten Tag mit <code>tagType = "cuisine"</code> finden → Küchenstil-Farbe</li>
|
||||
<li>Fallback: <code>background: var(--color-surface)</code> (neutral)</li>
|
||||
</ol>
|
||||
|
||||
<h3>Protein-Farben</h3>
|
||||
<div class="swatch-row">
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4923a,#a85e1a,#7a3d0c)"></div><div class="swatch-name">Hähnchen</div><div class="swatch-sub">protein-haehnchen</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#c04545,#8b2020,#5a1010)"></div><div class="swatch-name">Rind</div><div class="swatch-sub">protein-rind</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#5b9fd4,#2868a0,#10406e)"></div><div class="swatch-name">Fisch</div><div class="swatch-sub">protein-fisch</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#5fa85e,#2e7031,#1a4a1e)"></div><div class="swatch-name">Tofu</div><div class="swatch-sub">protein-tofu</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#7bc47b,#3d8c3d,#1e5a1e)"></div><div class="swatch-name">Vegetarisch</div><div class="swatch-sub">protein-veg</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4785a,#a04535,#6e2418)"></div><div class="swatch-name">Schwein</div><div class="swatch-sub">protein-schwein</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#9e6b3a,#6b3f1a,#3e2208)"></div><div class="swatch-name">Lamm</div><div class="swatch-sub">protein-lamm</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4b832,#a07010,#6e4800)"></div><div class="swatch-name">Ei</div><div class="swatch-sub">protein-ei</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#8b6b3a,#5e421a,#3a2408)"></div><div class="swatch-name">Hülsenfrüchte</div><div class="swatch-sub">protein-huelsenfruechte</div></div>
|
||||
</div>
|
||||
|
||||
<h3>Küchenstil-Farben</h3>
|
||||
<div class="swatch-row">
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#c04545,#7a1e1e,#4a0f0f)"></div><div class="swatch-name">Italienisch</div><div class="swatch-sub">cuisine-italienisch</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#3a6e3a,#1e4a1e,#0e2e0e)"></div><div class="swatch-name">Asiatisch</div><div class="swatch-sub">cuisine-asiatisch</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#c49010,#8b5e00,#5a3800)"></div><div class="swatch-name">Indisch</div><div class="swatch-sub">cuisine-indisch</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#d4923a,#8b4e10,#5a2e00)"></div><div class="swatch-name">Mexikanisch</div><div class="swatch-sub">cuisine-mexikanisch</div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:linear-gradient(160deg,#4a90b8,#1e5a8b,#0a3456)"></div><div class="swatch-name">Mediterran</div><div class="swatch-sub">cuisine-mediterran</div></div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Die CSS-Klassen (<code>protein-haehnchen</code>, <code>cuisine-asiatisch</code>, …) werden
|
||||
serverseitig aus den Rezept-Tags abgeleitet und als Svelte-Prop übergeben, z.B.
|
||||
<code>colorClass="protein-haehnchen"</code>. Das Component setzt die Klasse auf dem Kachel-Wrapper.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-label">04 · Flip-Mechanik</div>
|
||||
<h2>CSS 3D Card Flip</h2>
|
||||
|
||||
<p>Jede gefüllte Kachel besteht aus drei verschachtelten Elementen:</p>
|
||||
<pre>.scene → perspective: 900px; border-radius: var(--radius-lg); cursor: pointer
|
||||
.card → position: relative; transform-style: preserve-3d
|
||||
transition: transform .45s cubic-bezier(.4,0,.2,1)
|
||||
.card.flipped → transform: rotateY(180deg)
|
||||
.card-front → backface-visibility: hidden; position: absolute; inset: 0
|
||||
.card-back → backface-visibility: hidden; transform: rotateY(180deg)
|
||||
position: absolute; inset: 0; background: var(--color-page)</pre>
|
||||
|
||||
<h3>Vorderseite</h3>
|
||||
<ul>
|
||||
<li>Vollbild-Farbe oder <code>background-image: url(heroImageUrl)</code> mit <code>background-size: cover</code></li>
|
||||
<li>Dual-Gradient-Overlay als absolutes <code>::after</code>-Pseudo-Element:<br>
|
||||
<code>linear-gradient(to bottom, rgba(0,0,0,.38) 0%, transparent 28%, transparent 48%, rgba(0,0,0,.62) 100%)</code></li>
|
||||
<li>Oben links: Tageskürzel (9px uppercase). Oben rechts: Datums-Badge (Kreis)</li>
|
||||
<li>Unten: Rezeptname (Fraunces 13px), Meta-Zeile (Kochzeit · Aufwand), Tag-Chips</li>
|
||||
</ul>
|
||||
|
||||
<h3>Rückseite</h3>
|
||||
<ul>
|
||||
<li><strong>Farbstreifen (5 px)</strong> oben — identischer Gradient wie die Vorderseite. Gibt visuelle Kontinuität.</li>
|
||||
<li>Tageskürzel + Datum (links) · × Schließen-Button (rechts)</li>
|
||||
<li>Rezeptname (Fraunces 15px)</li>
|
||||
<li>Meta: Kochzeit · Aufwand · Portionen</li>
|
||||
<li>Zutaten-Pills: normale Zutaten als <code>.ingredient</code>, Vorrats-Zutaten (Staples) gedimmt als <code>.ingredient--staple</code></li>
|
||||
<li>Aktionen (gestapelt, volle Breite):</li>
|
||||
</ul>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Aktion</th><th>Stil</th><th>Verhalten</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Koch-Modus starten</td><td>Primary (grün ausgefüllt)</td><td>Navigiert zu <code>/planner/cook/[slotId]</code></td></tr>
|
||||
<tr><td>Rezept ansehen</td><td>Secondary (Rahmen)</td><td>Navigiert zu <code>/recipes/[recipeId]</code></td></tr>
|
||||
<tr><td>Gericht tauschen</td><td>Secondary (Rahmen)</td><td>Öffnet Rezept-Picker-Drawer (§06)</td></tr>
|
||||
<tr><td>Entfernen</td><td>Danger (roter Text, transparenter BG)</td><td>Löscht den Slot, Kachel wird leer</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Interaction Flow</h3>
|
||||
<ul>
|
||||
<li>Klick auf <code>.scene</code> → <code>.card.classList.toggle('flipped')</code></li>
|
||||
<li>Alle Geschwister-Kacheln im Grid → <code>opacity: 0.38; pointer-events: none</code></li>
|
||||
<li>× Button auf Rückseite → <code>event.stopPropagation()</code>, <code>classList.remove('flipped')</code>, Geschwister-Opacity zurücksetzen</li>
|
||||
<li>Escape-Taste → aktive Kachel zurückdrehen</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout green">
|
||||
<strong>Kein API-Aufruf beim Flip.</strong> Alle dargestellten Daten (Name, Zutaten, Aktionen) sind bereits
|
||||
im vorhandenen <code>slotMap</code>-State vorhanden. Der Flip ist eine rein visuelle Operation.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-label">05 · Leere Kacheln</div>
|
||||
<h2>Empty Tile — Inline Suggestions</h2>
|
||||
|
||||
<p>Leere Kacheln haben denselben <code>height: 100%</code> wie gefüllte Kacheln. Kein Flip.</p>
|
||||
|
||||
<pre>┌─────────────────┐
|
||||
│ Sa 12 │ ← Tageskürzel + Datum
|
||||
│─────────────────│
|
||||
│ + │
|
||||
│ Gericht wählen │ ← Klick öffnet Rezept-Picker-Drawer
|
||||
│─────────────────│
|
||||
│ VORSCHLÄGE │
|
||||
│ Ramen mit Ei [Neues Protein] │
|
||||
│ Shakshuka [Kein Overlap] │
|
||||
│ Tacos [Aufwand: leicht]│
|
||||
│ │
|
||||
│ Alle Rezepte → │
|
||||
└────────────────────────────────┘</pre>
|
||||
|
||||
<h3>Vorschlag-Tags (Reasoning)</h3>
|
||||
<p>Anstelle numerischer Score-Deltas (die für leere Slots immer positiv sind und daher keine Information tragen)
|
||||
werden Begründungs-Tags angezeigt:</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Tag</th><th>Farbe</th><th>Bedeutung</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Neues Protein</td><td>Grün</td><td>Proteinquelle kommt diese Woche noch nicht vor</td></tr>
|
||||
<tr><td>Kein Overlap</td><td>Grün</td><td>Keine Zutaten-Überschneidung mit anderen Tagen</td></tr>
|
||||
<tr><td>Aufwand: leicht</td><td>Gelb</td><td>Kochzeit < 30 Min oder Aufwand = einfach</td></tr>
|
||||
<tr><td>Aufwand: mittel</td><td>Neutral</td><td>Mittlerer Aufwand</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="callout">
|
||||
<strong>Datenquelle:</strong> Die vorhandene <code>GET /api/suggestions?weekId=&dayOfWeek=</code> API liefert
|
||||
<code>SuggestionItem { recipe, scoreDelta, hasConflict }</code>. Die Reasoning-Tags werden frontend-seitig
|
||||
aus den Rezept-Tags und dem vorhandenen <code>slotMap</code> abgeleitet, kein Backend-Änderungsbedarf.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-label">06 · Rezept-Picker</div>
|
||||
<h2>Recipe Picker Drawer</h2>
|
||||
|
||||
<p>
|
||||
Der Rezept-Picker öffnet sich als Slide-in-Drawer von rechts — ausschließlich auf explizite Anfrage.
|
||||
Er hat keinen persistenten Platz im Layout mehr.
|
||||
</p>
|
||||
|
||||
<h3>Trigger</h3>
|
||||
<ul>
|
||||
<li>Klick auf <strong>„Gericht tauschen"</strong> auf der Kachel-Rückseite</li>
|
||||
<li>Klick auf <strong>„Gericht wählen"</strong> CTA oder Vorschlag-Zeile auf einer leeren Kachel</li>
|
||||
</ul>
|
||||
|
||||
<h3>Drawer-Verhalten</h3>
|
||||
<ul>
|
||||
<li>Slide-in von rechts, überlagert den Inhalt (kein Layout-Shift)</li>
|
||||
<li>Breite: <code>min(480px, 90vw)</code></li>
|
||||
<li>Backdrop (halbtransparent) schließt den Drawer bei Klick</li>
|
||||
<li>Nach Auswahl: Drawer schließt sich, Slot wird aktualisiert, Kachel zeigt neues Rezept</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout">
|
||||
Der bestehende <code>RecipePicker</code>-Komponente (aktuell im rechten Panel) wird in einen
|
||||
generischen Drawer gewrappt. Der Drawer-Wrapper ist neu; der Picker selbst bleibt unverändert.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-label">07 · Mobile</div>
|
||||
<h2>Mobile — Out of Scope</h2>
|
||||
|
||||
<p>
|
||||
Dieses Spec betrifft ausschließlich die Desktop-Ansicht (<code>≥ 768px</code>).
|
||||
Das mobile Layout (vertikaler Stack, DayMealCard, ActionSheet) bleibt unverändert.
|
||||
CSS-3D-Flips auf Touch-Geräten haben bekannte Rendering-Unterschiede auf älteren Android-Browsern —
|
||||
ein separates Issue sollte die mobile Interaktion (ggf. Slide-up Sheet statt Flip) spezifizieren.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-label">08 · Komponenten</div>
|
||||
<h2>Komponenten-Übersicht</h2>
|
||||
|
||||
<div class="component-row">
|
||||
<span class="comp-file">src/routes/(app)/planner/+page.svelte</span>
|
||||
<span class="badge badge-mod">Ändern</span>
|
||||
<span class="comp-action">Rechtes Panel entfernen. Layout auf 2-spaltig (sidebar + main) umstellen. Toolbar-Button entfernen. Grid-Höhe auf 100% setzen.</span>
|
||||
</div>
|
||||
<div class="component-row">
|
||||
<span class="comp-file">src/lib/planner/DayMealCard.svelte</span>
|
||||
<span class="badge badge-mod">Ersetzen / umbenennen</span>
|
||||
<span class="comp-action">Zur Flip-Kachel umbauen: .scene → .card → .card-front + .card-back. Farb-Klassen-Prop, Gradient-Overlay, Back-Face mit Aktionen.</span>
|
||||
</div>
|
||||
<div class="component-row">
|
||||
<span class="comp-file">src/lib/planner/EmptyDayTile.svelte</span>
|
||||
<span class="badge badge-new">Neu</span>
|
||||
<span class="comp-action">Leere Kachel: + CTA + Inline-Suggestion-Liste mit Reasoning-Tags. Ersetzt den bisherigen leeren Slot-Platzhalter.</span>
|
||||
</div>
|
||||
<div class="component-row">
|
||||
<span class="comp-file">src/lib/planner/RecipePickerDrawer.svelte</span>
|
||||
<span class="badge badge-new">Neu</span>
|
||||
<span class="comp-action">Drawer-Wrapper um den bestehenden RecipePicker. Slide-in von rechts, Backdrop, Schließ-Logik.</span>
|
||||
</div>
|
||||
<div class="component-row">
|
||||
<span class="comp-file">src/lib/planner/RecipePicker.svelte</span>
|
||||
<span class="badge badge-mod">Ändern</span>
|
||||
<span class="comp-action">Aus dem rechten Panel lösen. Bekommt slotId als Prop. Keine Änderung an der Such-/Auswahl-Logik nötig.</span>
|
||||
</div>
|
||||
<div class="component-row">
|
||||
<span class="comp-file">src/app.css</span>
|
||||
<span class="badge badge-mod">Ergänzen</span>
|
||||
<span class="comp-action">14 Farb-Klassen für Protein- und Küchenstil-Gradients hinzufügen (<code>.protein-haehnchen</code>, <code>.cuisine-asiatisch</code>, …).</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-label">09 · Accessibility</div>
|
||||
<h2>A11y-Anforderungen</h2>
|
||||
|
||||
<ul>
|
||||
<li><code>.scene</code>: <code>role="button"</code>, <code>tabindex="0"</code>, <code>aria-expanded="false|true"</code>, <code>aria-label="[Rezeptname] — Details anzeigen"</code></li>
|
||||
<li><code>.card-back</code>: <code>aria-hidden="true"</code> solange nicht geflippt</li>
|
||||
<li>× Schließen-Button: <code>aria-label="Schließen"</code>, <code>type="button"</code></li>
|
||||
<li>Keyboard: <code>Enter</code> / <code>Space</code> flippt, <code>Escape</code> dreht zurück</li>
|
||||
<li>Dimming: gedimmte Kacheln bekommen <code>aria-hidden="true"</code> wenn eine andere geflippt ist</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user