docs(specs): add final frontend specs for members and settings Kachel views

Finalised implementation specs for /members (E2) and /settings (E1)
pages using the chosen Kachel (card grid) variation. Members spec
covers 6 states including role-change inline control and remove
confirmation dialog; notes backend gaps (DELETE/PATCH member
endpoints). Settings spec covers hub layout, D3 staples sub-page,
hover and empty states.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 15:06:11 +02:00
parent f0bbb3b009
commit 6dd0b7ac93
2 changed files with 1605 additions and 0 deletions

View File

@@ -0,0 +1,700 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>E1 — Einstellungen · Kachel-Ansicht · Finale Spezifikation</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: E1 Einstellungen Kachel-Ansicht, Finale Spezifikation
version: 1.0
journey: J8 Edit pantry staples
routes: /settings (E1 hub) → /household/staples?ctx=settings (D3)
screens: E1, D3
chosen-variation: V2 Kachel-Ansicht (Card sections)
last-updated: 2026-04-09
NAVIGATION STRUCTURE:
E1 (/settings) → Hub with 3 cards:
Card 1 "Vorräte" → navigates to D3 (/household/staples?ctx=settings)
Card 2 "Mitglieder" → navigates to E2 (/members)
Card 3 "Profil" → navigates to /profile (not yet implemented)
DATA:
Vorräte count: derived from GET /v1/ingredient-categories response
(count ingredients where isStaple === true)
Mitglieder count: from layout data (locals.haushalt via GET /v1/households/mine/members)
Profil name/email: from locals.benutzer
NOTE: D3 = A3. StaplesManager component is reused with context="settings".
StaplesManager renders categories as StapleChip pill grids, NOT checkboxes.
Auto-save on toggle (debounced PATCH 300ms). No save button.
-->
<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;
--green-deeper: #1E4A26;
--yellow-tint: #FDF6D8;
--yellow-light: #F9E08A;
--yellow-text: #8A6800;
--color-error: #DC4C3E;
--blue-tint: #E6F1FB;
--blue: #185FA5;
--blue-dark: #0C447C;
--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-xl: 16px; --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,.05);
}
*, *::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 layout ── */
.doc { max-width: 1040px; margin: 0 auto; padding: 48px 40px 96px; }
.doc-header { padding-bottom: 28px; border-bottom: 1px solid var(--color-border); margin-bottom: 48px; display: flex; justify-content: space-between; align-items: flex-end; }
.doc-header h1 { font-family: var(--font-display); font-size: 26px; font-weight: 500; letter-spacing: -0.02em; }
.doc-header p { font-size: 13px; color: var(--color-text-muted); margin-top: 4px; }
.doc-meta { font-family: var(--font-mono); font-size: 11px; color: var(--color-text-muted); text-align: right; line-height: 1.9; }
.section-label { font-size: 10px; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-text-muted); padding-bottom: 10px; border-bottom: 1px solid var(--color-border); margin-bottom: 32px; margin-top: 56px; }
.intro { font-size: 14px; line-height: 1.75; max-width: 640px; margin-bottom: 40px; }
/* ── State sections ── */
.state { margin-bottom: 64px; }
.state-header { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 20px; }
.state-id { font-family: var(--font-mono); font-size: 10px; font-weight: 500; background: var(--color-subtle); color: var(--color-text-muted); padding: 2px 8px; border-radius: var(--radius-sm); white-space: nowrap; margin-top: 3px; }
.state-title { font-size: 16px; font-weight: 500; letter-spacing: -0.01em; }
.state-desc { font-size: 13px; color: var(--color-text-muted); margin-top: 2px; max-width: 540px; }
/* ── Preview containers ── */
.preview-wrap { display: flex; gap: 24px; align-items: flex-start; margin-bottom: 20px; }
.preview-d-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.preview-m-wrap { flex-shrink: 0; display: flex; flex-direction: column; gap: 6px; }
.preview-label { font-size: 9px; font-weight: 500; letter-spacing: 0.09em; text-transform: uppercase; color: var(--color-text-muted); }
.preview-d-clip { height: 340px; overflow: hidden; border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-page); }
.preview-d-scale { transform: scale(0.5); transform-origin: top left; width: 200%; }
.preview-m-clip { width: 196px; height: 340px; overflow: hidden; border: 1.5px solid var(--color-border); border-radius: 24px; background: var(--color-page); }
.preview-m-scale { transform: scale(0.5); transform-origin: top left; width: 200%; }
/* ── Notes ── */
.notes { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 14px 18px; }
.notes-label { font-size: 9px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-muted); margin-bottom: 8px; }
.notes ul { list-style: none; display: flex; flex-direction: column; gap: 4px; }
.notes li { font-size: 12px; color: var(--color-text-muted); line-height: 1.5; display: flex; align-items: flex-start; gap: 8px; }
.notes li::before { content: '→'; color: var(--green); font-weight: 500; flex-shrink: 0; }
/* ── AppShell chrome ── */
.shell { display: flex; min-height: 100vh; background: var(--color-page); font-family: var(--font-sans); }
.sidebar { width: 224px; min-width: 224px; background: white; border-right: 1px solid var(--color-border); display: flex; flex-direction: column; }
.sidebar-brand { padding: 14px 18px; border-bottom: 1px solid var(--color-border); }
.sidebar-brand-row { display: flex; align-items: center; gap: 8px; }
.sidebar-logo { width: 22px; height: 22px; background: var(--green); border-radius: var(--radius-sm); }
.sidebar-app { font-family: var(--font-display); font-size: 15px; font-weight: 500; }
.sidebar-household { font-size: 10px; color: var(--color-text-muted); margin-top: 1px; }
.sidebar-nav { flex: 1; padding: 4px 8px; }
.sidebar-group-label { font-size: 8px; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-text-muted); padding: 16px 12px 4px; }
.sidebar-item { display: flex; align-items: center; gap: 8px; padding: 7px 12px; border-radius: var(--radius-md); font-size: 13px; color: var(--color-text); text-decoration: none; }
.sidebar-item.active { background: var(--green-tint); color: var(--green-dark); font-weight: 500; }
.sidebar-item:not(.active):hover { background: var(--color-subtle); }
.sidebar-icon { width: 20px; text-align: center; font-size: 16px; }
/* ── Page content ── */
.page-content { flex: 1; padding: 32px 40px; }
.page-title { font-family: var(--font-display); font-size: 24px; font-weight: 500; letter-spacing: -0.02em; margin-bottom: 4px; }
.page-subtitle { font-size: 13px; color: var(--color-text-muted); margin-bottom: 28px; }
/* ── Settings card grid ── */
.settings-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 16px; }
.settings-grid-bottom { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px; }
/* ── Setting card ── */
.setting-card { background: white; border: 1px solid var(--color-border); border-radius: var(--radius-xl); padding: 24px; box-shadow: var(--shadow-card); cursor: pointer; text-decoration: none; color: inherit; display: flex; flex-direction: column; }
.setting-card:hover { box-shadow: var(--shadow-raised); border-color: #C0BFB8; }
.setting-card.primary { border-left: 3px solid var(--green-dark); }
.setting-card.primary:hover { border-left-color: var(--green-dark); }
.card-icon { font-size: 22px; margin-bottom: 12px; }
.card-stat { font-family: var(--font-display); font-size: 36px; font-weight: 500; letter-spacing: -0.02em; color: var(--green-dark); line-height: 1; margin-bottom: 2px; }
.card-stat-label { font-size: 11px; color: var(--color-text-muted); margin-bottom: 12px; }
.card-title { font-size: 15px; font-weight: 500; margin-bottom: 4px; }
.card-desc { font-size: 12px; color: var(--color-text-muted); line-height: 1.5; flex: 1; }
.card-cta { margin-top: 16px; display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; color: var(--green-dark); }
.card-cta-secondary { margin-top: 16px; display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; color: var(--color-text-muted); }
.card-meta { font-size: 12px; color: var(--color-text-muted); margin-bottom: 4px; }
/* ── D3 Staples page chrome ── */
.breadcrumb { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--color-text-muted); margin-bottom: 20px; }
.breadcrumb a { color: var(--color-text-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--color-text); }
.breadcrumb-sep { font-size: 10px; }
/* ── Staple chips ── */
.category-block { margin-bottom: 24px; }
.category-name { font-size: 10px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-muted); margin-bottom: 10px; }
.chip-wrap { display: flex; flex-wrap: wrap; gap: 6px; }
.chip { padding: 5px 12px; border-radius: var(--radius-full); border: 1px solid var(--color-border); font-size: 12px; font-weight: 500; cursor: pointer; white-space: nowrap; }
.chip.on { background: var(--green-dark); color: white; border-color: var(--green-dark); }
.chip.off { background: transparent; color: var(--color-text-muted); }
.chip.off:hover { border-color: var(--green-light); color: var(--green-dark); }
.save-note { font-size: 11px; color: var(--color-text-muted); margin-top: 16px; font-style: italic; }
/* ── Mobile shell ── */
.m-shell { display: flex; flex-direction: column; background: var(--color-page); }
.m-header { padding: 16px; background: white; border-bottom: 1px solid var(--color-border); }
.m-header-title { font-size: 16px; font-weight: 500; }
.m-content { flex: 1; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.m-card { background: white; border: 1px solid var(--color-border); border-radius: var(--radius-xl); padding: 16px; box-shadow: var(--shadow-card); }
.m-card.primary { border-left: 3px solid var(--green-dark); }
.m-card-stat { font-family: var(--font-display); font-size: 28px; font-weight: 500; color: var(--green-dark); line-height: 1; margin-bottom: 2px; }
.m-card-stat-label { font-size: 10px; color: var(--color-text-muted); margin-bottom: 8px; }
.m-card-title { font-size: 14px; font-weight: 500; margin-bottom: 3px; }
.m-card-desc { font-size: 11px; color: var(--color-text-muted); }
.m-card-cta { margin-top: 12px; font-size: 11px; font-weight: 500; color: var(--green-dark); }
.m-tabbar { display: flex; border-top: 1px solid var(--color-border); background: white; }
.m-tab { flex: 1; display: flex; flex-direction: column; align-items: center; padding: 8px 4px 4px; font-size: 10px; color: var(--color-text-muted); gap: 2px; }
.m-tab.active { color: var(--green-dark); }
.m-tab-icon { font-size: 20px; }
/* ── Agent section ── */
.agent-section { background: var(--color-text); color: #E8E8E2; padding: 40px 48px; margin-top: 64px; }
.agent-section h2 { font-size: 10px; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; color: #6B6A63; margin-bottom: 4px; }
.agent-section > p { font-size: 13px; color: #9A9990; margin-bottom: 28px; line-height: 1.6; max-width: 640px; }
.spec-comment { font-family: var(--font-mono); font-size: 11px; color: #3A3A36; margin-bottom: 32px; line-height: 1.9; white-space: pre-wrap; }
.agent-table { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: 11px; margin-bottom: 40px; }
.agent-table thead tr { border-bottom: 1px solid #2A2A26; }
.agent-table th { text-align: left; padding: 8px 14px; font-size: 9px; font-weight: 500; letter-spacing: 0.09em; text-transform: uppercase; color: #5A5A55; font-family: var(--font-sans); }
.agent-table td { padding: 9px 14px; border-bottom: 1px solid #1E1E1A; vertical-align: top; line-height: 1.5; }
.agent-table tr:last-child td { border-bottom: none; }
.agent-table td:first-child { color: #7A7A72; white-space: nowrap; }
.agent-table td:nth-child(2) { color: #E8E8E2; font-weight: 500; }
.agent-table td:nth-child(3) { color: #5A5A55; }
.group-row td { padding-top: 20px; font-family: var(--font-sans); font-size: 9px; font-weight: 500; letter-spacing: 0.09em; text-transform: uppercase; color: #3A3A36; border-bottom: none; }
</style>
</head>
<body>
<div class="doc">
<!-- Header -->
<div class="doc-header">
<div>
<h1>E1 — Einstellungen</h1>
<p>Kachel-Ansicht · Finale Spezifikation · Route: <code>/settings</code><code>/household/staples?ctx=settings</code></p>
</div>
<div class="doc-meta">
screens: E1, D3<br/>
journey: J8<br/>
variation: Kachel (V2)<br/>
version: 1.0<br/>
date: 2026-04-09
</div>
</div>
<p class="intro">
Die Einstellungsseite dient als Hub mit drei Kacheln: Vorräte (primäre Aktion, navigiert zu D3),
Mitglieder (navigiert zu E2) und Profil. Die Vorräte-Kachel zeigt die aktive Zutatenanzahl als
Display-Font-Zahl. D3 verwendet die bestehende StaplesManager-Komponente mit <code>context="settings"</code>.
</p>
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section-label">S1 — Hub-Ansicht (E1 /settings)</div>
<div class="state">
<div class="state-header">
<div class="state-id">S1</div>
<div>
<div class="state-title">Einstellungs-Hub — drei Kacheln</div>
<div class="state-desc">Vorräte-Kachel (2fr, primär mit grünem Akzentstreifen), Mitglieder-Kachel (1fr), Profil-Kachel (1fr). Desktop 2-spaltig oben, dann 2-spaltig unten.</div>
</div>
</div>
<div class="preview-wrap">
<div class="preview-d-wrap">
<div class="preview-label">Desktop</div>
<div class="preview-d-clip">
<div class="preview-d-scale">
<div class="shell">
<div class="sidebar">
<div class="sidebar-brand">
<div class="sidebar-brand-row"><div class="sidebar-logo"></div><span class="sidebar-app">Mealplan</span></div>
<div class="sidebar-household">Familie Raddatz</div>
</div>
<div class="sidebar-nav">
<div class="sidebar-group-label">Plan</div>
<a class="sidebar-item" href="#"><span class="sidebar-icon">📅</span>Planer</a>
<a class="sidebar-item" href="#"><span class="sidebar-icon">🍽</span>Rezepte</a>
<a class="sidebar-item" href="#"><span class="sidebar-icon">🛒</span>Einkauf</a>
<div class="sidebar-group-label">Haushalt</div>
<a class="sidebar-item" href="#"><span class="sidebar-icon">👥</span>Mitglieder</a>
<a class="sidebar-item active" href="#"><span class="sidebar-icon">⚙️</span>Einstellungen</a>
</div>
</div>
<div class="page-content">
<div class="page-title">Einstellungen</div>
<div class="page-subtitle">Familie Raddatz</div>
<div class="settings-grid">
<!-- Vorräte card (2fr, primary) -->
<a class="setting-card primary" href="#">
<div class="card-icon">🥫</div>
<div class="card-stat">14</div>
<div class="card-stat-label">von 32 Zutaten als Vorrat markiert</div>
<div class="card-title">Vorräte</div>
<div class="card-desc">Lege fest, welche Zutaten immer zu Hause sind. Sie werden beim Einkaufen automatisch herausgefiltert.</div>
<div class="card-cta">Vorräte bearbeiten →</div>
</a>
<!-- Mitglieder card (1fr) -->
<a class="setting-card" href="#">
<div class="card-icon">👥</div>
<div class="card-title">Mitglieder</div>
<div class="card-meta" style="margin-top:4px;">3 Mitglieder</div>
<div class="card-desc" style="margin-top:8px;">Haushaltsmitglieder einladen, Rollen verwalten.</div>
<div class="card-cta-secondary">Mitglieder verwalten →</div>
</a>
</div>
<div class="settings-grid-bottom">
<!-- Profil card -->
<a class="setting-card" href="#">
<div class="card-icon">👤</div>
<div class="card-title">Profil</div>
<div class="card-meta" style="margin-top:4px;">Marcel R.</div>
<div class="card-desc" style="margin-top:8px;">Name und E-Mail-Adresse anpassen.</div>
<div class="card-cta-secondary">Profil bearbeiten →</div>
</a>
<!-- Placeholder / future -->
<div style="border: 1.5px dashed var(--color-border); border-radius: var(--radius-xl); padding: 24px; display:flex; align-items:center; justify-content:center; color: var(--color-text-muted); font-size: 12px;">Weitere Einstellungen folgen</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="preview-m-wrap">
<div class="preview-label">Mobile</div>
<div class="preview-m-clip">
<div class="preview-m-scale">
<div class="m-shell" style="min-height:680px;">
<div class="m-header"><div class="m-header-title">Einstellungen</div></div>
<div class="m-content">
<div class="m-card primary">
<div class="m-card-stat">14</div>
<div class="m-card-stat-label">von 32 Vorräten aktiv</div>
<div class="m-card-title">Vorräte</div>
<div class="m-card-desc">Welche Zutaten hast du immer zu Hause?</div>
<div class="m-card-cta">Vorräte bearbeiten →</div>
</div>
<div class="m-card">
<div class="m-card-title">👥 Mitglieder</div>
<div class="m-card-desc" style="margin-top:4px;">3 Mitglieder · Einladen &amp; Rollen</div>
<div class="m-card-cta">Verwalten →</div>
</div>
<div class="m-card">
<div class="m-card-title">👤 Profil</div>
<div class="m-card-desc" style="margin-top:4px;">Marcel R.</div>
<div class="m-card-cta">Bearbeiten →</div>
</div>
</div>
<div class="m-tabbar">
<div class="m-tab"><div class="m-tab-icon">📅</div>Planer</div>
<div class="m-tab"><div class="m-tab-icon">🍽</div>Rezepte</div>
<div class="m-tab"><div class="m-tab-icon">🛒</div>Einkauf</div>
<div class="m-tab active"><div class="m-tab-icon">⚙️</div>Einstellungen</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="notes">
<div class="notes-label">Notizen</div>
<ul>
<li>Vorräte-Kachel: <code>grid-column: span 1</code> aber <code>2fr</code> Spaltenbreite im 2-Spalten-Grid. Grüner Linksstreifen (<code>border-left: 3px solid --green-dark</code>).</li>
<li>Stat-Zahl: Anzahl Zutaten mit <code>isStaple === true</code>, aus dem gleichen Load-Call der D3-Seite</li>
<li>Mitglieder-Karte: Anzahl aus <code>locals.haushalt</code> oder separatem API-Call; navigiert zu <code>/members</code></li>
<li>Profil-Karte: Name aus <code>locals.benutzer.name</code>; Zielseite <code>/profile</code> (noch nicht implementiert — Link disabled oder Placeholder)</li>
<li>Hover: <code>box-shadow: --shadow-raised</code>, leicht dunklerer Border</li>
<li>Alle Kacheln sind <code>&lt;a&gt;</code>-Tags für korrekte Navigation und Accessibility</li>
<li>Mobile: Kacheln stapeln sich vertikal in voller Breite, kein Grid</li>
</ul>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section-label">S2 — Vorräte-Seite (D3 /household/staples?ctx=settings)</div>
<div class="state">
<div class="state-header">
<div class="state-id">S2</div>
<div>
<div class="state-title">D3 — Vorräte bearbeiten (StaplesManager, context="settings")</div>
<div class="state-desc">Navigiert man von der Vorräte-Kachel aus, erscheint die bestehende StaplesManager-Komponente mit Breadcrumb zurück zu Einstellungen.</div>
</div>
</div>
<div class="preview-wrap">
<div class="preview-d-wrap">
<div class="preview-label">Desktop</div>
<div class="preview-d-clip">
<div class="preview-d-scale">
<div class="shell">
<div class="sidebar">
<div class="sidebar-brand"><div class="sidebar-brand-row"><div class="sidebar-logo"></div><span class="sidebar-app">Mealplan</span></div><div class="sidebar-household">Familie Raddatz</div></div>
<div class="sidebar-nav">
<div class="sidebar-group-label">Plan</div>
<a class="sidebar-item" href="#"><span class="sidebar-icon">📅</span>Planer</a>
<a class="sidebar-item" href="#"><span class="sidebar-icon">🍽</span>Rezepte</a>
<a class="sidebar-item" href="#"><span class="sidebar-icon">🛒</span>Einkauf</a>
<div class="sidebar-group-label">Haushalt</div>
<a class="sidebar-item" href="#"><span class="sidebar-icon">👥</span>Mitglieder</a>
<a class="sidebar-item active" href="#"><span class="sidebar-icon">⚙️</span>Einstellungen</a>
</div>
</div>
<div class="page-content">
<!-- Breadcrumb -->
<div class="breadcrumb">
<a href="#">← Einstellungen</a>
<span class="breadcrumb-sep">/</span>
<span>Vorräte</span>
</div>
<div class="page-title">Vorräte</div>
<div class="page-subtitle">Markierte Zutaten werden beim Einkaufen herausgefiltert.</div>
<!-- StaplesManager content (context="settings") -->
<div class="category-block">
<div class="category-name">Gewürze &amp; Öle</div>
<div class="chip-wrap">
<span class="chip on">Salz</span>
<span class="chip on">Pfeffer</span>
<span class="chip on">Olivenöl</span>
<span class="chip off">Paprika</span>
<span class="chip off">Kreuzkümmel</span>
<span class="chip on">Knoblauch</span>
<span class="chip off">Chili</span>
</div>
</div>
<div class="category-block">
<div class="category-name">Grundnahrung</div>
<div class="chip-wrap">
<span class="chip on">Reis</span>
<span class="chip off">Nudeln</span>
<span class="chip on">Mehl</span>
<span class="chip on">Zucker</span>
<span class="chip off">Linsen</span>
<span class="chip off">Hülsenfrüchte</span>
</div>
</div>
<div class="category-block">
<div class="category-name">Kühlschrank</div>
<div class="chip-wrap">
<span class="chip on">Butter</span>
<span class="chip on">Eier</span>
<span class="chip off">Milch</span>
<span class="chip off">Käse</span>
<span class="chip off">Joghurt</span>
</div>
</div>
<div class="save-note">Änderungen werden automatisch gespeichert. Gilt ab der nächsten Einkaufsliste.</div>
</div>
</div>
</div>
</div>
</div>
<div class="preview-m-wrap">
<div class="preview-label">Mobile</div>
<div class="preview-m-clip">
<div class="preview-m-scale">
<div class="m-shell" style="min-height:680px;">
<div class="m-header">
<div style="font-size:11px;color:var(--color-text-muted);margin-bottom:2px;">← Einstellungen</div>
<div class="m-header-title">Vorräte</div>
</div>
<div class="m-content" style="gap:16px;">
<div>
<div class="category-name">Gewürze &amp; Öle</div>
<div class="chip-wrap">
<span class="chip on" style="font-size:11px;">Salz</span>
<span class="chip on" style="font-size:11px;">Pfeffer</span>
<span class="chip on" style="font-size:11px;">Olivenöl</span>
<span class="chip off" style="font-size:11px;">Paprika</span>
<span class="chip on" style="font-size:11px;">Knoblauch</span>
</div>
</div>
<div>
<div class="category-name">Grundnahrung</div>
<div class="chip-wrap">
<span class="chip on" style="font-size:11px;">Reis</span>
<span class="chip off" style="font-size:11px;">Nudeln</span>
<span class="chip on" style="font-size:11px;">Mehl</span>
<span class="chip on" style="font-size:11px;">Zucker</span>
</div>
</div>
</div>
<div class="m-tabbar">
<div class="m-tab"><div class="m-tab-icon">📅</div>Planer</div>
<div class="m-tab"><div class="m-tab-icon">🍽</div>Rezepte</div>
<div class="m-tab"><div class="m-tab-icon">🛒</div>Einkauf</div>
<div class="m-tab active"><div class="m-tab-icon">⚙️</div>Einstellungen</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="notes">
<div class="notes-label">Notizen</div>
<ul>
<li>Breadcrumb "← Einstellungen" navigiert zurück zu <code>/settings</code></li>
<li>"Einstellungen" bleibt in der Sidebar aktiv (kein eigener Nav-Eintrag für Vorräte)</li>
<li>StaplesManager-Komponente unverändert mit <code>context="settings"</code> (3-spaltig auf md+)</li>
<li>Kein Speichern-Button. Hinweistext "Änderungen werden automatisch gespeichert." unter den Chips</li>
<li>Mobile: Chips statt 3-spaltig 1-spaltig (volle Breite), Flex-Wrap bleibt bestehen</li>
<li>D3 hat eigene <code>+page.server.ts</code> die <code>+page.svelte</code> bei <code>/household/staples</code> gibt es bereits</li>
</ul>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section-label">S3 — Hover-Zustand der Kacheln</div>
<div class="state">
<div class="state-header">
<div class="state-id">S3</div>
<div>
<div class="state-title">Kachel-Hover — visuelles Feedback</div>
<div class="state-desc">Alle Kacheln sind anklickbare Links. Hover hebt die Kachel visuell an.</div>
</div>
</div>
<div class="preview-wrap">
<div class="preview-d-wrap">
<div class="preview-label">Desktop — Vorräte-Kachel im Hover</div>
<div class="preview-d-clip">
<div class="preview-d-scale">
<div class="shell">
<div class="sidebar">
<div class="sidebar-brand"><div class="sidebar-brand-row"><div class="sidebar-logo"></div><span class="sidebar-app">Mealplan</span></div><div class="sidebar-household">Familie Raddatz</div></div>
<div class="sidebar-nav">
<div class="sidebar-group-label">Haushalt</div>
<a class="sidebar-item" href="#"><span class="sidebar-icon">👥</span>Mitglieder</a>
<a class="sidebar-item active" href="#"><span class="sidebar-icon">⚙️</span>Einstellungen</a>
</div>
</div>
<div class="page-content">
<div class="page-title">Einstellungen</div>
<div class="page-subtitle">Familie Raddatz</div>
<div class="settings-grid">
<!-- Hovered Vorräte card -->
<a class="setting-card primary" href="#" style="box-shadow:var(--shadow-raised);border-color:#C0BFB8;cursor:pointer;">
<div class="card-icon">🥫</div>
<div class="card-stat">14</div>
<div class="card-stat-label">von 32 Zutaten als Vorrat markiert</div>
<div class="card-title">Vorräte</div>
<div class="card-desc">Lege fest, welche Zutaten immer zu Hause sind.</div>
<div class="card-cta">Vorräte bearbeiten →</div>
</a>
<a class="setting-card" href="#">
<div class="card-icon">👥</div>
<div class="card-title">Mitglieder</div>
<div class="card-meta" style="margin-top:4px;">3 Mitglieder</div>
<div class="card-desc" style="margin-top:8px;">Haushaltsmitglieder einladen, Rollen verwalten.</div>
<div class="card-cta-secondary">Mitglieder verwalten →</div>
</a>
</div>
<div class="settings-grid-bottom">
<a class="setting-card" href="#"><div class="card-icon">👤</div><div class="card-title">Profil</div><div class="card-meta" style="margin-top:4px;">Marcel R.</div><div class="card-desc" style="margin-top:8px;">Name und E-Mail anpassen.</div><div class="card-cta-secondary">Profil bearbeiten →</div></a>
<div style="border:1.5px dashed var(--color-border);border-radius:var(--radius-xl);padding:24px;display:flex;align-items:center;justify-content:center;color:var(--color-text-muted);font-size:12px;">Weitere folgen</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="notes">
<div class="notes-label">Notizen</div>
<ul>
<li>Hover: <code>box-shadow: --shadow-raised</code> + <code>border-color: #C0BFB8</code></li>
<li>Vorräte-Kachel behält den grünen Linksstreifen auch im Hover</li>
<li>Transition: <code>box-shadow 150ms ease, border-color 150ms ease</code></li>
<li>Cursor: <code>pointer</code> auf allen Kacheln</li>
<li>Focus-visible: <code>outline: 2px solid --green-dark; outline-offset: 2px</code></li>
</ul>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section-label">S4 — Leerer Zustand (kein Vorrat gesetzt)</div>
<div class="state">
<div class="state-header">
<div class="state-id">S4</div>
<div>
<div class="state-title">Vorräte-Kachel bei 0 aktiven Vorräten</div>
<div class="state-desc">Wenn noch kein Vorrat gesetzt wurde, zeigt die Kachel eine Einladung zur Aktion statt der Zahl.</div>
</div>
</div>
<div class="preview-wrap">
<div class="preview-d-wrap">
<div class="preview-label">Desktop — 0 Vorräte</div>
<div class="preview-d-clip">
<div class="preview-d-scale">
<div class="shell">
<div class="sidebar">
<div class="sidebar-brand"><div class="sidebar-brand-row"><div class="sidebar-logo"></div><span class="sidebar-app">Mealplan</span></div><div class="sidebar-household">Familie Raddatz</div></div>
<div class="sidebar-nav">
<div class="sidebar-group-label">Haushalt</div>
<a class="sidebar-item" href="#"><span class="sidebar-icon">👥</span>Mitglieder</a>
<a class="sidebar-item active" href="#"><span class="sidebar-icon">⚙️</span>Einstellungen</a>
</div>
</div>
<div class="page-content">
<div class="page-title">Einstellungen</div>
<div class="page-subtitle">Familie Raddatz</div>
<div class="settings-grid">
<a class="setting-card primary" href="#">
<div class="card-icon">🥫</div>
<!-- Empty state: no big number, instead prompt -->
<div style="font-size:13px;color:var(--color-text-muted);margin-bottom:8px;">Noch keine Vorräte eingerichtet</div>
<div class="card-title">Vorräte</div>
<div class="card-desc">Lege fest, welche Zutaten immer zu Hause sind. Sie werden beim Einkaufen automatisch herausgefiltert.</div>
<div class="card-cta">Jetzt einrichten →</div>
</a>
<a class="setting-card" href="#">
<div class="card-icon">👥</div>
<div class="card-title">Mitglieder</div>
<div class="card-meta" style="margin-top:4px;">1 Mitglied</div>
<div class="card-desc" style="margin-top:8px;">Haushaltsmitglieder einladen, Rollen verwalten.</div>
<div class="card-cta-secondary">Mitglieder verwalten →</div>
</a>
</div>
<div class="settings-grid-bottom">
<a class="setting-card" href="#"><div class="card-icon">👤</div><div class="card-title">Profil</div><div class="card-meta" style="margin-top:4px;">Marcel R.</div><div class="card-cta-secondary" style="margin-top:8px;">Bearbeiten →</div></a>
<div style="border:1.5px dashed var(--color-border);border-radius:var(--radius-xl);padding:24px;display:flex;align-items:center;justify-content:center;color:var(--color-text-muted);font-size:12px;">Weitere folgen</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="notes">
<div class="notes-label">Notizen</div>
<ul>
<li>Wenn <code>stapleCount === 0</code>: Stat-Zahl weglassen, stattdessen "Noch keine Vorräte eingerichtet" in muted</li>
<li>CTA-Text ändert sich: "Jetzt einrichten →" statt "Vorräte bearbeiten →"</li>
<li>Kachel navigiert weiterhin zu D3 — StaplesManager lädt immer, unabhängig vom Count</li>
</ul>
</div>
</div>
<!-- ─── Machine-readable agent section ─── -->
<div class="agent-section">
<h2>Maschinen-lesbare Spezifikation</h2>
<p>Diese Sektion enthält verbindliche Implementierungsregeln für den Coding-Agenten.</p>
<pre class="spec-comment">
/* spec:rules — E1 Einstellungen Kachel
*
* ROUTE: /settings (E1 hub)
* DATA LOAD (page.server.ts):
* - GET /v1/ingredient-categories to count stapleCount
* stapleCount = sum of ingredients where isStaple === true
* - member count available from layout data (locals.haushalt)
* or fetch GET /v1/households/mine/members and count length
* - profile name from locals.benutzer.name
*
* LAYOUT: E1 HUB
* grid: 2 columns (2fr 1fr) top row + 2 columns (1fr 1fr) bottom row; gap 16px
* mobile: single column, full-width cards, gap 12px
*
* CARD: all cards are <a> tags (href to target route)
* border-radius: --radius-xl
* border: 1px solid --color-border
* bg: white
* padding: 24px desktop / 16px mobile
* hover: box-shadow --shadow-raised, border-color #C0BFB8
* transition: box-shadow 150ms ease, border-color 150ms ease
* cursor: pointer
* focus-visible: outline 2px solid --green-dark, offset 2px
*
* VORRÄTE CARD (primary)
* border-left: 3px solid --green-dark
* stat number: font-family --font-display, font-size 36px, color --green-dark
* stat label: "von {total} Zutaten als Vorrat markiert", 11px, --color-text-muted
* empty state (stapleCount === 0): hide stat, show "Noch keine Vorräte eingerichtet"
* cta: "Vorräte bearbeiten →" (empty: "Jetzt einrichten →")
* href: /household/staples?ctx=settings
*
* MITGLIEDER CARD
* meta: "{memberCount} Mitglieder"
* href: /members
*
* PROFIL CARD
* meta: locals.benutzer.name
* href: /profile (not yet implemented — render as disabled or placeholder)
*
* ROUTE: /household/staples?ctx=settings (D3)
* component: StaplesManager with context="settings" (already exists)
* breadcrumb: "← Einstellungen" linking back to /settings
* sidebar: "Einstellungen" stays active (no separate nav item for staples)
* no save button — StaplesManager auto-saves via debounced PATCH 300ms
* hint text below grid: "Änderungen werden automatisch gespeichert. Gilt ab der nächsten Einkaufsliste."
* grid: 3-col on md+ (context="settings" already sets this in StaplesManager)
*
* CHIP STYLES (for reference — rendered by StapleChip, do NOT reimplement)
* selected: bg --green-dark, color white, border-color --green-dark
* unselected: bg transparent, color --color-text-muted, border 1px solid --color-border
* hover unselected: border-color --green-light, color --green-dark
*
* CATEGORY LABEL TYPOGRAPHY
* font-size: 10px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase
* color: --color-text-muted; margin-bottom: 10px
*/
</pre>
<table class="agent-table">
<thead>
<tr><th>Property</th><th>Value</th><th>Notes</th></tr>
</thead>
<tbody>
<tr class="group-row"><td colspan="3">E1 Hub Layout</td></tr>
<tr><td>grid-desktop</td><td>2fr 1fr / 1fr 1fr</td><td>top row / bottom row</td></tr>
<tr><td>grid-mobile</td><td>1fr</td><td>full-width stack</td></tr>
<tr><td>gap</td><td>16px desktop / 12px mobile</td><td></td></tr>
<tr class="group-row"><td colspan="3">Vorräte Card</td></tr>
<tr><td>stat-font</td><td>--font-display, 36px, --green-dark</td><td>Fraunces</td></tr>
<tr><td>accent-border</td><td>border-left: 3px solid --green-dark</td><td>primary indicator</td></tr>
<tr><td>stat-source</td><td>count isStaple=true from /v1/ingredient-categories</td><td>load in page.server.ts</td></tr>
<tr><td>empty-state</td><td>hide stat; show muted text</td><td>when stapleCount === 0</td></tr>
<tr><td>href</td><td>/household/staples?ctx=settings</td><td>D3 route</td></tr>
<tr class="group-row"><td colspan="3">D3 Staples Page</td></tr>
<tr><td>component</td><td>StaplesManager context="settings"</td><td>existing, do not modify</td></tr>
<tr><td>breadcrumb</td><td>← Einstellungen → /settings</td><td>above page title</td></tr>
<tr><td>active-nav</td><td>Einstellungen in sidebar</td><td>not a separate nav entry</td></tr>
<tr><td>save-hint</td><td>"Änderungen werden automatisch gespeichert."</td><td>below chip grid</td></tr>
<tr><td>debounce</td><td>300ms (in StaplesManager)</td><td>do not add extra debounce</td></tr>
</tbody>
</table>
</div>
</div>
</body>
</html>